class UserSetting(JSONSettingsBase, db.Model):
    """User-specific settings"""
    __table_args__ = (db.Index(None, 'user_id', 'module',
                               'name'), db.Index(None, 'user_id', 'module'),
                      db.UniqueConstraint('user_id', 'module', 'name'),
                      db.CheckConstraint('module = lower(module)',
                                         'lowercase_module'),
                      db.CheckConstraint('name = lower(name)',
                                         'lowercase_name'), {
                                             'schema': 'users'
                                         })

    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        nullable=False,
                        index=True)

    user = db.relationship('User',
                           lazy=True,
                           backref=db.backref('_all_settings',
                                              lazy='dynamic',
                                              cascade='all, delete-orphan'))

    @return_ascii
    def __repr__(self):
        return '<UserSetting({}, {}, {}, {!r})>'.format(
            self.user_id, self.module, self.name, self.value)
class EquipmentType(db.Model):
    __tablename__ = 'equipment_types'
    __table_args__ = (db.UniqueConstraint('name', 'location_id'), {
        'schema': 'roombooking'
    })

    id = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer,
                          db.ForeignKey('roombooking.equipment_types.id'))
    name = db.Column(db.String, nullable=False, index=True)
    location_id = db.Column(db.Integer,
                            db.ForeignKey('roombooking.locations.id'),
                            nullable=False)

    children = db.relationship('EquipmentType',
                               backref=db.backref('parent', remote_side=[id]))

    # relationship backrefs:
    # - location (Location.equipment_types)
    # - parent (EquipmentType.children)
    # - reservations (Reservation.used_equipment)
    # - rooms (Room.available_equipment)

    @return_ascii
    def __repr__(self):
        return u'<EquipmentType({0}, {1}, {2})>'.format(
            self.id, self.name, self.location_id)
class RoomAttribute(db.Model):
    __tablename__ = 'room_attributes'
    __table_args__ = (db.UniqueConstraint('name', 'location_id'), {
        'schema': 'roombooking'
    })

    id = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer,
                          db.ForeignKey('roombooking.room_attributes.id'))
    name = db.Column(db.String, nullable=False, index=True)
    title = db.Column(db.String, nullable=False)
    location_id = db.Column(db.Integer,
                            db.ForeignKey('roombooking.locations.id'),
                            nullable=False)
    type = db.Column(db.String, nullable=False)
    is_required = db.Column(db.Boolean, nullable=False)
    is_hidden = db.Column(db.Boolean, nullable=False)

    children = db.relationship('RoomAttribute',
                               backref=db.backref('parent', remote_side=[id]))

    # relationship backrefs:
    # - location (Location.attributes)
    # - parent (RoomAttribute.children)
    # - room_associations (RoomAttributeAssociation.attribute)

    @return_ascii
    def __repr__(self):
        return u'<RoomAttribute({}, {}, {})>'.format(self.id, self.name,
                                                     self.location.name)
Beispiel #4
0
class PaperCompetence(db.Model):
    __tablename__ = 'competences'
    __table_args__ = (db.UniqueConstraint('user_id', 'event_id'),
                      {'schema': 'event_paper_reviewing'})

    id = db.Column(
        db.Integer,
        primary_key=True
    )
    user_id = db.Column(
        db.Integer,
        db.ForeignKey('users.users.id'),
        index=True,
        nullable=False
    )
    event_id = db.Column(
        db.Integer,
        db.ForeignKey('events.events.id'),
        index=True,
        nullable=False
    )
    competences = db.Column(
        ARRAY(db.String),
        nullable=False,
        default=[]
    )

    event = db.relationship(
        'Event',
        lazy=True,
        backref=db.backref(
            'paper_competences',
            cascade='all, delete-orphan',
            lazy=True
        )
    )
    user = db.relationship(
        'User',
        lazy=True,
        backref=db.backref(
            'paper_competences',
            lazy='dynamic'
        )
    )

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'user_id', 'event_id', _text=', '.join(self.competences))
class Holiday(db.Model):
    __tablename__ = 'holidays'
    __table_args__ = (db.UniqueConstraint('date', 'location_id'), {
        'schema': 'roombooking'
    })

    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.Date, nullable=False, index=True)
    name = db.Column(db.String)
    location_id = db.Column(db.Integer,
                            db.ForeignKey('roombooking.locations.id'),
                            nullable=False)

    # relationship backrefs:
    # - location (Location.holidays)

    @return_ascii
    def __repr__(self):
        return u'<Holiday({}, {}, {}, {})>'.format(self.id, self.date,
                                                   self.name or 'n/a',
                                                   self.location.name)
