def _get_person_link(self, data, extra_data=None): extra_data = extra_data or {} person = get_event_person(self.event, data, create_untrusted_persons=self.create_untrusted_persons, allow_external=True) person_data = {'title': next((x.value for x in UserTitle if data.get('title') == orig_string(x.title)), UserTitle.none), 'first_name': data.get('firstName', ''), 'last_name': data['familyName'], 'affiliation': data.get('affiliation', ''), 'address': data.get('address', ''), 'phone': data.get('phone', ''), 'display_order': data['displayOrder']} person_data.update(extra_data) person_link = None if self.object and inspect(person).persistent: person_link = self.person_link_cls.find_first(person=person, object=self.object) if not person_link: person_link = self.person_link_cls(person=person) person_link.populate_from_dict(person_data) email = data.get('email', '').lower() if email != person_link.email: if not self.event or not self.event.persons.filter_by(email=email).first(): person_link.person.email = email person_link.person.user = get_user_by_email(email) if inspect(person).persistent: signals.event.person_updated.send(person_link.person) else: raise UserValueError(_('There is already a person with the email {}').format(email)) return person_link
def _process(self): if self.editable: raise UserValueError(_('Editable already exists')) args = parser.parse({ 'files': EditingFilesField(self.event, self.contrib, self.editable_type, required=True) }) service_url = editing_settings.get(self.event, 'service_url') initial_state = InitialRevisionState.new if service_url else InitialRevisionState.ready_for_review editable = create_new_editable(self.contrib, self.editable_type, session.user, args['files'], initial_state) if service_url: try: service_handle_new_editable(editable, session.user) except ServiceRequestFailed: raise ServiceUnavailable( _('Submission failed, please try again later.')) return '', 201
def _reschedule_duration(self): for entry, successor in window(self._entries): duration = successor.start_dt - entry.start_dt - self.gap if duration <= timedelta(0): raise UserValueError(_("The chosen time gap would result in an entry with a duration of less than a " "minute. Please choose a smaller gap between entries.")) entry.object.duration = duration
def create_form(self, event, existing_vc_room=None, existing_event_vc_room=None): """Override the default room form creation mechanism.""" if existing_vc_room and request.method != 'POST': try: self.refresh_room(existing_vc_room, event) except VCRoomNotFoundError as exc: raise UserValueError(str(exc)) except VCRoomError: # maybe a temporary issue - we just keep going and fail when saving in # case it's something more persistent pass form = super().create_form( event, existing_vc_room=existing_vc_room, existing_event_vc_room=existing_event_vc_room) if existing_vc_room: form.host_choice.render_kw = {'disabled': True} form.host_user.render_kw = {'disabled': True} if self.settings.get('allow_webinars'): # if we're editing a VC room, we will not allow the meeting type to be changed form.meeting_type.render_kw = {'disabled': True} if form.data['meeting_type'] == 'webinar': # webinar hosts cannot be changed through the API form.host_choice.render_kw = {'disabled': True} form.host_user.render_kw = {'disabled': True} elif not form.is_submitted(): form.password.data = gen_random_password() return form
def _process(self): data = request.json required_keys = {'contribution_ids', 'day'} allowed_keys = required_keys | {'session_block_id'} if set(data.viewkeys()) > allowed_keys: raise BadRequest('Invalid keys found') elif required_keys > set(data.viewkeys()): raise BadRequest('Required keys missing') entries = [] day = dateutil.parser.parse(data['day']).date() query = Contribution.query.with_parent(self.event).filter( Contribution.id.in_(data['contribution_ids'])) with track_time_changes(auto_extend='end', user=session.user) as changes: for contribution in query: start_dt = find_next_start_dt( contribution.duration, obj=self.session_block or self.event, day=None if self.session_block else day, force=True) entry = self._schedule(contribution, start_dt) if entry.end_dt.astimezone(entry.event.tzinfo).date() > day: raise UserValueError( _("Contribution '{}' could not be scheduled since it doesn't fit on this day." ).format(contribution.title)) entries.append(entry) notifications = get_time_changes_notifications( changes, tzinfo=self.event.tzinfo) return jsonify_data(update=serialize_entry_update( entries[0], session_=self.session) if entries else None, notifications=notifications, flash=False)
def _process_DELETE(self): if EditingRevisionFile.query.with_parent(self.file_type).has_rows(): raise UserValueError(_('Cannot delete file type which already has files')) review_conditions = editing_settings.get(self.event, self.editable_type.name + '_review_conditions') if any(self.file_type.id in cond for cond in review_conditions.values()): raise UserValueError(_('Cannot delete file type which is used in a review condition')) if self.file_type.publishable: is_last = not (EditingFileType.query .with_parent(self.event) .filter(EditingFileType.publishable, EditingFileType.id != self.file_type.id) .has_rows()) if is_last: raise UserValueError(_('Cannot delete the only publishable file type')) delete_file_type(self.file_type) return '', 204
def _get_person_link(self, data, extra_data=None): extra_data = extra_data or {} person = self._get_event_person(data) person_data = { 'title': next((x.value for x in UserTitle if data.get('title') == x.title), UserTitle.none), 'first_name': data.get('firstName', ''), 'last_name': data['familyName'], 'affiliation': data.get('affiliation', ''), 'address': data.get('address', ''), 'phone': data.get('phone', '') } person_data.update(extra_data) person_link = None if self.object and inspect(person).persistent: person_link = self.person_link_cls.find_first(person=person, object=self.object) if not person_link: person_link = self.person_link_cls(person=person) person_link.populate_from_dict(person_data) email = data.get('email', '').lower() if email != person_link.email: if not self.event.persons.filter_by(email=email).first(): person_link.person.email = email else: raise UserValueError( _('There is already a person with the email {}').format( email)) return person_link
def _process_args(self): self.backend_name = request.view_args['backend'] try: self.backend = current_plugin.backend_classes[self.backend_name] except KeyError: raise NotFound if self.backend.unique and LiveSyncAgent.query.filter_by(backend_name=self.backend_name).has_rows(): raise UserValueError(_('This backend is already in use'))
def _process(self, id): last_rev = self.contrib.paper.get_last_revision() if last_rev: found = next((f for f in last_rev.files if f.id == id), None) if found: with found.open() as stream: return self._save_file(found, stream) raise UserValueError(_('No such file was found within the paper'))
def _process(self): language = request.form['lang'] if language not in get_all_locales(): raise UserValueError('Invalid language') session.lang = language if session.user: session.user.settings.set('lang', language) return '', 204
def _process(self): if self.editable: raise UserValueError(_('Editable already exists')) args = parser.parse({ 'files': EditingFilesField(self.event, self.contrib, required=True) }) create_new_editable(self.contrib, self.editable_type, session.user, args['files']) return '', 201
def _process_POST(self): f = request.files['photo'] try: photo = Image.open(f) except OSError: raise UserValueError( _('You cannot upload this file as a room picture.')) if photo.format.lower() not in {'jpeg', 'png', 'gif'}: raise UserValueError( _('The file has an invalid format ({format}).').format( format=photo.format)) if photo.mode != 'RGB': photo = photo.convert('RGB') image_bytes = BytesIO() photo.save(image_bytes, 'JPEG') image_bytes.seek(0) self.room.photo = Photo(data=image_bytes.read()) token = build_rooms_spritesheet() return jsonify(rooms_sprite_token=str(token))
def _process(self): if not self.registrations: raise UserValueError(_("The selected registrants have been removed.")) registration = self.registrations[0] email_body = replace_placeholders('registration-email', request.form['body'], regform=self.regform, registration=registration) tpl = get_template_module('events/registration/emails/custom_email.html', email_subject=request.form['subject'], email_body=email_body) html = render_template('events/registration/management/email_preview.html', subject=tpl.get_subject(), body=tpl.get_body()) return jsonify(html=html)
def import_registrations_from_csv(regform, fileobj, skip_moderation=True, notify_users=False): """Import event registrants from a CSV file into a form.""" with csv_text_io_wrapper(fileobj) as ftxt: reader = csv.reader(ftxt.read().splitlines()) reg_data = (db.session.query(Registration.user_id, Registration.email) .with_parent(regform) .filter(Registration.is_active) .all()) registered_user_ids = {rd.user_id for rd in reg_data if rd.user_id is not None} registered_emails = {rd.email for rd in reg_data} used_emails = set() email_row_map = {} todo = [] for row_num, row in enumerate(reader, 1): try: first_name, last_name, affiliation, position, phone, email = [value.strip() for value in row] email = email.lower() except ValueError: raise UserValueError(_('Row {}: malformed CSV data - please check that the number of columns is correct') .format(row_num)) if not email: raise UserValueError(_('Row {}: missing e-mail address').format(row_num)) if not validate_email(email): raise UserValueError(_('Row {}: invalid e-mail address').format(row_num)) if not first_name or not last_name: raise UserValueError(_('Row {}: missing first or last name').format(row_num)) if email in registered_emails: raise UserValueError(_('Row {}: a registration with this email already exists').format(row_num)) user = get_user_by_email(email) if user and user.id in registered_user_ids: raise UserValueError(_('Row {}: a registration for this user already exists').format(row_num)) if email in used_emails: raise UserValueError(_('Row {}: email address is not unique').format(row_num)) if conflict_row_num := email_row_map.get(email): raise UserValueError(_('Row {}: email address belongs to the same user as in row {}') .format(row_num, conflict_row_num)) used_emails.add(email) if user: email_row_map.update((e, row_num) for e in user.all_emails) todo.append({ 'email': email, 'first_name': first_name.title(), 'last_name': last_name.title(), 'affiliation': affiliation, 'phone': phone, 'position': position })
def _process_POST(self): data = dict({'background_position': 'stretch', 'items': []}, **request.json['template']) self.validate_json(TEMPLATE_DATA_JSON_SCHEMA, data) invalid_placeholders = {x['type'] for x in data['items']} - set(get_placeholder_options()) - {'fixed'} if invalid_placeholders: raise UserValueError('Invalid item types: {}'.format(', '.join(invalid_placeholders))) update_template(self.template, title=request.json['title'], data=data, backside_template_id=request.json['backside_template_id'], is_clonable=request.json['is_clonable'], clear_background=request.json['clear_background']) flash(_("Template successfully saved."), 'success') return jsonify_data()
def import_members_from_csv(self, f): with csv_text_io_wrapper(f) as ftxt: reader = csv.reader(ftxt.read().splitlines()) emails = set() for num_row, row in enumerate(reader, 1): if len(row) != 1: raise UserValueError(_('Row {}: malformed CSV data').format(num_row)) email = row[0].strip().lower() if email and not validate_email(email): raise UserValueError(_('Row {row}: invalid email address: {email}').format(row=num_row, email=email)) if email in emails: raise UserValueError(_('Row {}: email address is not unique').format(num_row)) emails.add(email) users = set(User.query.filter(~User.is_deleted, User.all_emails.in_(emails))) users_emails = {user.email for user in users} unknown_emails = emails - users_emails new_members = users - self.role.members return new_members, users, unknown_emails
def _process(self): new_start_dt = self.event.tzinfo.localize( dateutil.parser.parse(request.form.get('startDate'))).astimezone(utc) new_end_dt = self.event.tzinfo.localize(dateutil.parser.parse(request.form.get('endDate'))).astimezone(utc) new_duration = new_end_dt - new_start_dt is_session_block = self.entry.type == TimetableEntryType.SESSION_BLOCK tzinfo = self.event.tzinfo if is_session_block and new_end_dt.astimezone(tzinfo).date() != self.entry.start_dt.astimezone(tzinfo).date(): raise UserValueError(_('Session block cannot span more than one day')) with track_time_changes(auto_extend=True, user=session.user) as changes: update_timetable_entry_object(self.entry, {'duration': new_duration}) if is_session_block: self.entry.move(new_start_dt) if not is_session_block: update_timetable_entry(self.entry, {'start_dt': new_start_dt}) if is_session_block and self.entry.children: if new_end_dt < max(self.entry.children, key=attrgetter('end_dt')).end_dt: raise UserValueError(_('Session block cannot be shortened this much because contributions contained ' "wouldn't fit.")) notifications = get_time_changes_notifications(changes, tzinfo=self.event.tzinfo, entry=self.entry) return jsonify_data(update=serialize_entry_update(self.entry, session_=self.session), notifications=notifications)
def _process(self): access_request = self.registration.cern_access_request if not access_request or access_request.has_identity_info: raise UserValueError(_('The personal data for this registrant has already been entered')) form = AccessIdentityDataForm() if form.validate_on_submit(): form.populate_obj(access_request) db.session.flush() send_ticket(self.registration) return jsonify_data(html=render_plugin_template('cern_access_status.html', registration=self.registration, header=False)) return jsonify_template('identity_data_form_management.html', render_plugin_template, form=form, registration=self.registration)
def _process(self, source): self.user.picture_source = source if source == ProfilePictureSource.standard: self.user.picture = None self.user.picture_metadata = None logger.info('Profile picture of user %s removed by %s', self.user, session.user) return '', 204 if source == ProfilePictureSource.custom: f = request.files['picture'] try: pic = Image.open(f) except OSError: raise UserValueError( _('You cannot upload this file as profile picture.')) if pic.format.lower() not in {'jpeg', 'png', 'gif', 'webp'}: raise UserValueError( _('The file has an invalid format ({format}).').format( format=pic.format)) if pic.mode not in ('RGB', 'RGBA'): pic = pic.convert('RGB') pic = square(pic) if pic.height > 256: pic = pic.resize((256, 256), resample=Image.BICUBIC) image_bytes = BytesIO() pic.save(image_bytes, 'PNG') image_bytes.seek(0) set_user_avatar(self.user, image_bytes.read(), f.filename) else: content, lastmod = get_gravatar_for_user( self.user, source == ProfilePictureSource.identicon, 256) set_user_avatar(self.user, content, source.name, lastmod) logger.info('Profile picture of user %s updated by %s', self.user, session.user) return '', 204
def import_registrations_from_csv(regform, fileobj, skip_moderation=True, notify_users=False): """Import event registrants from a CSV file into a form.""" columns = [ 'first_name', 'last_name', 'affiliation', 'position', 'phone', 'email' ] user_records = import_user_records_from_csv(fileobj, columns=columns) reg_data = (db.session.query( Registration.user_id, Registration.email).with_parent(regform).filter( Registration.is_active).all()) registered_user_ids = { rd.user_id for rd in reg_data if rd.user_id is not None } registered_emails = {rd.email for rd in reg_data} for row_num, record in enumerate(user_records, 1): if record['email'] in registered_emails: raise UserValueError( _('Row {}: a registration with this email already exists'). format(row_num)) user = get_user_by_email(record['email']) if user and user.id in registered_user_ids: raise UserValueError( _('Row {}: a registration for this user already exists'). format(row_num)) return [ create_registration(regform, data, notify_user=notify_users, skip_moderation=skip_moderation) for data in user_records ]
def import_registrations_from_csv(regform, fileobj, skip_moderation=True, notify_users=False): """Import event registrants from a CSV file into a form.""" with csv_text_io_wrapper(fileobj) as ftxt: reader = csv.reader(ftxt.read().splitlines()) query = db.session.query(Registration.email).with_parent(regform).filter( Registration.is_active) registered_emails = {email for (email, ) in query} used_emails = set() todo = [] for row_num, row in enumerate(reader, 1): try: first_name, last_name, affiliation, position, phone, email = [ value.strip() for value in row ] email = email.lower() except ValueError: raise UserValueError( _('Row {}: malformed CSV data - please check that the number of columns is correct' ).format(row_num)) if not email: raise UserValueError( _('Row {}: missing e-mail address').format(row_num)) if not validate_email(email): raise UserValueError( _('Row {}: invalid e-mail address').format(row_num)) if not first_name or not last_name: raise UserValueError( _('Row {}: missing first or last name').format(row_num)) if email in registered_emails: raise UserValueError( _('Row {}: a registration with this email already exists'). format(row_num)) if email in used_emails: raise UserValueError( _('Row {}: email address is not unique').format(row_num)) used_emails.add(email) todo.append({ 'email': email, 'first_name': first_name.title(), 'last_name': last_name.title(), 'affiliation': affiliation, 'phone': phone, 'position': position }) return [ create_registration(regform, data, notify_user=notify_users, skip_moderation=skip_moderation) for data in todo ]
def adjust_payment_form_data(self, data): data['postfinance_methods'] = get_payment_methods(data['event'], data['currency']) data['selected_method'] = selected_method = request.args.get('postfinance_method', '') base_amount = data['amount'] if selected_method: method = get_payment_method(data['event'], data['currency'], selected_method) if method is None: raise UserValueError(_('Invalid currency')) modifier = Decimal(1 / (1 - method['fee'] / 100)) data['amount'] = base_amount * modifier data['fee'] = data['amount'] - base_amount data['form_data'] = self._generate_form_data(data['amount'], data) else: data['form_data'] = None data['fee'] = None if data['event_settings']['apply_fees']: # we don't know the final price data['amount'] = None
def move_timetable_entry(entry, parent=None, day=None): """Move the `entry` to another session or top-level timetable :param entry: `TimetableEntry` to be moved :param parent: If specified then the entry will be set as a child of parent :param day: If specified then the entry will be moved to the top-level timetable on this day """ if bool(parent) + bool(day) != 1: raise TypeError("Wrong number of arguments") from indico.modules.events.contributions.operations import update_contribution updates = {} contrib_update_data = {} if day: new_start_dt = entry.start_dt.replace(day=day.day, month=day.month) updates['start_dt'] = new_start_dt updates['parent'] = None contrib_update_data = {'session_id': None, 'session_block_id': None} elif parent: new_start_dt = find_latest_entry_end_dt( parent.object) or parent.start_dt tz = entry.event.tzinfo if (new_start_dt + entry.duration ).astimezone(tz).date() != parent.start_dt.astimezone(tz).date(): raise UserValueError( _('Session block cannot span more than one day')) updates['parent'] = parent updates['start_dt'] = new_start_dt contrib_update_data = { 'session': parent.session_block.session, 'session_block': parent.session_block } update_timetable_entry(entry, updates) if entry.type == TimetableEntryType.CONTRIBUTION: update_contribution(entry.object, contrib_update_data) if parent and entry.end_dt > parent.end_dt: duration = parent.object.duration + (entry.end_dt - parent.end_dt) update_session_block(parent.object, {'duration': duration})
def _generate_form_data(self, amount, data): if amount is None: return {} registration = data['registration'] personal_data = registration.get_personal_data() event = data['event'] currency = data['currency'] seed = data['settings']['hash_seed_{}'.format(currency.lower())] shop_id = data['settings']['shop_id_{}'.format(currency.lower())] method = get_payment_method(event, currency, data['selected_method']) if method is None: raise UserValueError(_('Invalid currency')) template_page = '' # yes, apparently it's supposed to be empty.. template_hash = sha512((seed + template_page).encode('utf-8')).hexdigest() order_id = self._get_order_id(data) locator = registration.locator.uuid address = re.sub(r'(\r?\n)+', ', ', personal_data.get('address', '')) form_data = { 'PSPID': shop_id, 'ORDERID': order_id, 'AMOUNT': int(amount * 100), 'CURRENCY': currency, 'LANGUAGE': session.lang, 'CN': unicode_to_ascii(remove_accents(registration.full_name[:35], False)), 'EMAIL': registration.email[:50], 'OWNERADDRESS': address[:35], 'OWNERTELNO': personal_data.get('phone', '')[:30], 'TP': template_page + '&hash=' + template_hash, 'PM': method['type'], 'BRAND': method['name'], 'PARAMVAR': data['settings']['server_url_suffix'], 'HOMEURL': url_for('event_registration.display_regform', locator, _external=True), 'ACCEPTURL': url_for_plugin('payment_cern.success', locator, _external=True), 'CANCELURL': url_for_plugin('payment_cern.cancel', locator, _external=True), 'DECLINEURL': url_for_plugin('payment_cern.decline', locator, _external=True), 'EXCEPTIONURL': url_for_plugin('payment_cern.uncertain', locator, _external=True), 'BACKURL': url_for('payment.event_payment', locator, _external=True) } form_data['SHASIGN'] = create_hash(seed, form_data) return form_data
def _process(self): new_identifier = session.user.identifier if new_identifier == self.vc_room.data['host'] or new_identifier in self.vc_room.data['alternative_hosts']: flash(_('You were already an (alternative) host of this meeting'), 'warning') return jsonify(success=False) try: self.plugin.refresh_room(self.vc_room, self.event) self.vc_room.data['alternative_hosts'].append(new_identifier) flag_modified(self.vc_room, 'data') self.plugin.update_room(self.vc_room, self.event) except VCRoomNotFoundError as exc: db.session.rollback() raise UserValueError(str(exc)) from exc except VCRoomError: db.session.rollback() raise else: flash(_("You are now an alternative host of room '{room}'").format(room=self.vc_room.name), 'success') return jsonify(success=True)
def import_user_records_from_csv(fileobj, columns): """Parse and do basic validation of user data from a CSV file. :param fileobj: the CSV file to be read :param columns: A list of column names, 'first_name', 'last_name', & 'email' are compulsory. :return: A list dictionaries each representing one row, the keys of which are given by the column names. """ with csv_text_io_wrapper(fileobj) as ftxt: reader = csv.reader(ftxt.read().splitlines()) used_emails = set() email_row_map = {} user_records = [] for row_num, row in enumerate(reader, 1): values = [value.strip() for value in row] if len(columns) != len(values): raise UserValueError( _('Row {}: malformed CSV data - please check that the number of columns is correct' ).format(row_num)) record = dict(zip(columns, values)) if not record['email']: raise UserValueError( _('Row {}: missing e-mail address').format(row_num)) record['email'] = record['email'].lower() if not validate_email(record['email']): raise UserValueError( _('Row {}: invalid e-mail address').format(row_num)) if not record['first_name'] or not record['last_name']: raise UserValueError( _('Row {}: missing first or last name').format(row_num)) record['first_name'] = record['first_name'].title() record['last_name'] = record['last_name'].title() if record['email'] in used_emails: raise UserValueError( _('Row {}: email address is not unique').format(row_num)) if conflict_row_num := email_row_map.get(record['email']): raise UserValueError( _('Row {}: email address belongs to the same user as in row {}' ).format(row_num, conflict_row_num)) used_emails.add(record['email']) if user := get_user_by_email(record['email']): email_row_map.update((e, row_num) for e in user.all_emails)
def _process(self): dialog = request.args.get('dialog') == '1' form = TestForm(prefix='dlg-' if dialog else '', csrf_enabled=False) data = {} if form.validate_on_submit(): _send_email(form.with_event.data) data = form.data elif form.is_submitted(): data = form.errors if dialog: if form.validate_on_submit(): if form.check.data: raise ValueError('You are full of fail (or ValueErrors)') if form.check2.data: raise UserValueError('You sent us failing garbage') if form.check3.data: raise Forbidden('You shall not pass') if form.check4.data: raise NotFound('Keep going, nothing to see here...') return jsonify_data(flash=False, html=json.dumps(data)) return jsonify_form(form) # return jsonify_form(form, form_header_kwargs={'action': '/robots.txt'}) else: return WPTest.render_template('test.html', form=form, data=data)
def _check_access(self): RHContributionEditableBase._check_access(self) if not self._check_revision_access(): raise UserValueError( _('You cannot perform this action on this revision'))
def import_contributions_from_csv(event, f): """Import timetable contributions from a CSV file into an event.""" with csv_text_io_wrapper(f) as ftxt: reader = csv.reader(ftxt.read().splitlines()) contrib_data = [] for num_row, row in enumerate(reader, 1): try: start_dt, duration, title, first_name, last_name, affiliation, email = ( value.strip() for value in row) email = email.lower() except ValueError: raise UserValueError( _('Row {}: malformed CSV data - please check that the number of columns is correct' ).format(num_row)) try: parsed_start_dt = event.tzinfo.localize( dateutil.parser.parse(start_dt)) if start_dt else None except ValueError: raise UserValueError( _('Row {row}: can\'t parse date: "{date}"').format( row=num_row, date=start_dt)) try: parsed_duration = timedelta( minutes=int(duration)) if duration else None except ValueError: raise UserValueError( _("Row {row}: can't parse duration: {duration}").format( row=num_row, duration=duration)) if not title: raise UserValueError( _('Row {}: contribution title is required').format(num_row)) if email and not validate_email(email): raise UserValueError( _('Row {row}: invalid email address: {email}').format( row=num_row, email=email)) contrib_data.append({ 'start_dt': parsed_start_dt, 'duration': parsed_duration or timedelta(minutes=20), 'title': title, 'speaker': { 'first_name': first_name, 'last_name': last_name, 'affiliation': affiliation, 'email': email } }) # now that we're sure the data is OK, let's pre-allocate the friendly ids # for the contributions in question Contribution.allocate_friendly_ids(event, len(contrib_data)) contributions = [] all_changes = defaultdict(list) for contrib_fields in contrib_data: speaker_data = contrib_fields.pop('speaker') with track_time_changes() as changes: contribution = create_contribution(event, contrib_fields, extend_parent=True) contributions.append(contribution) for key, val in changes[event].items(): all_changes[key].append(val) email = speaker_data['email'] if not email: continue # set the information of the speaker person = get_event_person(event, speaker_data) link = ContributionPersonLink(person=person, is_speaker=True) link.populate_from_dict({ 'first_name': speaker_data['first_name'], 'last_name': speaker_data['last_name'], 'affiliation': speaker_data['affiliation'] }) contribution.person_links.append(link) return contributions, all_changes
def track_time_changes(auto_extend=False, user=None): """Track time changes of event objects. This provides a list of changes while the context manager was active and also triggers `times_changed` signals. If the code running inside the ``with`` block of this context manager raises an exception, no signals will be triggered. :param auto_extend: Whether entry parents will get their boundaries automatically extended or not. Passing ``'start'`` will extend only start datetime, ``'end'`` to extend only end datetime. :param user: The `User` that will trigger time changes. """ if auto_extend: assert user is not None if 'old_times' in g: raise RuntimeError('time change tracking may not be nested') g.old_times = defaultdict(dict) changes = defaultdict(dict) try: yield changes except: del g.old_times raise else: if auto_extend: by_start = auto_extend in (True, 'start') by_end = auto_extend in (True, 'end') initial_changes = set(g.old_times) # g.old_times changes during iteration for obj in list(g.old_times): if not isinstance(obj, Event): obj.extend_parent(by_start=by_start, by_end=by_end) cascade_changes = set(g.old_times) - initial_changes for obj in cascade_changes: if isinstance(obj, Event): if not obj.can_manage(user): # TODO: raise Forbidden exceptions after adding protection check in the UI raise UserValueError( _("Your action requires modification of event boundaries, but you are " "not authorized to manage the event.")) elif not obj.object.can_manage(user): # TODO: raise Forbidden exceptions after adding protection check in the UI raise UserValueError( _("Your action requires modification of session block boundaries, but you are " "not authorized to manage the session block.")) old_times = g.pop('old_times') for obj, info in old_times.iteritems(): if isinstance(obj, TimetableEntry): obj = obj.object if obj.start_dt != info['start_dt']: changes[obj]['start_dt'] = (info['start_dt'], obj.start_dt) if obj.duration != info['duration']: changes[obj]['duration'] = (info['duration'], obj.duration) if obj.end_dt != info['end_dt']: changes[obj]['end_dt'] = (info['end_dt'], obj.end_dt) for obj, obj_changes in changes.iteritems(): entry = None if isinstance(obj, Event) else obj.timetable_entry signals.event.times_changed.send(type(obj), entry=entry, obj=obj, changes=obj_changes)