def _process(self): if self.regform.has_ended: self.regform.end_dt = None else: self.regform.start_dt = now_utc() logger.info("Registrations for %s opened by %s", self.regform, session.user) flash( _("Registrations for {} are now open").format(self.regform.title), 'success') return redirect(url_for('.manage_regform', self.regform))
def _render_now_happening_info(event, text_color_css, **kwargs): from fossir.modules.events.layout import layout_settings if layout_settings.get(event, 'show_banner'): current_dt = now_utc(exact=False) entries = event.timetable_entries.filter(TimetableEntry.start_dt <= current_dt, TimetableEntry.end_dt > current_dt, TimetableEntry.type != TimetableEntryType.SESSION_BLOCK).all() if not entries: return return render_template('events/display/now_happening.html', event=event, entries=entries, text_color_css=text_color_css)
def get_not_deletable_templates(obj): """Get all non-deletable templates for an event/category""" not_deletable_criteria = [ DesignerTemplate.is_system_template, DesignerTemplate.backside_template_of != None, # noqa DesignerTemplate.ticket_for_regforms.any( RegistrationForm.event.has(Event.ends_after(now_utc()))) ] return set( DesignerTemplate.query.filter(DesignerTemplate.owner == obj, db.or_(*not_deletable_criteria)))
def scheduled_dt(self): if self.schedule_type.data == 'absolute': if self.absolute_date.data is None or self.absolute_time.data is None: return None dt = datetime.combine(self.absolute_date.data, self.absolute_time.data) return self.event.tzinfo.localize(dt).astimezone(pytz.utc) elif self.schedule_type.data == 'relative': if self.relative_delta.data is None: return None return self.event.start_dt - self.relative_delta.data elif self.schedule_type.data == 'now': return now_utc()
def _send_emails(self, form, recipients): for recipient in recipients: if self.no_account and isinstance(recipient, EventPerson): recipient.invited_dt = now_utc() email_body = replace_placeholders('event-persons-email', form.body.data, person=recipient, event=self.event, register_link=self.no_account) email_subject = replace_placeholders('event-persons-email', form.subject.data, person=recipient, event=self.event, register_link=self.no_account) tpl = get_template_module('emails/custom.html', subject=email_subject, body=email_body) bcc = [session.user.email] if form.copy_for_sender.data else [] email = make_email(to_list=recipient.email, bcc_list=bcc, from_address=form.from_address.data, template=tpl, html=True) send_email(email, self.event, 'Event Persons')
def _process(self): self.user.settings.set('suggest_categories', True) tz = session.tzinfo hours, minutes = timedelta_split(tz.utcoffset(datetime.now()))[:2] categories = get_related_categories(self.user) categories_events = [] if categories: category_ids = {c['categ'].id for c in categories.itervalues()} today = now_utc(False).astimezone(tz).date() query = (Event.query.filter( ~Event.is_deleted, Event.category_chain_overlaps(category_ids), Event.start_dt.astimezone(session.tzinfo) >= today).options( joinedload('category').load_only('id', 'title'), joinedload('series'), subqueryload('acl_entries'), load_only('id', 'category_id', 'start_dt', 'end_dt', 'title', 'access_key', 'protection_mode', 'series_id', 'series_pos', 'series_count')).order_by( Event.start_dt, Event.id)) categories_events = get_n_matching( query, 10, lambda x: x.can_access(self.user)) from_dt = now_utc(False) - relativedelta( weeks=1, hour=0, minute=0, second=0) linked_events = [(event, { 'management': bool(roles & self.management_roles), 'reviewing': bool(roles & self.reviewer_roles), 'attendance': bool(roles & self.attendance_roles) }) for event, roles in get_linked_events( self.user, from_dt, 10).iteritems()] return WPUser.render_template( 'dashboard.html', 'dashboard', offset='{:+03d}:{:02d}'.format(hours, minutes), user=self.user, categories=categories, categories_events=categories_events, suggested_categories=get_suggested_categories(self.user), linked_events=linked_events)
def test_email_content(monkeypatch, abstract_objects, create_email_template, dummy_user): def _mock_send_email(email, event, module, user): assert event == ev assert module == 'Abstracts' assert email[ 'subject'] == '[fossir] Abstract Acceptance notification (#314)' assert_text_equal( email['body'], """ Dear Guinea Pig, We're pleased to announce that your abstract "Broken Symmetry and the Mass of Gauge Vector Mesons" with ID #314 has been accepted in track "Dummy Track" (Poster). See below a summary of your submitted abstract: Conference: {event.title} Submitted by: Guinea Pig Title: Broken Symmetry and the Mass of Gauge Vector Mesons Primary Authors: John Doe, John Smith, Pocahontas Silva Co-authors: Track classification: Dummy Track Presentation type: Poster For a more detailed summary please visit the page of your abstract: http://localhost/event/-314/abstracts/1234/. Kind regards, The organizers of {event.title} -- fossir :: Call for Abstracts http://localhost/event/{event.id}/ """.format(event=ev)) ev, abstract, track, contrib_type = abstract_objects monkeypatch.setattr( 'fossir.modules.events.abstracts.notifications.send_email', _mock_send_email) ev.abstract_email_templates.append( create_email_template(ev, 0, 'accept', 'accept', [{ 'state': [AbstractState.accepted.value] }], True)) abstract.accepted_contrib_type = contrib_type abstract.accepted_track = track abstract.state = AbstractState.accepted abstract.judge = dummy_user abstract.judgment_dt = now_utc(False) send_abstract_notifications(abstract)
def judge_abstract(abstract, abstract_data, judgment, judge, contrib_session=None, merge_persons=False, send_notifications=False): abstract.judge = judge abstract.judgment_dt = now_utc() abstract.judgment_comment = abstract_data['judgment_comment'] log_data = {'Judgment': orig_string(judgment.title)} if judgment == AbstractAction.accept: abstract.state = AbstractState.accepted abstract.accepted_track = abstract_data.get('accepted_track') if abstract_data.get('override_contrib_type') or abstract_data.get( 'accepted_contrib_type'): abstract.accepted_contrib_type = abstract_data.get( 'accepted_contrib_type') else: abstract.accepted_contrib_type = abstract.submitted_contrib_type if not abstract.contribution: abstract.contribution = create_contribution_from_abstract( abstract, contrib_session) if abstract.accepted_track: log_data['Track'] = abstract.accepted_track.title if abstract.accepted_contrib_type: log_data['Type'] = abstract.accepted_contrib_type.name elif judgment == AbstractAction.reject: abstract.state = AbstractState.rejected elif judgment == AbstractAction.mark_as_duplicate: abstract.state = AbstractState.duplicate abstract.duplicate_of = abstract_data['duplicate_of'] log_data['Duplicate of'] = abstract.duplicate_of.verbose_title elif judgment == AbstractAction.merge: abstract.state = AbstractState.merged abstract.merged_into = abstract_data['merged_into'] log_data['Merged into'] = abstract.merged_into.verbose_title log_data['Merge authors'] = merge_persons if merge_persons: _merge_person_links(abstract.merged_into, abstract) db.session.flush() if send_notifications: log_data['Notifications sent'] = send_abstract_notifications(abstract) logger.info('Abstract %s judged by %s', abstract, judge) abstract.event.log(EventLogRealm.management, EventLogKind.change, 'Abstracts', 'Abstract {} judged'.format(abstract.verbose_title), judge, data=log_data)
def _create_event(id_=None, **kwargs): # we specify `acl_entries` so SA doesn't load it when accessing it for # the first time, which would require no_autoflush blocks in some cases now = now_utc(exact=False) kwargs.setdefault('type_', EventType.meeting) kwargs.setdefault( 'title', u'dummy#{}'.format(id_) if id_ is not None else u'dummy') kwargs.setdefault('start_dt', now) kwargs.setdefault('end_dt', now + timedelta(hours=1)) kwargs.setdefault('timezone', 'UTC') kwargs.setdefault('category', dummy_category) event = Event(id=id_, creator=dummy_user, acl_entries=set(), **kwargs) db.session.flush() return event
def update_abstract_review(review, review_data, questions_data): event = review.abstract.event changes = review.populate_from_dict(review_data) review.modified_dt = now_utc() log_fields = {} for question in event.abstract_review_questions: field_name = 'question_{}'.format(question.id) rating = question.get_review_rating(review, allow_create=True) old_value = rating.value rating.value = int(questions_data[field_name]) if old_value != rating.value: changes[field_name] = (old_value, rating.value) log_fields[field_name] = {'title': question.text, 'type': 'number'} db.session.flush() logger.info("Abstract review %s modified", review) log_fields.update({'proposed_action': 'Action', 'comment': 'Comment'}) if review.proposed_action in { AbstractAction.mark_as_duplicate, AbstractAction.merge }: log_fields['proposed_related_abstract'] = { 'title': 'Other abstract', 'type': 'string', 'convert': lambda change: [x.verbose_title if x else None for x in change] } elif review.proposed_action == AbstractAction.accept: log_fields['proposed_contribution_type'] = { 'title': 'Contribution type', 'type': 'string', 'convert': lambda change: [x.name if x else None for x in change] } elif review.proposed_action == AbstractAction.change_tracks: log_fields['proposed_tracks'] = { 'title': 'Other tracks', 'convert': lambda change: [sorted(t.title for t in x) for x in change] } event.log(EventLogRealm.management, EventLogKind.change, 'Abstracts', 'Review for abstract {} modified'.format( review.abstract.verbose_title), session.user, data={ 'Track': review.track.title, 'Changes': make_diff_log(changes, log_fields) })
def serialize(self): metadata = { 'timestamp': now_utc(), 'fossir_version': fossir.__version__, 'objects': list( self._serialize_objects(Event.__table__, Event.id == self.event.id)), 'users': self.users } yaml_data = yaml.dump(metadata, indent=2) self._add_file('data.yaml', len(yaml_data), yaml_data)
def create(cls, event, type_, data): """Create a new static list link. If one exists with the same data, that link is used instead of creating a new one. :param event: the `Event` for which to create the link :param type_: the type of the link :param data: the data to associate with the link :return: the newly created `StaticListLink` """ static_list_link = event.static_list_links.filter_by( type=type_, data=data).first() if static_list_link is None: static_list_link = cls(event=event, type=type_, data=data) else: # bump timestamp in case we start expiring old links # in the future if static_list_link.last_used_dt is not None: static_list_link.last_used_dt = now_utc() else: static_list_link.created_dt = now_utc() db.session.flush() return static_list_link
def reject(cls, req, data, user): """Rejects the request To ensure that additional data is saved, this method should call :method:`manager_save`. :param req: the :class:`Request` of the request :param data: the form data from the management form :param user: the user processing the request """ from fossir.modules.events.requests.models.requests import RequestState cls.manager_save(req, data) req.state = RequestState.rejected req.processed_by_user = user req.processed_dt = now_utc() notify_rejected_request(req)
def test_notification_any_conditions(mocker, abstract_objects, create_email_template, dummy_user): event, abstract, track, contrib_type = abstract_objects event.abstract_email_templates = [ create_email_template(event, 0, 'accept', 'accepted', [{ 'state': [AbstractState.accepted.value] }], True) ] send_email = mocker.patch( 'fossir.modules.events.abstracts.notifications.send_email') abstract.state = AbstractState.accepted abstract.judge = dummy_user abstract.judgment_dt = now_utc(False) abstract.accepted_track = track send_abstract_notifications(abstract) assert send_email.call_count == 1
def judge_paper(paper, judgment, comment, judge): if judgment == PaperAction.accept: paper.state = PaperRevisionState.accepted elif judgment == PaperAction.reject: paper.state = PaperRevisionState.rejected elif judgment == PaperAction.to_be_corrected: paper.state = PaperRevisionState.to_be_corrected paper.last_revision.judgment_comment = comment paper.last_revision.judge = judge paper.last_revision.judgment_dt = now_utc() db.session.flush() log_data = {'New state': orig_string(judgment.title)} notify_paper_judgment(paper) logger.info('Paper %r was judged by %r to %s', paper, judge, orig_string(judgment.title)) paper.event.log(EventLogRealm.management, EventLogKind.change, 'Papers', 'Paper "{}" was judged'.format(orig_string(paper.verbose_title)), judge, data=log_data)
def load(cls, event, type_, uuid): """Load the data associated with a link :param event: the `Event` the link belongs to :param type_: the type of the link :param uuid: the UUID of the link :return: the link data or ``None`` if the link does not exist """ try: UUID(uuid) except ValueError: return None static_list_link = event.static_list_links.filter_by( type=type_, uuid=uuid).first() if static_list_link is None: return None static_list_link.last_used_dt = now_utc() return static_list_link.data
def _get_form_defaults(self): category = self._default_category tzinfo = timezone(config.DEFAULT_TIMEZONE) if category is not None: tzinfo = timezone(category.timezone) # try to find good dates/times now = now_utc(exact=False) start_dt = now + relativedelta(hours=1, minute=0) if start_dt.astimezone(tzinfo).time() > time(18): start_dt = tzinfo.localize(datetime.combine(now.date() + relativedelta(days=1), time(9))) end_dt = start_dt + relativedelta(hours=2) # XXX: Do not provide a default value for protection_mode. It is selected via JavaScript code # once a category has been selected. return FormDefaults(category=category, timezone=tzinfo.zone, start_dt=start_dt, end_dt=end_dt, occurrences=[(start_dt, end_dt - start_dt)], location_data={'inheriting': False})
def update_abstract_comment(comment, comment_data): changes = comment.populate_from_dict(comment_data) comment.modified_by = session.user comment.modified_dt = now_utc() db.session.flush() logger.info("Abstract comment %s modified by %s", comment, session.user) comment.abstract.event.log(EventLogRealm.management, EventLogKind.change, 'Abstracts', 'Comment on abstract {} modified'.format( comment.abstract.verbose_title), session.user, data={ 'Changes': make_diff_log(changes, { 'text': 'Text', 'visibility': 'Visibility' }) })
def run(self, new_event, cloners, shared_data): attrs = get_simple_column_attrs( db.m.EventReminder) - {'created_dt', 'scheduled_dt', 'is_sent'} attrs |= {'creator_id'} for old_reminder in self._find_reminders(): scheduled_dt = new_event.start_dt - old_reminder.event_start_delta # Skip anything that's would have been sent on a past date. # We ignore the time on purpose so cloning an event shortly before will # still trigger a reminder that's just a few hours overdue. if scheduled_dt.date() < now_utc().date(): logger.info( 'Not cloning reminder %s which would trigger at %s', old_reminder, scheduled_dt) continue reminder = db.m.EventReminder( **{attr: getattr(old_reminder, attr) for attr in attrs}) reminder.scheduled_dt = scheduled_dt new_event.reminders.append(reminder) db.session.flush() logger.info('Added reminder during event cloning: %s', reminder)
def _parseDateTime(cls, dateTime, allowNegativeOffset): """ Accepted formats: * ISO 8601 subset - YYYY-MM-DD[THH:MM] * 'today', 'yesterday', 'tomorrow' and 'now' * days in the future/past: '[+/-]DdHHhMMm' 'ctx' means that the date will change according to its function ('from' or 'to') """ # if it's a an "alias", return immediately now = now_utc() if dateTime in cls._deltas: return ('ctx', now + cls._deltas[dateTime]) elif dateTime == 'now': return ('abs', now) elif dateTime == 'today': return ('ctx', now) m = re.match(r'^([+-])?(?:(\d{1,3})d)?(?:(\d{1,2})h)?(?:(\d{1,2})m)?$', dateTime) if m: mod = -1 if m.group(1) == '-' else 1 if not allowNegativeOffset and mod == -1: raise ArgumentParseError('End date cannot be a negative offset') atoms = list(0 if a is None else int(a) * mod for a in m.groups()[1:]) if atoms[1] > 23 or atoms[2] > 59: raise ArgumentParseError("Invalid time!") return ('ctx', timedelta(days=atoms[0], hours=atoms[1], minutes=atoms[2])) else: # iso 8601 subset try: return ('abs', datetime.strptime(dateTime, "%Y-%m-%dT%H:%M")) except ValueError: pass try: return ('ctx', datetime.strptime(dateTime, "%Y-%m-%d")) except ValueError: raise ArgumentParseError("Impossible to parse '%s'" % dateTime)
def test_notification_several_conditions(db, mocker, abstract_objects, create_email_template, create_dummy_track, create_dummy_contrib_type, dummy_user): event, abstract, track, contrib_type = abstract_objects event.abstract_email_templates = [ create_email_template(event, 0, 'accept', 'accepted', [{ 'state': [AbstractState.accepted.value], 'track': [track.id], 'contribution_type': [contrib_type.id] }, { 'state': [AbstractState.accepted.value], 'contribution_type': [] }], True) ] send_email = mocker.patch( 'fossir.modules.events.abstracts.notifications.send_email') abstract.state = AbstractState.accepted abstract.judge = dummy_user abstract.judgment_dt = now_utc(False) abstract.accepted_track = track send_abstract_notifications(abstract) assert send_email.call_count == 1 send_email.reset_mock() abstract.accepted_contrib_type = contrib_type send_abstract_notifications(abstract) assert send_email.call_count == 1 send_email.reset_mock() abstract.accepted_track = create_dummy_track(event) abstract.accepted_contrib_type = create_dummy_contrib_type( event, name='Presentation') db.session.flush() send_abstract_notifications(abstract) assert send_email.call_count == 0
def test_notification_stop_on_match(mocker, abstract_objects, create_email_template, dummy_user): event, abstract, track, contrib_type = abstract_objects event.abstract_email_templates = [ create_email_template(event, 0, 'accept', 'accepted poster', [{ 'state': [AbstractState.accepted.value] }], False), create_email_template(event, 0, 'accept', 'accepted poster 2', [{ 'state': [AbstractState.accepted.value] }], True) ] send_email = mocker.patch( 'fossir.modules.events.abstracts.notifications.send_email') abstract.state = AbstractState.accepted abstract.judge = dummy_user abstract.judgment_dt = now_utc(False) send_abstract_notifications(abstract) assert send_email.call_count == 2 send_email.reset_mock() event.abstract_email_templates[0].stop_on_match = True send_abstract_notifications(abstract) assert send_email.call_count == 1
def static_sites_cleanup(days=30): """Clean up old static sites :param days: number of days after which to remove static sites """ expired_sites = StaticSite.find_all( StaticSite.requested_dt < (now_utc() - timedelta(days=days)), StaticSite.state == StaticSiteState.success) logger.info('Removing %d expired static sites from the past %d days', len(expired_sites), days) try: for site in expired_sites: try: site.delete() except StorageReadOnlyError: # If a site is on read-only storage we simply keep it alive. logger.debug( 'Could not delete static site %r (read-only storage)', site) else: site.state = StaticSiteState.expired logger.info('Removed static site %r', site) finally: db.session.commit()
def register_login(self, ip): """Updates the last login information""" self.last_login_dt = now_utc() self.last_login_ip = ip
def can_edit_abstracts(self, user): modification_end = self.modification_end_dt return self.can_submit_abstracts(user) or ( modification_end is not None and modification_end > now_utc())
def modification_ended(self): return self.modification_end_dt is not None and self.modification_end_dt <= now_utc( )
def has_ended(self): return self.end_dt is not None and self.end_dt <= now_utc()
def has_started(self): return self.start_dt is not None and self.start_dt <= now_utc()
def close(self): now = now_utc(False) abstracts_settings.set(self.event, 'end_dt', now) if not self.has_started: abstracts_settings.set(self.event, 'start_dt', now)
def _process_args(self): RHDisplayCategoryBase._process_args(self) self.now = now_utc(exact=False).astimezone( self.category.display_tzinfo)