class ContributionField(db.Model):
    __tablename__ = 'contribution_fields'
    __table_args__ = (db.UniqueConstraint('event_id', 'legacy_id'),
                      {'schema': 'events'})

    id = db.Column(
        db.Integer,
        primary_key=True
    )
    event_id = db.Column(
        db.Integer,
        db.ForeignKey('events.events.id'),
        index=True,
        nullable=False
    )
    legacy_id = db.Column(
        db.String,
        nullable=True
    )
    position = db.Column(
        db.Integer,
        nullable=False,
        default=_get_next_position
    )
    title = db.Column(
        db.String,
        nullable=False
    )
    description = db.Column(
        db.Text,
        nullable=False,
        default=''
    )
    is_required = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    is_active = db.Column(
        db.Boolean,
        nullable=False,
        default=True
    )
    field_type = db.Column(
        db.String,
        nullable=True
    )
    field_data = db.Column(
        JSON,
        nullable=False,
        default={}
    )

    event = db.relationship(
        'Event',
        lazy=True,
        backref=db.backref(
            'contribution_fields',
            order_by=position,
            cascade='all, delete-orphan',
            lazy='dynamic'
        )
    )

    # relationship backrefs:
    # - abstract_values (AbstractFieldValue.contribution_field)
    # - contribution_values (ContributionFieldValue.contribution_field)

    def _get_field(self, management=False):
        from fossir.modules.events.contributions import get_contrib_field_types
        try:
            impl = get_contrib_field_types()[self.field_type]
        except KeyError:
            return None
        return impl(self, management=management)

    @property
    def field(self):
        return self._get_field()

    @property
    def mgmt_field(self):
        return self._get_field(management=True)

    @property
    def filter_choices(self):
        return {x['id']: x['option'] for x in self.field_data.get('options', {})}

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'field_type', is_required=False, is_active=True, _text=self.title)

    @locator_property
    def locator(self):
        return dict(self.event.locator, contrib_field_id=self.id)
Beispiel #7
0
class Identity(db.Model):
    """Identities of fossir users"""
    __tablename__ = 'identities'
    __table_args__ = (db.UniqueConstraint('provider', 'identifier'), {
        'schema': 'users'
    })

    #: the unique id of the identity
    id = db.Column(db.Integer, primary_key=True)
    #: the id of the user this identity belongs to
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        nullable=False)
    #: the provider name of the identity
    provider = db.Column(db.String, nullable=False)
    #: the unique identifier of the user within its provider
    identifier = db.Column(db.String, nullable=False)
    #: internal data used by the flask-multipass system
    multipass_data = db.Column(JSON, nullable=False, default=lambda: None)
    #: the user data from the user provider
    _data = db.Column('data', JSON, nullable=False, default={})
    #: the hash of the password in case of a local identity
    password_hash = db.Column(db.String)
    #: the password of the user in case of a local identity
    password = PasswordProperty('password_hash')
    #: the timestamp of the latest login
    last_login_dt = db.Column(UTCDateTime)
    #: the ip address that was used for the latest login
    last_login_ip = db.Column(INET)

    # relationship backrefs:
    # - user (User.identities)

    @property
    def data(self):
        data = MultiDict()
        data.update(self._data)
        return data

    @data.setter
    def data(self, data):
        self._data = dict(data.lists())

    @property
    def locator(self):
        return {'identity': self.id}

    @property
    def safe_last_login_dt(self):
        """last_login_dt that is safe for sorting (no None values)"""
        return self.last_login_dt or as_utc(datetime(1970, 1, 1))

    def register_login(self, ip):
        """Updates the last login information"""
        self.last_login_dt = now_utc()
        self.last_login_ip = ip

    @return_ascii
    def __repr__(self):
        return '<Identity({}, {}, {}, {})>'.format(self.id, self.user_id,
                                                   self.provider,
                                                   self.identifier)
