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)
Example #3
0
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)))
Example #4
0
 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')
Example #6
0
 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)
Example #7
0
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)
Example #9
0
 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)
              })
Example #11
0
 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
Example #13
0
    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)
Example #14
0
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)
Example #20
0
    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)
Example #21
0
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
Example #22
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
Example #23
0
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()
Example #24
0
 def register_login(self, ip):
     """Updates the last login information"""
     self.last_login_dt = now_utc()
     self.last_login_ip = ip
Example #25
0
 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())
Example #26
0
 def modification_ended(self):
     return self.modification_end_dt is not None and self.modification_end_dt <= now_utc(
     )
Example #27
0
 def has_ended(self):
     return self.end_dt is not None and self.end_dt <= now_utc()
Example #28
0
 def has_started(self):
     return self.start_dt is not None and self.start_dt <= now_utc()
Example #29
0
 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)
Example #30
0
 def _process_args(self):
     RHDisplayCategoryBase._process_args(self)
     self.now = now_utc(exact=False).astimezone(
         self.category.display_tzinfo)