def _process_cascaded_category_contents(records): """ Travel from categories to subcontributions, flattening the whole event structure. Yields everything that it finds (except for elements whose protection has changed but are not inheriting their protection settings from anywhere). :param records: queue records to process """ category_prot_records = { rec.category_id for rec in records if rec.type == EntryType.category and rec.change == ChangeType.protection_changed } category_move_records = { rec.category_id for rec in records if rec.type == EntryType.category and rec.change == ChangeType.moved } changed_events = set() category_prot_records -= category_move_records # A move already implies sending the whole record # Protection changes are handled differently, as there may not be the need to re-generate the record if category_prot_records: for categ in Category.find(Category.id.in_(category_prot_records)): cte = categ.get_protection_parent_cte() # Update only children that inherit inheriting_categ_children = (Event.query.join( cte, db.and_((Event.category_id == cte.c.id), (cte.c.protection_parent == categ.id)))) inheriting_direct_children = Event.find( (Event.category_id == categ.id) & Event.is_inheriting) changed_events.update( itertools.chain(inheriting_direct_children, inheriting_categ_children)) # Add move operations and explicitly-passed event records if category_move_records: changed_events.update( Event.find(Event.category_chain_overlaps(category_move_records))) for elem in _process_cascaded_event_contents( records, additional_events=changed_events): yield elem
def _process_args(self): self.event = Event.find(id=request.view_args['event_id'], is_deleted=False).first_or_404() self._registration = (self.event.registrations .filter_by(id=request.view_args['registrant_id'], is_deleted=False) .options(joinedload('data').joinedload('field_data')) .first_or_404())
def migrate_event_series(self): self.print_step("Migrating event series") all_series = self.get_event_series() all_series_ids = set(chain.from_iterable(all_series)) events = { e.id: e for e in Event.find(Event.id.in_(all_series_ids)).options( load_only('id', 'series_id')) } for series in committing_iterator( verbose_iterator(all_series, len(all_series), lambda x: 0, lambda x: '')): series &= events.viewkeys() if len(series) < 2: self.print_warning('Skipping single-event series: {}'.format( sorted(series))) continue es = EventSeries(show_sequence_in_title=False) for id_ in series: events[id_].series = es if not self.quiet: self.print_success(repr(series)) AttachmentFolder.find( AttachmentFolder.title.op('~')('^part\d+$')).update( {AttachmentFolder.is_deleted: True}, synchronize_session=False) db.session.commit()
def _checkParams(self): self.event = Event.find(id=request.view_args['event_id'], is_deleted=False).first_or_404() self._registration = (self.event.registrations .filter_by(id=request.view_args['registrant_id'], is_deleted=False) .options(joinedload('data').joinedload('field_data')) .first_or_404())
def _category_moved(category, old_parent, new_parent, **kwargs): events = Event.find(Event.category_chain.contains([int(category.id)])).all() # update the category chain of all events from the moved category for event in events: event.category_chain = map(int, reversed(event.category.getCategoryPath())) # update the category chain of all events from the target category for event in g.get('detached_events_moved', set()): # the event was in the target category of of the category move assert event.category_id == int(new_parent.id) # and it is now in the category that has just been moved assert int(event.as_legacy.getOwner().id) == int(category.id) # this will also update the chain (sqlalchemy hook) event.category_id = int(category.id)
def _category_moved(category, old_parent, new_parent, **kwargs): events = Event.find(Event.category_chain.contains([int(category.id) ])).all() # update the category chain of all events from the moved category for event in events: event.category_chain = map(int, reversed(event.category.getCategoryPath())) # update the category chain of all events from the target category for event in g.get('detached_events_moved', set()): # the event was in the target category of of the category move assert event.category_id == int(new_parent.id) # and it is now in the category that has just been moved assert int(event.as_legacy.getOwner().id) == int(category.id) # this will also update the chain (sqlalchemy hook) event.category_id = int(category.id)
def _process_cascaded_category_contents(records): """ Travel from categories to subcontributions, flattening the whole event structure. Yields everything that it finds (except for elements whose protection has changed but are not inheriting their protection settings from anywhere). :param records: queue records to process """ category_prot_records = {rec.category_id for rec in records if rec.type == EntryType.category and rec.change == ChangeType.protection_changed} category_move_records = {rec.category_id for rec in records if rec.type == EntryType.category and rec.change == ChangeType.moved} changed_events = set() category_prot_records -= category_move_records # A move already implies sending the whole record # Protection changes are handled differently, as there may not be the need to re-generate the record if category_prot_records: for categ in Category.find(Category.id.in_(category_prot_records)): cte = categ.get_protection_parent_cte() # Update only children that inherit inheriting_categ_children = (Event.query .join(cte, db.and_((Event.category_id == cte.c.id), (cte.c.protection_parent == categ.id)))) inheriting_direct_children = Event.find((Event.category_id == categ.id) & Event.is_inheriting) changed_events.update(itertools.chain(inheriting_direct_children, inheriting_categ_children)) # Add move operations and explicitly-passed event records if category_move_records: changed_events.update(Event.find(Event.category_chain_overlaps(category_move_records))) for elem in _process_cascaded_event_contents(records, additional_events=changed_events): yield elem
def _process_cascaded_event_contents(records, additional_events=None): """ Flatten a series of records into its most basic elements (subcontribution level). Yields results. :param records: queue records to process :param additional_events: events whose content will be included in addition to those found in records """ changed_events = additional_events or set() changed_contributions = set() changed_subcontributions = set() session_records = {rec.session_id for rec in records if rec.type == EntryType.session} contribution_records = {rec.contrib_id for rec in records if rec.type == EntryType.contribution} subcontribution_records = {rec.subcontrib_id for rec in records if rec.type == EntryType.subcontribution} event_records = {rec.event_id for rec in records if rec.type == EntryType.event} if event_records: changed_events.update(Event.find(Event.id.in_(event_records))) for event in changed_events: yield event # Sessions are added (explicitly changed only, since they don't need to be sent anywhere) if session_records: changed_contributions.update(Contribution .find(Contribution.session_id.in_(session_records), ~Contribution.is_deleted)) # Contributions are added (implictly + explicitly changed) changed_event_ids = {ev.id for ev in changed_events} condition = Contribution.event_id.in_(changed_event_ids) & ~Contribution.is_deleted if contribution_records: condition = db.or_(condition, Contribution.id.in_(contribution_records)) contrib_query = Contribution.find(condition).options(joinedload('subcontributions')) for contribution in contrib_query: yield contribution changed_subcontributions.update(contribution.subcontributions) # Same for subcontributions if subcontribution_records: changed_subcontributions.update(SubContribution.find(SubContribution.id.in_(subcontribution_records))) for subcontrib in changed_subcontributions: yield subcontrib
def initial_export(agent_id, force): """Performs the initial data export for an agent""" agent = LiveSyncAgent.find_first(id=agent_id) if agent is None: print 'No such agent' return if agent.backend is None: print cformat('Cannot run agent %{red!}{}%{reset} (backend not found)').format(agent.name) return print cformat('Selected agent: %{white!}{}%{reset} ({})').format(agent.name, agent.backend.title) if agent.initial_data_exported and not force: print 'The initial export has already been performed for this agent.' print cformat('To re-run it, use %{yellow!}--force%{reset}') return agent.create_backend().run_initial_export(Event.find(is_deleted=False)) agent.initial_data_exported = True db.session.commit()
def migrate_event_series(self): self.print_step("Migrating event series") all_series = self.get_event_series() all_series_ids = set(chain.from_iterable(all_series)) events = {e.id: e for e in Event.find(Event.id.in_(all_series_ids)).options(load_only('id', 'series_id'))} for series in committing_iterator(verbose_iterator(all_series, len(all_series), lambda x: 0, lambda x: '')): series &= events.viewkeys() if len(series) < 2: self.print_warning('Skipping single-event series: {}'.format(sorted(series))) continue es = EventSeries(show_sequence_in_title=False) for id_ in series: events[id_].series = es if not self.quiet: self.print_success(repr(series)) AttachmentFolder.find(AttachmentFolder.title.op('~')('^part\d+$')).update({AttachmentFolder.is_deleted: True}, synchronize_session=False) db.session.commit()
def initial_export(agent_id, force): """Performs the initial data export for an agent""" agent = LiveSyncAgent.find_first(id=agent_id) if agent is None: print 'No such agent' return if agent.backend is None: print cformat( 'Cannot run agent %{red!}{}%{reset} (backend not found)').format( agent.name) return print cformat('Selected agent: %{white!}{}%{reset} ({})').format( agent.name, agent.backend.title) if agent.initial_data_exported and not force: print 'The initial export has already been performed for this agent.' print cformat('To re-run it, use %{yellow!}--force%{reset}') return agent.create_backend().run_initial_export(Event.find(is_deleted=False)) agent.initial_data_exported = True db.session.commit()
def as_event(self): """Returns the :class:`.Event` for this object :rtype: indico.modules.events.models.events.Event """ from indico.modules.events.models.events import Event event_id = int(self.id) # this is pretty ugly, but the api sends queries in a loop in # some cases and we can't really avoid this for now. If we # already have the event in the identity map we keep using # the simple id-based lookup though as lazyloading the acl # entries is just one query anyway if (has_request_context() and request.blueprint == 'api' and request.endpoint != 'api.jsonrpc' and (Event, (event_id, )) not in db.session.identity_map): acl_user_strategy = joinedload('acl_entries').defaultload('user') # remote group membership checks will trigger a load on _all_emails # but not all events use this so there's no need to eager-load them acl_user_strategy.noload('_primary_email') acl_user_strategy.noload('_affiliation') return Event.find(id=event_id).options(acl_user_strategy).one() else: # use get() so sqlalchemy can make use of the identity cache return Event.get_one(event_id)
def get_category_timetable(categ_ids, start_dt, end_dt, detail_level='event', tz=utc, from_categ=None, grouped=True): """Retrieve time blocks that fall within a specific time interval for a given set of categories. :param categ_ids: iterable containing list of category IDs :param start_dt: start of search interval (``datetime``, expected to be in display timezone) :param end_dt: end of search interval (``datetime`` in expected to be in display timezone) :param detail_level: the level of detail of information (``event|session|contribution``) :param tz: the ``timezone`` information should be displayed in :param from_categ: ``Category`` that will be taken into account to calculate visibility :param grouped: Whether to group results by start date :returns: a dictionary containing timetable information in a structured way. See source code for examples. """ day_start = start_dt.astimezone(utc) day_end = end_dt.astimezone(utc) dates_overlap = lambda t: (t.start_dt >= day_start) & (t.start_dt <= day_end) items = defaultdict(lambda: defaultdict(list)) # first of all, query TimetableEntries/events that fall within # specified range of dates (and category set) events = _query_events(categ_ids, day_start, day_end) if from_categ: events = events.filter(Event.is_visible_in(from_categ)) for eid, tt_start_dt in events: if tt_start_dt: items[eid][tt_start_dt.astimezone(tz).date()].append(tt_start_dt) else: items[eid] = None # then, retrieve detailed information about the events event_ids = set(items) query = (Event.find(Event.id.in_(event_ids)) .options(subqueryload(Event.person_links).joinedload(EventPersonLink.person), joinedload(Event.own_room).noload('owner'), joinedload(Event.own_venue), joinedload(Event.category).undefer('effective_icon_data'), undefer('effective_protection_mode'))) scheduled_events = defaultdict(list) ongoing_events = [] events = [] for e in query: if grouped: local_start_dt = e.start_dt.astimezone(tz).date() local_end_dt = e.end_dt.astimezone(tz).date() if items[e.id] is None: # if there is no TimetableEntry, this means the event has not timetable on that interval for day in iterdays(max(start_dt.date(), local_start_dt), min(end_dt.date(), local_end_dt)): # if the event starts on this date, we've got a time slot if day.date() == local_start_dt: scheduled_events[day.date()].append((e.start_dt, e)) else: ongoing_events.append(e) else: for start_d, start_dts in items[e.id].viewitems(): scheduled_events[start_d].append((start_dts[0], e)) else: events.append(e) # result['events'][date(...)] -> [(datetime(....), Event(...))] # result[event_id]['contribs'][date(...)] -> [(TimetableEntry(...), Contribution(...))] # result['ongoing_events'] = [Event(...)] if grouped: result = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) else: result = defaultdict(lambda: defaultdict(list)) result.update({ 'events': scheduled_events if grouped else events, 'ongoing_events': ongoing_events }) # according to detail level, ask for extra information from the DB if detail_level != 'event': query = _query_blocks(event_ids, dates_overlap, detail_level) if grouped: for b in query: start_date = b.timetable_entry.start_dt.astimezone(tz).date() result[b.session.event_id]['blocks'][start_date].append((b.timetable_entry, b)) else: for b in query: result[b.session.event_id]['blocks'].append(b) if detail_level == 'contribution': query = (Contribution.find(Contribution.event_id.in_(event_ids), dates_overlap(TimetableEntry), ~Contribution.is_deleted) .options(contains_eager(Contribution.timetable_entry), joinedload(Contribution.person_links)) .join(TimetableEntry)) if grouped: for c in query: start_date = c.timetable_entry.start_dt.astimezone(tz).date() result[c.event_id]['contribs'][start_date].append((c.timetable_entry, c)) else: for c in query: result[c.event_id]['contributions'].append(c) query = (Break.find(TimetableEntry.event_id.in_(event_ids), dates_overlap(TimetableEntry)) .options(contains_eager(Break.timetable_entry)) .join(TimetableEntry)) if grouped: for b in query: start_date = b.timetable_entry.start_dt.astimezone(tz).date() result[b.timetable_entry.event_id]['breaks'][start_date].append((b.timetable_entry, b)) else: for b in query: result[b.timetable_entry.event_id]['breaks'].append(b) return result
def _checkParams(self): self.event = Event.find(id=request.view_args['event_id'], is_deleted=False).first_or_404()
def _process_args(self): self.event = Event.find(id=request.view_args['event_id'], is_deleted=False).first_or_404()
def _events_query(self): return Event.find( Event.attachment_folders.any( AttachmentFolder.title.op('~')('^part\d+$')))
def get_category_timetable(categ_ids, start_dt, end_dt, detail_level='event', tz=utc, from_categ=None, grouped=True): """Retrieve time blocks that fall within a specific time interval for a given set of categories. :param categ_ids: iterable containing list of category IDs :param start_dt: start of search interval (``datetime``, expected to be in display timezone) :param end_dt: end of search interval (``datetime`` in expected to be in display timezone) :param detail_level: the level of detail of information (``event|session|contribution``) :param tz: the ``timezone`` information should be displayed in :param from_categ: ``Category`` that will be taken into account to calculate visibility :param grouped: Whether to group results by start date :returns: a dictionary containing timetable information in a structured way. See source code for examples. """ day_start = start_dt.astimezone(utc) day_end = end_dt.astimezone(utc) dates_overlap = lambda t: (t.start_dt >= day_start) & (t.start_dt <= day_end) items = defaultdict(lambda: defaultdict(list)) # first of all, query TimetableEntries/events that fall within # specified range of dates (and category set) events = _query_events(categ_ids, day_start, day_end) if from_categ: events = events.filter(Event.is_visible_in(from_categ)) for eid, tt_start_dt in events: if tt_start_dt: items[eid][tt_start_dt.astimezone(tz).date()].append(tt_start_dt) else: items[eid] = None # then, retrieve detailed information about the events event_ids = set(items) query = (Event.find(Event.id.in_(event_ids)) .options(subqueryload(Event.person_links).joinedload(EventPersonLink.person), joinedload(Event.own_room).noload('owner'), joinedload(Event.own_venue), joinedload(Event.category).undefer('effective_icon_data'), undefer('effective_protection_mode'))) scheduled_events = defaultdict(list) ongoing_events = [] events = [] for e in query: if grouped: local_start_dt = e.start_dt.astimezone(tz).date() local_end_dt = e.end_dt.astimezone(tz).date() if items[e.id] is None: # if there is no TimetableEntry, this means the event has not timetable on that interval for day in iterdays(max(start_dt.date(), local_start_dt), min(end_dt.date(), local_end_dt)): # if the event starts on this date, we've got a time slot if day.date() == local_start_dt: scheduled_events[day.date()].append((e.start_dt, e)) else: ongoing_events.append(e) else: for start_d, start_dts in items[e.id].viewitems(): scheduled_events[start_d].append((start_dts[0], e)) else: events.append(e) # result['events'][date(...)] -> [(datetime(....), Event(...))] # result[event_id]['contribs'][date(...)] -> [(TimetableEntry(...), Contribution(...))] # result['ongoing_events'] = [Event(...)] if grouped: result = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) else: result = defaultdict(lambda: defaultdict(list)) result.update({ 'events': scheduled_events if grouped else events, 'ongoing_events': ongoing_events }) # according to detail level, ask for extra information from the DB if detail_level != 'event': query = _query_blocks(event_ids, dates_overlap, detail_level) if grouped: for b in query: start_date = b.timetable_entry.start_dt.astimezone(tz).date() result[b.session.event_id]['blocks'][start_date].append((b.timetable_entry, b)) else: for b in query: result[b.session.event_id]['blocks'].append(b) if detail_level == 'contribution': query = (Contribution.find(Contribution.event_id.in_(event_ids), dates_overlap(TimetableEntry), ~Contribution.is_deleted) .options(contains_eager(Contribution.timetable_entry), joinedload(Contribution.person_links)) .join(TimetableEntry)) if grouped: for c in query: start_date = c.timetable_entry.start_dt.astimezone(tz).date() result[c.event_id]['contribs'][start_date].append((c.timetable_entry, c)) else: for c in query: result[c.event_id]['contributions'].append(c) query = (Break.find(TimetableEntry.event_id.in_(event_ids), dates_overlap(TimetableEntry)) .options(contains_eager(Break.timetable_entry)) .join(TimetableEntry)) if grouped: for b in query: start_date = b.timetable_entry.start_dt.astimezone(tz).date() result[b.timetable_entry.event_id]['breaks'][start_date].append((b.timetable_entry, b)) else: for b in query: result[b.timetable_entry.event_id]['breaks'].append(b) return result
def _checkParams(self): self.event = Event.find(id=request.view_args['event_id'], is_deleted=False).first_or_404()
def _process_args(self): self.event = Event.find(id=request.view_args['event_id'], is_deleted=False).first_or_404()
def _process_cascaded_event_contents(records, additional_events=None): """ Flatten a series of records into its most basic elements (subcontribution level). Yields results. :param records: queue records to process :param additional_events: events whose content will be included in addition to those found in records """ changed_events = additional_events or set() changed_contributions = set() changed_subcontributions = set() session_records = { rec.session_id for rec in records if rec.type == EntryType.session } contribution_records = { rec.contrib_id for rec in records if rec.type == EntryType.contribution } subcontribution_records = { rec.subcontrib_id for rec in records if rec.type == EntryType.subcontribution } event_records = { rec.event_id for rec in records if rec.type == EntryType.event } if event_records: changed_events.update(Event.find(Event.id.in_(event_records))) for event in changed_events: yield event # Sessions are added (explicitly changed only, since they don't need to be sent anywhere) if session_records: changed_contributions.update( Contribution.find(Contribution.session_id.in_(session_records), ~Contribution.is_deleted)) # Contributions are added (implictly + explicitly changed) changed_event_ids = {ev.id for ev in changed_events} condition = Contribution.event_id.in_( changed_event_ids) & ~Contribution.is_deleted if contribution_records: condition = db.or_(condition, Contribution.id.in_(contribution_records)) contrib_query = Contribution.find(condition).options( joinedload('subcontributions')) for contribution in contrib_query: yield contribution changed_subcontributions.update(contribution.subcontributions) # Same for subcontributions if subcontribution_records: changed_subcontributions.update( SubContribution.find( SubContribution.id.in_(subcontribution_records))) for subcontrib in changed_subcontributions: yield subcontrib
def _events_query(self): return Event.find(Event.attachment_folders.any(AttachmentFolder.title.op('~')('^part\d+$')))