Beispiel #8
0
class RegistrationInvitation(db.Model):
    """An invitation for someone to register"""
    __tablename__ = 'invitations'
    __table_args__ = (db.CheckConstraint(
        "(state = {state}) OR (registration_id IS NULL)".format(
            state=InvitationState.accepted),
        name='registration_state'),
                      db.UniqueConstraint('registration_form_id', 'email'), {
                          'schema': 'event_registration'
                      })

    #: The ID of the invitation
    id = db.Column(db.Integer, primary_key=True)
    #: The UUID of the invitation
    uuid = db.Column(UUID,
                     index=True,
                     unique=True,
                     nullable=False,
                     default=lambda: unicode(uuid4()))
    #: The ID of the registration form
    registration_form_id = db.Column(
        db.Integer,
        db.ForeignKey('event_registration.forms.id'),
        index=True,
        nullable=False)
    #: The ID of the registration (if accepted)
    registration_id = db.Column(
        db.Integer,
        db.ForeignKey('event_registration.registrations.id'),
        index=True,
        unique=True,
        nullable=True)
    #: The state of the invitation
    state = db.Column(PyIntEnum(InvitationState),
                      nullable=False,
                      default=InvitationState.pending)
    #: Whether registration moderation should be skipped
    skip_moderation = db.Column(db.Boolean, nullable=False, default=False)
    #: The email of the invited person
    email = db.Column(db.String, nullable=False)
    #: The first name of the invited person
    first_name = db.Column(db.String, nullable=False)
    #: The last name of the invited person
    last_name = db.Column(db.String, nullable=False)
    #: The affiliation of the invited person
    affiliation = db.Column(db.String, nullable=False)

    #: The associated registration
    registration = db.relationship('Registration',
                                   lazy=True,
                                   backref=db.backref('invitation',
                                                      lazy=True,
                                                      uselist=False))

    # relationship backrefs:
    # - registration_form (RegistrationForm.invitations)

    @locator_property
    def locator(self):
        return dict(self.registration_form.locator, invitation_id=self.id)

    @locator.uuid
    def locator(self):
        """A locator suitable for 'display' pages.

        Instead of the numeric ID it uses the UUID
        """
        assert self.uuid is not None
        return dict(self.registration_form.locator, invitation=self.uuid)

    @return_ascii
    def __repr__(self):
        full_name = '{} {}'.format(self.first_name, self.last_name)
        return format_repr(self,
                           'id',
                           'registration_form_id',
                           'email',
                           'state',
                           _text=full_name)
Beispiel #9
0
class SessionBlock(LocationMixin, db.Model):
    __tablename__ = 'session_blocks'
    __auto_table_args = (db.UniqueConstraint('id', 'session_id'),  # useless but needed for the compound fkey
                         {'schema': 'events'})
    location_backref_name = 'session_blocks'

    @declared_attr
    def __table_args__(cls):
        return auto_table_args(cls)

    id = db.Column(
        db.Integer,
        primary_key=True
    )
    session_id = db.Column(
        db.Integer,
        db.ForeignKey('events.sessions.id'),
        index=True,
        nullable=False
    )
    title = db.Column(
        db.String,
        nullable=False,
        default=''
    )
    duration = db.Column(
        db.Interval,
        nullable=False
    )

    #: Persons associated with this session block
    person_links = db.relationship(
        'SessionBlockPersonLink',
        lazy=True,
        cascade='all, delete-orphan',
        backref=db.backref(
            'session_block',
            lazy=True
        )
    )

    # relationship backrefs:
    # - contributions (Contribution.session_block)
    # - legacy_mapping (LegacySessionBlockMapping.session_block)
    # - session (Session.blocks)
    # - timetable_entry (TimetableEntry.session_block)
    # - vc_room_associations (VCRoomEventAssociation.linked_block)

    @declared_attr
    def contribution_count(cls):
        from fossir.modules.events.contributions.models.contributions import Contribution
        query = (db.select([db.func.count(Contribution.id)])
                 .where((Contribution.session_block_id == cls.id) & ~Contribution.is_deleted)
                 .correlate_except(Contribution))
        return db.column_property(query, deferred=True)

    def __init__(self, **kwargs):
        # explicitly initialize those relationships with None to avoid
        # an extra query to check whether there is an object associated
        # when assigning a new one (e.g. during cloning)
        kwargs.setdefault('timetable_entry', None)
        super(SessionBlock, self).__init__(**kwargs)

    @property
    def event(self):
        return self.session.event

    @locator_property
    def locator(self):
        return dict(self.session.locator, block_id=self.id)

    @property
    def location_parent(self):
        return self.session

    def can_access(self, user, allow_admin=True):
        return self.session.can_access(user, allow_admin=allow_admin)

    @property
    def has_note(self):
        return self.session.has_note

    @property
    def note(self):
        return self.session.note

    @property
    def full_title(self):
        return '{}: {}'.format(self.session.title, self.title) if self.title else self.session.title

    def can_manage(self, user, allow_admin=True):
        return self.session.can_manage_blocks(user, allow_admin=allow_admin)

    def can_manage_attachments(self, user):
        return self.session.can_manage_attachments(user)

    def can_edit_note(self, user):
        return self.session.can_edit_note(user)

    @property
    def start_dt(self):
        return self.timetable_entry.start_dt if self.timetable_entry else None

    @property
    def end_dt(self):
        return self.timetable_entry.start_dt + self.duration if self.timetable_entry else None

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', _text=self.title or None)
 def __auto_table_args():
     return (db.Index(None, 'category_id', 'module',
                      'name'), db.Index(None, 'category_id', 'module'),
             db.UniqueConstraint('category_id', 'module', 'name'), {
                 'schema': 'categories'
             })
