Show message instead of sending empty package
[cds-indico.git] / indico / modules / attachments / controllers / event_package.py
bloba4e514763c0d38438fcd8fae05530773dab2efa0
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 datetime
20 import os
21 from collections import OrderedDict
22 from tempfile import NamedTemporaryFile
23 from zipfile import ZipFile
25 from flask import session, flash
26 from sqlalchemy import cast, Date
28 from indico.core.config import Config
29 from indico.core.db.sqlalchemy.links import LinkType
30 from indico.util.date_time import format_date
31 from indico.util.i18n import _
32 from indico.util.fs import secure_filename
33 from indico.util.string import to_unicode
34 from indico.util.tasks import delete_file
35 from indico.web.flask.util import send_file
36 from indico.web.forms.base import FormDefaults
37 from indico.modules.attachments.forms import AttachmentPackageForm
38 from indico.modules.attachments.models.attachments import Attachment, AttachmentFile, AttachmentType
39 from indico.modules.attachments.models.folders import AttachmentFolder
40 from MaKaC.conference import SubContribution
43 def _get_start_date(obj):
44 if isinstance(obj, SubContribution):
45 return obj.getContribution().getAdjustedStartDate()
46 else:
47 return obj.getAdjustedStartDate()
50 class AttachmentPackageGeneratorMixin:
52 def _filter_attachments(self, filter_data):
53 added_since = filter_data.get('added_since', None)
54 filter_type = filter_data['filter_type']
55 attachments = []
57 if filter_type == 'all':
58 attachments = self._get_all_attachments(added_since)
59 elif filter_type == 'sessions':
60 attachments = self._filter_by_sessions(filter_data.get('sessions', []), added_since)
61 elif filter_type == 'contributions':
62 attachments = self._filter_by_contributions(filter_data.get('contributions', []), added_since)
63 elif filter_type == 'dates':
64 attachments = self._filter_by_dates(filter_data.get('dates'))
66 return self._filter_protected(attachments)
68 def _filter_protected(self, attachments):
69 return [attachment for attachment in attachments if attachment.can_access(session.user)]
71 def _get_all_attachments(self, added_since):
72 query = self._build_base_query()
74 if added_since:
75 query = self._filter_by_date(query, added_since)
77 return [att for att in query if att.folder.linked_object and _get_start_date(att.folder.linked_object)]
79 def _build_base_query(self):
80 return Attachment.find(Attachment.type == AttachmentType.file, ~AttachmentFolder.is_deleted,
81 ~Attachment.is_deleted, AttachmentFolder.event_id == int(self._conf.getId()),
82 _join=AttachmentFolder)
84 def _filter_by_sessions(self, session_ids, added_since):
85 query = self._build_base_query().filter(AttachmentFolder.link_type.in_([LinkType.session, LinkType.contribution,
86 LinkType.subcontribution]))
88 if added_since:
89 query = self._filter_by_date(query, added_since)
91 return [att for att in query if att.folder.linked_object.getSession()
92 and att.folder.linked_object.getSession().getId() in session_ids]
94 def _filter_by_contributions(self, contribution_ids, added_since):
95 query = self._build_base_query().filter(AttachmentFolder.contribution_id.in_(contribution_ids),
96 AttachmentFolder.link_type.in_([LinkType.contribution,
97 LinkType.subcontribution]))
99 if added_since:
100 query = self._filter_by_date(query, added_since)
102 return [att for att in query if att.folder.linked_object and _get_start_date(att.folder.linked_object)]
104 def _filter_by_dates(self, dates):
105 return [att for att in self._build_base_query() if att.folder.linked_object and
106 unicode(_get_start_date(att.folder.linked_object)) in dates]
108 def _filter_by_date(self, query, added_since):
109 return query.join(Attachment.file).filter(cast(AttachmentFile.created_dt, Date) >= added_since)
111 def _generate_zip_file(self, attachments):
112 temp_file = NamedTemporaryFile(suffix='indico.tmp', dir=Config.getInstance().getTempDir())
113 with ZipFile(temp_file.name, 'w', allowZip64=True) as zip_handler:
114 self.used = set()
115 for attachment in attachments:
116 name = self._prepare_folder_structure(attachment)
117 self.used.add(name)
118 with attachment.file.storage.get_local_path(attachment.file.storage_file_id) as filepath:
119 zip_handler.write(filepath, name)
121 # Delete the temporary file after some time. Even for a large file we don't
122 # need a higher delay since the webserver will keep it open anyway until it's
123 # done sending it to the client.
124 delete_file.apply_async(args=[temp_file.name], countdown=3600)
125 temp_file.delete = False
126 return send_file('material-{}.zip'.format(self._conf.id), temp_file.name, 'application/zip', inline=False)
128 def _prepare_folder_structure(self, attachment):
129 event_dir = secure_filename(self._conf.getTitle(), None)
130 segments = [event_dir] if event_dir else []
131 segments.extend(self._get_base_path(attachment))
132 if not attachment.folder.is_default:
133 segments.append(secure_filename(attachment.folder.title, unicode(attachment.folder.id)))
134 segments.append(attachment.file.filename)
135 path = os.path.join(*filter(None, segments))
136 while path in self.used:
137 # prepend the id if there's a path collision
138 segments[-1] = '{}-{}'.format(attachment.id, segments[-1])
139 path = os.path.join(*filter(None, segments))
140 return path
142 def _get_base_path(self, attachment):
143 obj = linked_object = attachment.folder.linked_object
144 paths = []
145 while obj != self._conf:
146 owner = obj.getOwner()
147 if isinstance(obj, SubContribution):
148 start_date = owner.getAdjustedStartDate()
149 else:
150 start_date = obj.getAdjustedStartDate()
152 if start_date is not None:
153 paths.append(secure_filename(start_date.strftime('%H%M_{}').format(obj.getTitle()), ''))
154 else:
155 paths.append(secure_filename(obj.getTitle(), unicode(obj.getId())))
156 obj = owner
158 if isinstance(linked_object, SubContribution):
159 linked_obj_start_date = linked_object.getOwner().getAdjustedStartDate()
160 else:
161 linked_obj_start_date = linked_object.getAdjustedStartDate()
163 if attachment.folder.linked_object != self._conf and linked_obj_start_date is not None:
164 paths.append(secure_filename(linked_obj_start_date.strftime('%Y%m%d_%A'), ''))
166 return reversed(paths)
169 class AttachmentPackageMixin(AttachmentPackageGeneratorMixin):
170 wp = None
172 def _process(self):
173 form, skipped_fields = self._prepare_form()
174 if form.validate_on_submit():
175 attachments = self._filter_attachments(form.data)
176 if attachments:
177 return self._generate_zip_file(attachments)
178 else:
179 flash(_('There are no materials matching your criteria.'), 'warning')
181 return self.wp.render_template('generate_package.html', self._conf, form=form, skipped_fields=skipped_fields)
183 def _prepare_form(self):
184 form = AttachmentPackageForm(obj=FormDefaults(filter_type='all'))
185 filter_types = OrderedDict(all=_('Everything'))
186 sessions = self._load_session_data()
187 contributions = self._load_contribution_data()
188 dates = self._load_dates()
190 if sessions:
191 filter_types['sessions'] = _('Specific sessions')
192 if contributions:
193 filter_types['contributions'] = _('Specific contributions')
194 if dates:
195 filter_types['dates'] = _('Specific days')
197 skipped_fields = {'sessions', 'contributions', 'dates'} - filter_types.viewkeys()
198 form.filter_type.choices = filter_types.items()
199 form.sessions.choices = sessions
200 form.contributions.choices = contributions
201 form.dates.choices = dates
202 return form, skipped_fields
204 def _load_session_data(self):
205 return [(session.getId(), to_unicode(session.getTitle())) for session in self._conf.getSessionList()]
207 def _load_contribution_data(self):
208 return [(contrib.getId(), to_unicode(contrib.getTitle()))
209 for contrib in self._conf.getContributionList() if contrib.getOwner() == self._conf
210 and contrib.getStartDate()]
212 def _load_dates(self):
213 offset = self._conf.getAdjustedEndDate() - self._conf.getAdjustedStartDate()
214 return [(unicode(self._conf.getAdjustedStartDate() + datetime.timedelta(days=day_number)),
215 format_date(self._conf.getAdjustedStartDate() + datetime.timedelta(days=day_number), 'short'))
216 for day_number in xrange(offset.days + 1)]