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)
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)
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)
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)
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' })
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)
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