Beispiel #11
0
 def __auto_table_args():
     return db.UniqueConstraint('event_id', 'module', 'name'),
class Agreement(db.Model):
    """Agreements between a person and fossir"""
    __tablename__ = 'agreements'
    __table_args__ = (db.UniqueConstraint('event_id', 'type', 'identifier'), {
        'schema': 'events'
    })

    #: Entry ID
    id = db.Column(db.Integer, primary_key=True)
    #: Entry universally unique ID
    uuid = db.Column(db.String, nullable=False)
    #: ID of the event
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         nullable=False,
                         index=True)
    #: Type of agreement
    type = db.Column(db.String, nullable=False)
    #: Unique identifier within the event and type
    identifier = db.Column(db.String, nullable=False)
    #: Email of the person agreeing
    person_email = db.Column(db.String, nullable=True)
    #: Full name of the person agreeing
    person_name = db.Column(db.String, nullable=False)
    #: A :class:`AgreementState`
    state = db.Column(PyIntEnum(AgreementState),
                      default=AgreementState.pending,
                      nullable=False)
    #: The date and time the agreement was created
    timestamp = db.Column(UTCDateTime, default=now_utc, nullable=False)
    #: ID of a linked user
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        index=True,
                        nullable=True)
    #: The date and time the agreement was signed
    signed_dt = db.Column(UTCDateTime)
    #: The IP from which the agreement was signed
    signed_from_ip = db.Column(db.String)
    #: Explanation as to why the agreement was accepted/rejected
    reason = db.Column(db.String)
    #: Attachment
    attachment = db.deferred(db.Column(db.LargeBinary))
    #: Filename and extension of the attachment
    attachment_filename = db.Column(db.String)
    #: Definition-specific data of the agreement
    data = db.Column(JSON)

    #: The user this agreement is linked to
    user = db.relationship('User',
                           lazy=False,
                           backref=db.backref('agreements', lazy='dynamic'))
    #: The Event this agreement is associated with
    event = db.relationship('Event',
                            lazy=True,
                            backref=db.backref('agreements', lazy='dynamic'))

    @hybrid_property
    def accepted(self):
        return self.state in {
            AgreementState.accepted, AgreementState.accepted_on_behalf
        }

    @accepted.expression
    def accepted(self):
        return self.state.in_(
            (AgreementState.accepted, AgreementState.accepted_on_behalf))

    @hybrid_property
    def pending(self):
        return self.state == AgreementState.pending

    @hybrid_property
    def rejected(self):
        return self.state in {
            AgreementState.rejected, AgreementState.rejected_on_behalf
        }

    @rejected.expression
    def rejected(self):
        return self.state.in_(
            (AgreementState.rejected, AgreementState.rejected_on_behalf))

    @hybrid_property
    def signed_on_behalf(self):
        return self.state in {
            AgreementState.accepted_on_behalf,
            AgreementState.rejected_on_behalf
        }

    @signed_on_behalf.expression
    def signed_on_behalf(self):
        return self.state.in_((AgreementState.accepted_on_behalf,
                               AgreementState.rejected_on_behalf))

    @property
    def definition(self):
        from fossir.modules.events.agreements.util import get_agreement_definitions
        return get_agreement_definitions().get(self.type)

    @property
    def locator(self):
        return {'confId': self.event_id, 'id': self.id}

    @return_ascii
    def __repr__(self):
        state = self.state.name if self.state is not None else None
        return '<Agreement({}, {}, {}, {}, {}, {})>'.format(
            self.id, self.event_id, self.type, self.identifier,
            self.person_email, state)

    @staticmethod
    def create_from_data(event, type_, person):
        agreement = Agreement(event=event,
                              type=type_,
                              state=AgreementState.pending,
                              uuid=str(uuid4()))
        agreement.identifier = person.identifier
        agreement.person_email = person.email
        agreement.person_name = person.name
        if person.user:
            agreement.user = person.user
        agreement.data = person.data
        return agreement

    def accept(self, from_ip, reason=None, on_behalf=False):
        self.state = AgreementState.accepted if not on_behalf else AgreementState.accepted_on_behalf
        self.signed_from_ip = from_ip
        self.reason = reason
        self.signed_dt = now_utc()
        self.definition.handle_accepted(self)

    def reject(self, from_ip, reason=None, on_behalf=False):
        self.state = AgreementState.rejected if not on_behalf else AgreementState.rejected_on_behalf
        self.signed_from_ip = from_ip
        self.reason = reason
        self.signed_dt = now_utc()
        self.definition.handle_rejected(self)

    def reset(self):
        self.definition.handle_reset(self)
        self.state = AgreementState.pending
        self.attachment = None
        self.attachment_filename = None
        self.reason = None
        self.signed_dt = None
        self.signed_from_ip = None

    def render(self, form, **kwargs):
        definition = self.definition
        if definition is None:
            raise ServiceUnavailable(
                'This agreement type is currently not available.')
        return definition.render_form(self, form, **kwargs)

    def belongs_to(self, person):
        return self.identifier == person.identifier

    def is_orphan(self):
        definition = self.definition
        if definition is None:
            raise ServiceUnavailable(
                'This agreement type is currently not available.')
        return definition.is_agreement_orphan(self.event, self)
