Correct way to prepare folder structure in material package
[cds-indico.git] / indico / modules / attachments / controllers / event_package.py
blobdc24209c70b19387097c026bcb112ba5574fe6c9
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 from __future__ import unicode_literals
19 import os
20 from operator import itemgetter
21 from tempfile import NamedTemporaryFile
22 from zipfile import ZipFile
24 from flask import session
25 from sqlalchemy import cast, Date
27 from indico.core.config import Config
28 from indico.core.db.sqlalchemy.links import LinkType
29 from indico.util.fs import secure_filename
30 from indico.util.date_time import format_date
31 from indico.util.string import to_unicode
32 from indico.web.flask.util import send_file
33 from indico.modules.attachments.forms import AttachmentPackageForm
34 from indico.modules.attachments.models.attachments import Attachment, AttachmentFile, AttachmentType
35 from indico.modules.attachments.models.folders import AttachmentFolder
37 from MaKaC.conference import SubContribution
40 class AttachmentPackageMixin:
41 wp = None
43 def _process(self):
44 form = self._prepare_form()
45 if form.validate_on_submit():
46 return self._generate_zip_file(self._filter_attachments(form))
48 return self.wp.render_template('generate_package.html', self._conf, form=form)
50 def _prepare_form(self):
51 form = AttachmentPackageForm()
52 form.sessions.choices = self._load_session_data()
53 form.contributions.choices = self._load_contribution_data()
54 form.contributions_schedule_dates.choices = self._load_schedule_data()
55 return form
57 def _load_session_data(self):
58 return [(session.getId(), to_unicode(session.getTitle())) for session in self._conf.getSessionList()]
60 def _load_contribution_data(self):
61 return [(contrib.getId(), to_unicode(contrib.getTitle()))
62 for contrib in self._conf.getContributionList() if contrib.getOwner() == self._conf
63 and contrib.getStartDate()]
65 def _load_schedule_data(self):
66 dates = {contrib.getStartDate().date() for contrib in self._conf.getContributionList() if contrib.getStartDate()}
67 return sorted([(unicode(d), format_date(d, 'short')) for d in dates], key=itemgetter(1))
69 def _filter_attachments(self, form):
70 attachments = []
71 added_since = form.added_since.data
72 attachments.extend(self._filter_protected(self._filter_top_level_attachments(added_since)))
73 attachments.extend(self._filter_protected(self._filter_by_sessions(form.sessions.data, added_since)))
75 contribution_ids = set(form.contributions.data +
76 self._get_contributions_by_schedule_date(form.contributions_schedule_dates.data))
77 attachments.extend(self._filter_protected(self._filter_by_contributions(contribution_ids, added_since)))
78 return attachments
80 def _filter_protected(self, attachments):
81 return [attachment for attachment in attachments if attachment.can_access(session.user)]
83 def _filter_top_level_attachments(self, added_since):
84 query = self._build_base_query().filter(AttachmentFolder.linked_object == self._conf)
86 if added_since:
87 query = self._filter_by_date(query, added_since)
89 return query.all()
91 def _build_base_query(self):
92 return Attachment.find(Attachment.type == AttachmentType.file, ~AttachmentFolder.is_deleted,
93 ~Attachment.is_deleted, AttachmentFolder.event_id == int(self._conf.getId()),
94 _join=AttachmentFolder)
96 def _filter_by_sessions(self, session_ids, added_since):
97 query = self._build_base_query().filter(AttachmentFolder.link_type == LinkType.session)
98 if session_ids:
99 query = query.filter(AttachmentFolder.session_id.in_(session_ids))
101 if added_since:
102 query = self._filter_by_date(query, added_since)
104 return query.all()
106 def _get_contributions_by_schedule_date(self, dates):
107 return [contribution.getId() for contribution in self._conf.getContributionList()
108 if contribution.getStartDate() and str(contribution.getStartDate().date()) in dates]
110 def _filter_by_contributions(self, contribution_ids, added_since):
111 query = self._build_base_query().filter(AttachmentFolder.link_type.in_([LinkType.contribution,
112 LinkType.subcontribution]))
113 if contribution_ids:
114 query = query.filter(AttachmentFolder.contribution_id.in_(contribution_ids))
115 else:
116 query = query.filter(AttachmentFolder.contribution_id is not None)
118 if added_since:
119 query = self._filter_by_date(query, added_since)
121 return query.all()
123 def _filter_by_date(self, query, added_since):
124 return query.filter(cast(AttachmentFile.created_dt, Date) >= added_since)
126 def _generate_zip_file(self, attachments):
127 # XXX: could use a celery task to delay the temporary file after a day or so.
128 # right now this relies on an external cronjob to do so...
129 temp_file = NamedTemporaryFile(suffix='indico.tmp', dir=Config.getInstance().getTempDir(), delete=False)
130 with ZipFile(temp_file.name, 'w', allowZip64=True) as zip_handler:
131 for attachment in attachments:
132 name = self._prepare_folder_structure(attachment)
133 with attachment.file.storage.get_local_path(attachment.file.storage_file_id) as filepath:
134 zip_handler.write(filepath, name)
136 return send_file('material-{}.zip'.format(self._conf.id), temp_file.name, 'application/zip', inline=False)
138 def _prepare_folder_structure(self, attachment):
139 event_dir = secure_filename(self._conf.getTitle(), None)
140 segments = [event_dir] if event_dir else []
141 segments.extend(self._get_base_path(attachment))
142 segments.extend([secure_filename(attachment.folder.title, unicode(attachment.folder.id)),
143 attachment.file.filename])
144 return os.path.join(*segments)
146 def _get_base_path(self, attachment):
147 obj = linked_object = attachment.folder.linked_object
148 paths = []
149 while obj != self._conf:
150 owner = obj.getOwner()
151 if isinstance(obj, SubContribution):
152 start_date = owner.getAdjustedStartDate()
153 else:
154 start_date = obj.getAdjustedStartDate()
156 paths.append(secure_filename(start_date.strftime('%H%M_{}').format(obj.getTitle()), ''))
157 obj = owner
159 if isinstance(linked_object, SubContribution):
160 linked_obj_start_date = linked_object.getOwner().getAdjustedStartDate()
161 else:
162 linked_obj_start_date = linked_object.getAdjustedStartDate()
164 if attachment.folder.linked_object != self._conf:
165 paths.append(secure_filename(linked_obj_start_date.strftime('%Y%m%d_%A'), ''))
167 return reversed(paths)