Beispiel #13
0
class RegistrationForm(db.Model):
    """A registration form for an event"""

    __tablename__ = 'forms'
    __table_args__ = (
        db.Index(
            'ix_uq_forms_participation',
            'event_id',
            unique=True,
            postgresql_where=db.text('is_participation AND NOT is_deleted')),
        db.UniqueConstraint(
            'id', 'event_id'),  # useless but needed for the registrations fkey
        {
            'schema': 'event_registration'
        })

    #: The ID of the object
    id = db.Column(db.Integer, primary_key=True)
    #: The ID of the event
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         index=True,
                         nullable=False)
    #: The title of the registration form
    title = db.Column(db.String, nullable=False)
    #: Whether it's the 'Participants' form of a meeting/lecture
    is_participation = db.Column(db.Boolean, nullable=False, default=False)
    # An introduction text for users
    introduction = db.Column(db.Text, nullable=False, default='')
    #: Contact information for registrants
    contact_info = db.Column(db.String, nullable=False, default='')
    #: Datetime when the registration form is open
    start_dt = db.Column(UTCDateTime, nullable=True)
    #: Datetime when the registration form is closed
    end_dt = db.Column(UTCDateTime, nullable=True)
    #: Whether registration modifications are allowed
    modification_mode = db.Column(PyIntEnum(ModificationMode),
                                  nullable=False,
                                  default=ModificationMode.not_allowed)
    #: Datetime when the modification period is over
    modification_end_dt = db.Column(UTCDateTime, nullable=True)
    #: Whether the registration has been marked as deleted
    is_deleted = db.Column(db.Boolean, nullable=False, default=False)
    #: Whether users must be logged in to register
    require_login = db.Column(db.Boolean, nullable=False, default=False)
    #: Whether registrations must be associated with an fossir account
    require_user = db.Column(db.Boolean, nullable=False, default=False)
    #: Maximum number of registrations allowed
    registration_limit = db.Column(db.Integer, nullable=True)
    #: Whether registrations should be displayed in the participant list
    publish_registrations_enabled = db.Column(db.Boolean,
                                              nullable=False,
                                              default=False)
    #: Whether to display the number of registrations
    publish_registration_count = db.Column(db.Boolean,
                                           nullable=False,
                                           default=False)
    #: Whether checked-in status should be displayed in the event pages and participant list
    publish_checkin_enabled = db.Column(db.Boolean,
                                        nullable=False,
                                        default=False)
    #: Whether registrations must be approved by a manager
    moderation_enabled = db.Column(db.Boolean, nullable=False, default=False)
    #: The base fee users have to pay when registering
    base_price = db.Column(
        db.Numeric(8, 2),  # max. 999999.99
        nullable=False,
        default=0)
    #: Currency for prices in the registration form
    currency = db.Column(db.String, nullable=False)
    #: Notifications sender address
    notification_sender_address = db.Column(db.String, nullable=True)
    #: Custom message to include in emails for pending registrations
    message_pending = db.Column(db.Text, nullable=False, default='')
    #: Custom message to include in emails for unpaid registrations
    message_unpaid = db.Column(db.Text, nullable=False, default='')
    #: Custom message to include in emails for complete registrations
    message_complete = db.Column(db.Text, nullable=False, default='')
    #: Whether the manager notifications for this event are enabled
    manager_notifications_enabled = db.Column(db.Boolean,
                                              nullable=False,
                                              default=False)
    #: List of emails that should receive management notifications
    manager_notification_recipients = db.Column(ARRAY(db.String),
                                                nullable=False,
                                                default=[])
    #: Whether tickets are enabled for this form
    tickets_enabled = db.Column(db.Boolean, nullable=False, default=False)
    #: Whether to send tickets by e-mail
    ticket_on_email = db.Column(db.Boolean, nullable=False, default=True)
    #: Whether to show a ticket download link on the event homepage
    ticket_on_event_page = db.Column(db.Boolean, nullable=False, default=True)
    #: Whether to show a ticket download link on the registration summary page
    ticket_on_summary_page = db.Column(db.Boolean,
                                       nullable=False,
                                       default=True)
    #: The ID of the template used to generate tickets
    ticket_template_id = db.Column(db.Integer,
                                   db.ForeignKey(DesignerTemplate.id),
                                   nullable=True,
                                   index=True)

    #: The Event containing this registration form
    event = db.relationship(
        'Event',
        lazy=True,
        backref=db.backref(
            'registration_forms',
            primaryjoin=
            '(RegistrationForm.event_id == Event.id) & ~RegistrationForm.is_deleted',
            cascade='all, delete-orphan',
            lazy=True))
    #: The template used to generate tickets
    ticket_template = db.relationship('DesignerTemplate',
                                      lazy=True,
                                      foreign_keys=ticket_template_id,
                                      backref=db.backref('ticket_for_regforms',
                                                         lazy=True))
    # The items (sections, text, fields) in the form
    form_items = db.relationship('RegistrationFormItem',
                                 lazy=True,
                                 cascade='all, delete-orphan',
                                 order_by='RegistrationFormItem.position',
                                 backref=db.backref('registration_form',
                                                    lazy=True))
    #: The registrations associated with this form
    registrations = db.relationship(
        'Registration',
        lazy=True,
        cascade='all, delete-orphan',
        foreign_keys=[Registration.registration_form_id],
        backref=db.backref('registration_form', lazy=True))
    #: The registration invitations associated with this form
    invitations = db.relationship('RegistrationInvitation',
                                  lazy=True,
                                  cascade='all, delete-orphan',
                                  backref=db.backref('registration_form',
                                                     lazy=True))

    @hybrid_property
    def has_ended(self):
        return self.end_dt is not None and self.end_dt <= now_utc()

    @has_ended.expression
    def has_ended(cls):
        return cls.end_dt.isnot(None) & (cls.end_dt <= now_utc())

    @hybrid_property
    def has_started(self):
        return self.start_dt is not None and self.start_dt <= now_utc()

    @has_started.expression
    def has_started(cls):
        return cls.start_dt.isnot(None) & (cls.start_dt <= now_utc())

    @hybrid_property
    def is_modification_open(self):
        end_dt = self.modification_end_dt if self.modification_end_dt else self.end_dt
        return now_utc() <= end_dt if end_dt else True

    @is_modification_open.expression
    def is_modification_open(self):
        now = now_utc()
        return now <= db.func.coalesce(self.modification_end_dt, self.end_dt,
                                       now)

    @hybrid_property
    def is_open(self):
        return not self.is_deleted and self.has_started and not self.has_ended

    @is_open.expression
    def is_open(cls):
        return ~cls.is_deleted & cls.has_started & ~cls.has_ended

    @hybrid_property
    def is_scheduled(self):
        return not self.is_deleted and self.start_dt is not None

    @is_scheduled.expression
    def is_scheduled(cls):
        return ~cls.is_deleted & cls.start_dt.isnot(None)

    @property
    def locator(self):
        return dict(self.event.locator, reg_form_id=self.id)

    @property
    def active_fields(self):
        return [
            field for field in self.form_items
            if (field.is_field and field.is_enabled and not field.is_deleted
                and field.parent.is_enabled and not field.parent.is_deleted)
        ]

    @property
    def sections(self):
        return [x for x in self.form_items if x.is_section]

    @property
    def disabled_sections(self):
        return [
            x for x in self.sections if not x.is_visible and not x.is_deleted
        ]

    @property
    def limit_reached(self):
        return self.registration_limit and len(
            self.active_registrations) >= self.registration_limit

    @property
    def is_active(self):
        return self.is_open and not self.limit_reached

    @property
    @memoize_request
    def active_registrations(self):
        return (Registration.query.with_parent(self).filter(
            Registration.is_active).options(subqueryload('data')).all())

    @property
    def sender_address(self):
        contact_email = self.event.contact_emails[
            0] if self.event.contact_emails else None
        return self.notification_sender_address or contact_email

    @return_ascii
    def __repr__(self):
        return '<RegistrationForm({}, {}, {})>'.format(self.id, self.event_id,
                                                       self.title)

    def is_modification_allowed(self, registration):
        """Checks whether a registration may be modified"""
        if not registration.is_active:
            return False
        elif self.modification_mode == ModificationMode.allowed_always:
            return True
        elif self.modification_mode == ModificationMode.allowed_until_payment:
            return not registration.is_paid
        else:
            return False

    def can_submit(self, user):
        return self.is_active and (not self.require_login or user)

    @memoize_request
    def get_registration(self, user=None, uuid=None, email=None):
        """Retrieves registrations for this registration form by user or uuid"""
        if (bool(user) + bool(uuid) + bool(email)) != 1:
            raise ValueError(
                "Exactly one of `user`, `uuid` and `email` must be specified")
        if user:
            return user.registrations.filter_by(registration_form=self).filter(
                Registration.is_active).first()
        if uuid:
            try:
                UUID(hex=uuid)
            except ValueError:
                raise BadRequest('Malformed registration token')
            return Registration.query.with_parent(self).filter_by(
                uuid=uuid).filter(Registration.is_active).first()
        if email:
            return Registration.query.with_parent(self).filter_by(
                email=email).filter(Registration.is_active).first()

    def render_base_price(self):
        return format_currency(self.base_price,
                               self.currency,
                               locale=session.lang or 'en_GB')

    def get_personal_data_field_id(self, personal_data_type):
        """Returns the field id corresponding to the personal data field with the given name."""
        for field in self.active_fields:
            if (isinstance(field, RegistrationFormPersonalDataField)
                    and field.personal_data_type == personal_data_type):
                return field.id
class OAuthToken(db.Model):
    """OAuth tokens"""

    __tablename__ = 'tokens'
    __table_args__ = (db.UniqueConstraint('application_id', 'user_id'), {
        'schema': 'oauth'
    })

    #: the unique identifier of the token
    id = db.Column(db.Integer, primary_key=True)
    #: the identifier of the linked application
    application_id = db.Column(db.Integer,
                               db.ForeignKey('oauth.applications.id'),
                               nullable=False)
    #: the identifier of the linked user
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        nullable=False,
                        index=True)
    #: an unguessable unique string of characters
    access_token = db.Column(UUID, unique=True, nullable=False)
    #: the list of scopes the linked application has access to
    _scopes = db.Column('scopes', ARRAY(db.String))
    #: the last time the token was used by the application
    last_used_dt = db.Column(UTCDateTime, nullable=True)

    #: application authorized by this token
    application = db.relationship('OAuthApplication',
                                  lazy=True,
                                  backref=db.backref(
                                      'tokens',
                                      lazy='dynamic',
                                      cascade='all, delete-orphan'))
    #: the user who owns this token
    user = db.relationship('User',
                           lazy=False,
                           backref=db.backref('oauth_tokens',
                                              lazy='dynamic',
                                              cascade='all, delete-orphan'))

    @property
    def locator(self):
        return {'id': self.id}

    @property
    def expires(self):
        return None

    @property
    def scopes(self):
        """The set of scopes the linked application has access to."""
        return set(self._scopes)

    @scopes.setter
    def scopes(self, value):
        self._scopes = sorted(value)

    @property
    def type(self):
        return 'bearer'

    @return_ascii
    def __repr__(self):  # pragma: no cover
        return '<OAuthToken({}, {}, {})>'.format(self.id, self.application,
                                                 self.user)
class PaperRevision(ProposalRevisionMixin, RenderModeMixin, db.Model):
    __tablename__ = 'revisions'
    __table_args__ = (
        db.Index(None,
                 'contribution_id',
                 unique=True,
                 postgresql_where=db.text('state = {}'.format(
                     PaperRevisionState.accepted))),
        db.UniqueConstraint('contribution_id', 'submitted_dt'),
        db.CheckConstraint(
            '(state IN ({}, {}, {})) = (judge_id IS NOT NULL)'.format(
                PaperRevisionState.accepted, PaperRevisionState.rejected,
                PaperRevisionState.to_be_corrected),
            name='judge_if_judged'),
        db.CheckConstraint(
            '(state IN ({}, {}, {})) = (judgment_dt IS NOT NULL)'.format(
                PaperRevisionState.accepted, PaperRevisionState.rejected,
                PaperRevisionState.to_be_corrected),
            name='judgment_dt_if_judged'), {
                'schema': 'event_paper_reviewing'
            })

    possible_render_modes = {RenderMode.markdown}
    default_render_mode = RenderMode.markdown
    proposal_attr = 'paper'

    id = db.Column(db.Integer, primary_key=True)
    state = db.Column(PyIntEnum(PaperRevisionState),
                      nullable=False,
                      default=PaperRevisionState.submitted)
    _contribution_id = db.Column('contribution_id',
                                 db.Integer,
                                 db.ForeignKey('events.contributions.id'),
                                 index=True,
                                 nullable=False)
    submitter_id = db.Column(db.Integer,
                             db.ForeignKey('users.users.id'),
                             index=True,
                             nullable=False)
    submitted_dt = db.Column(UTCDateTime, nullable=False, default=now_utc)
    judge_id = db.Column(db.Integer,
                         db.ForeignKey('users.users.id'),
                         index=True,
                         nullable=True)
    judgment_dt = db.Column(UTCDateTime, nullable=True)
    _judgment_comment = db.Column('judgment_comment',
                                  db.Text,
                                  nullable=False,
                                  default='')

    _contribution = db.relationship('Contribution',
                                    lazy=True,
                                    backref=db.backref(
                                        '_paper_revisions',
                                        lazy=True,
                                        order_by=submitted_dt.asc()))
    submitter = db.relationship('User',
                                lazy=True,
                                foreign_keys=submitter_id,
                                backref=db.backref('paper_revisions',
                                                   lazy='dynamic'))
    judge = db.relationship('User',
                            lazy=True,
                            foreign_keys=judge_id,
                            backref=db.backref('judged_papers',
                                               lazy='dynamic'))

    judgment_comment = RenderModeMixin.create_hybrid_property(
        '_judgment_comment')

    # relationship backrefs:
    # - comments (PaperReviewComment.paper_revision)
    # - files (PaperFile.paper_revision)
    # - reviews (PaperReview.revision)

    def __init__(self, *args, **kwargs):
        paper = kwargs.pop('paper', None)
        if paper:
            kwargs.setdefault('_contribution', paper.contribution)
        super(PaperRevision, self).__init__(*args, **kwargs)

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', '_contribution_id', state=None)

    @locator_property
    def locator(self):
        return dict(self.paper.locator, revision_id=self.id)

    @property
    def paper(self):
        return self._contribution.paper

    @property
    def is_last_revision(self):
        return self == self.paper.last_revision

    @property
    def number(self):
        return self.paper.revisions.index(self) + 1

    @paper.setter
    def paper(self, paper):
        self._contribution = paper.contribution

    def get_timeline(self, user=None):
        comments = [x for x in self.comments
                    if x.can_view(user)] if user else self.comments
        reviews = [x for x in self.reviews
                   if x.can_view(user)] if user else self.reviews
        judgment = [
            PaperJudgmentProxy(self)
        ] if self.state == PaperRevisionState.to_be_corrected else []
        return sorted(chain(comments, reviews, judgment),
                      key=attrgetter('created_dt'))

    def get_reviews(self, group=None, user=None):
        reviews = []
        if user and group:
            reviews = [
                x for x in self.reviews
                if x.group.instance == group and x.user == user
            ]
        elif user:
            reviews = [x for x in self.reviews if x.user == user]
        elif group:
            reviews = [x for x in self.reviews if x.group.instance == group]
        return reviews

    def get_reviewed_for_groups(self, user, include_reviewed=False):
        from fossir.modules.events.papers.models.reviews import PaperTypeProxy
        reviewed_for = {x.type
                        for x in self.reviews
                        if x.user == user} if include_reviewed else set()
        if self.paper.cfp.content_reviewing_enabled and user in self.paper.cfp.content_reviewers:
            reviewed_for.add(PaperReviewType.content)
        if self.paper.cfp.layout_reviewing_enabled and user in self.paper.cfp.layout_reviewers:
            reviewed_for.add(PaperReviewType.layout)
        return set(map(PaperTypeProxy, reviewed_for))

    def has_user_reviewed(self, user, review_type=None):
        from fossir.modules.events.papers.models.reviews import PaperReviewType
        if review_type:
            if isinstance(review_type, basestring):
                review_type = PaperReviewType[review_type]
            return any(review.user == user and review.type == review_type
                       for review in self.reviews)
        else:
            layout_review = next(
                (review for review in self.reviews if review.user == user
                 and review.type == PaperReviewType.layout), None)
            content_review = next(
                (review for review in self.reviews if review.user == user
                 and review.type == PaperReviewType.content), None)
            if user in self._contribution.paper_layout_reviewers and user in self._contribution.paper_content_reviewers:
                return bool(layout_review and content_review)
            elif user in self._contribution.paper_layout_reviewers:
                return bool(layout_review)
            elif user in self._contribution.paper_content_reviewers:
                return bool(content_review)

    def get_spotlight_file(self):
        pdf_files = [
            paper_file for paper_file in self.files
            if paper_file.content_type == 'application/pdf'
        ]
        return pdf_files[0] if len(pdf_files) == 1 else None