示例#1
0
class AbstractReviewRating(db.Model):
    __tablename__ = 'abstract_review_ratings'
    __table_args__ = (db.UniqueConstraint('review_id', 'question_id'), {
        'schema': 'event_abstracts'
    })

    id = db.Column(db.Integer, primary_key=True)
    question_id = db.Column(
        db.Integer,
        db.ForeignKey('event_abstracts.abstract_review_questions.id'),
        index=True,
        nullable=False)
    review_id = db.Column(db.Integer,
                          db.ForeignKey('event_abstracts.abstract_reviews.id'),
                          index=True,
                          nullable=False)
    value = db.Column(db.Integer, nullable=False)
    question = db.relationship('AbstractReviewQuestion',
                               lazy=True,
                               backref=db.backref('ratings',
                                                  cascade='all, delete-orphan',
                                                  lazy=True))
    review = db.relationship('AbstractReview',
                             lazy=True,
                             backref=db.backref('ratings',
                                                cascade='all, delete-orphan',
                                                lazy=True))

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'review_id', 'question_id')
class VidyoExtension(db.Model):
    __tablename__ = 'vidyo_extensions'
    __table_args__ = {'schema': 'plugin_vc_vidyo'}

    #: ID of the videoconference room
    vc_room_id = db.Column(db.Integer,
                           db.ForeignKey('events.vc_rooms.id'),
                           primary_key=True)
    extension = db.Column(db.BigInteger, index=True)
    owned_by_id = db.Column(db.Integer,
                            db.ForeignKey('users.users.id'),
                            index=True,
                            nullable=False)
    vc_room = db.relationship('VCRoom',
                              lazy=False,
                              backref=db.backref('vidyo_extension',
                                                 cascade='all, delete-orphan',
                                                 uselist=False,
                                                 lazy=False))

    #: The user who owns the Vidyo room
    owned_by_user = db.relationship('User',
                                    lazy=True,
                                    backref=db.backref('vc_rooms_vidyo',
                                                       lazy='dynamic'))

    @return_ascii
    def __repr__(self):
        return '<VidyoExtension({}, {}, {})>'.format(self.vc_room,
                                                     self.extension,
                                                     self.owned_by_user)
示例#3
0
class Judgment(db.Model):
    """Represents an abstract judgment, emitted by a judge"""

    __tablename__ = 'judgments'
    __table_args__ = (db.UniqueConstraint('abstract_id', 'track_id',
                                          'judge_user_id'), {
                                              'schema': 'event_abstracts'
                                          })

    id = db.Column(db.Integer, primary_key=True)
    creation_dt = db.Column(UTCDateTime,
                            nullable=False,
                            default=now_utc,
                            index=True)
    abstract_id = db.Column(db.Integer,
                            db.ForeignKey('event_abstracts.abstracts.id'),
                            index=True,
                            nullable=False)
    track_id = db.Column(db.Integer, nullable=False)
    judge_user_id = db.Column(db.Integer,
                              db.ForeignKey('users.users.id'),
                              nullable=False,
                              index=True)
    accepted_type_id = db.Column(db.Integer,
                                 db.ForeignKey('events.contribution_types.id'),
                                 nullable=True,
                                 index=True)
    abstract = db.relationship('Abstract',
                               lazy=False,
                               backref=db.backref('judgments', lazy='dynamic'))
    judge = db.relationship('User',
                            lazy=False,
                            backref=db.backref('abstract_judgments',
                                               lazy='dynamic'))
    accepted_type = db.relationship('ContributionType',
                                    lazy=False,
                                    backref=db.backref('abstract_judgments',
                                                       lazy='dynamic'))

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           abstract=self.abstract,
                           judge=self.judge)

    @property
    def as_legacy(self):
        return next(
            (judgment for judgment in
             self.abstract.as_legacy.getJudgementHistoryByTrack(self.track)
             if judgment.getResponsible().as_new == self.judge), None)

    @property
    def track(self):
        return self.abstract.event_new.as_legacy.getTrackById(
            str(self.track_id))
class VidyoExtension(db.Model):
    __tablename__ = 'vidyo_extensions'
    __table_args__ = {'schema': 'plugin_vc_vidyo'}

    #: ID of the videoconference room
    vc_room_id = db.Column(
        db.Integer,
        db.ForeignKey('events.vc_rooms.id'),
        primary_key=True
    )
    extension = db.Column(
        db.BigInteger,
        index=True
    )
    owned_by_id = db.Column(
        db.Integer,
        db.ForeignKey('users.users.id'),
        index=True,
        nullable=False
    )
    vc_room = db.relationship(
        'VCRoom',
        lazy=False,
        backref=db.backref(
            'vidyo_extension',
            cascade='all, delete-orphan',
            uselist=False,
            lazy=False
        )
    )

    #: The user who owns the Vidyo room
    owned_by_user = db.relationship(
        'User',
        lazy=True,
        backref=db.backref(
            'vc_rooms_vidyo',
            lazy='dynamic'
        )
    )

    @property
    def join_url(self):
        from indico_vc_vidyo.plugin import VidyoPlugin
        url = self.vc_room.data['url']
        custom_url_tpl = VidyoPlugin.settings.get('client_chooser_url')
        if custom_url_tpl:
            return custom_url_tpl + '?' + urllib.urlencode({'url': url})
        return url

    @return_ascii
    def __repr__(self):
        return '<VidyoExtension({}, {}, {})>'.format(self.vc_room, self.extension, self.owned_by_user)
示例#5
0
class OutlookQueueEntry(db.Model):
    """Pending calendar updates"""
    __tablename__ = 'queue'
    __table_args__ = (db.Index(None, 'user_id', 'event_id', 'action'), {
        'schema': 'plugin_outlook'
    })

    #: Entry ID (mainly used to sort by insertion order)
    id = db.Column(db.Integer, primary_key=True)
    #: ID of the user
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        index=True,
                        nullable=False)
    #: ID of the event
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         index=True,
                         nullable=False)
    #: :class:`OutlookAction` to perform
    action = db.Column(PyIntEnum(OutlookAction), nullable=False)

    #: The user associated with the queue entry
    user = db.relationship('User',
                           lazy=False,
                           backref=db.backref('outlook_queue', lazy='dynamic'))
    #: The Event this queue entry is associated with
    event = db.relationship('Event',
                            lazy=True,
                            backref=db.backref('outlook_queue_entries',
                                               lazy='dynamic'))

    @return_ascii
    def __repr__(self):
        return '<OutlookQueueEntry({}, {}, {}, {})>'.format(
            self.id, self.event_id, self.user_id,
            OutlookAction(self.action).name)

    @classmethod
    def record(cls, event, user, action):
        """Records a new calendar action

        :param event: the event (a :class:`.Event` instance)
        :param user: the user (a :class:`.User` instance)
        :param action: the action (an :class:`OutlookAction` member)
        """
        # It would be nice to delete matching records first, but this sometimes results in very weird deadlocks
        event.outlook_queue_entries.append(cls(user_id=user.id, action=action))
        db.session.flush()
示例#6
0
 def user_id(cls):
     return db.Column(
         db.Integer,
         db.ForeignKey('users.users.id'),
         nullable=True,
         index=True
     )
示例#7
0
 def category_role_id(cls):
     if not cls.allow_category_roles:
         return
     return db.Column(db.Integer,
                      db.ForeignKey('categories.roles.id'),
                      nullable=True,
                      index=True)
示例#8
0
文件: persons.py 项目: javfg/indico
 def _affiliation_id(cls):
     return db.Column(
         'affiliation_id',
         db.ForeignKey('indico.affiliations.id'),
         nullable=True,
         index=True
     )
示例#9
0
文件: persons.py 项目: wtakase/indico
class SubContributionPersonLink(PersonLinkBase):
    """Association between EventPerson and SubContribution."""

    __tablename__ = 'subcontribution_person_links'
    __auto_table_args = {'schema': 'events'}
    person_link_backref_name = 'subcontribution_links'
    person_link_unique_columns = ('subcontribution_id', )
    object_relationship_name = 'subcontribution'

    # subcontribution persons are always speakers and never authors
    # we provide these attributes to make subcontribution links
    # compatible with contribution links
    is_speaker = True
    author_type = AuthorType.none

    subcontribution_id = db.Column(db.Integer,
                                   db.ForeignKey('events.subcontributions.id'),
                                   index=True,
                                   nullable=False)

    # relationship backrefs:
    # - subcontribution (SubContribution.person_links)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'person_id',
                           'subcontribution_id',
                           _text=self.full_name)
示例#10
0
 def registration_form_id(cls):
     if not cls.allow_registration_forms:
         return
     return db.Column(db.Integer,
                      db.ForeignKey('event_registration.forms.id'),
                      nullable=True,
                      index=True)
示例#11
0
class EventPersonLink(PersonLinkBase):
    """Association between EventPerson and Event.

    Chairperson or speaker (lecture)
    """

    __tablename__ = 'event_person_links'
    __auto_table_args = {'schema': 'events'}
    person_link_backref_name = 'event_links'
    person_link_unique_columns = ('event_id', )
    object_relationship_name = 'event'

    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         index=True,
                         nullable=False)

    # relationship backrefs:
    # - event (Event.person_links)

    @property
    def is_submitter(self):
        if not self.event:
            raise Exception("No event to check submission rights against")
        return self.person.has_role('submit', self.event)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'person_id',
                           'event_id',
                           _text=self.full_name)
示例#12
0
 def event_role_id(cls):
     if not cls.allow_event_roles:
         return
     return db.Column(db.Integer,
                      db.ForeignKey('events.roles.id'),
                      nullable=True,
                      index=True)
示例#13
0
class AbstractPersonLink(PersonLinkBase):
    """Association between EventPerson and Abstract."""

    __tablename__ = 'abstract_person_links'
    __auto_table_args = {'schema': 'event_abstracts'}
    person_link_backref_name = 'abstract_links'
    person_link_unique_columns = ('abstract_id', )
    object_relationship_name = 'abstract'

    abstract_id = db.Column(db.Integer,
                            db.ForeignKey('event_abstracts.abstracts.id'),
                            index=True,
                            nullable=False)
    is_speaker = db.Column(db.Boolean, nullable=False, default=False)
    author_type = db.Column(PyIntEnum(AuthorType),
                            nullable=False,
                            default=AuthorType.none)

    # relationship backrefs:
    # - abstract (Abstract.person_links)

    @locator_property
    def locator(self):
        return dict(self.abstract.locator, person_id=self.id)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'person_id',
                           'abstract_id',
                           is_speaker=False,
                           author_type=None,
                           _text=self.full_name)
示例#14
0
 def person_id(cls):
     return db.Column(
         db.Integer,
         db.ForeignKey('events.persons.id'),
         index=True,
         nullable=False
     )
示例#15
0
文件: groups.py 项目: javfg/indico
class TrackGroup(DescriptionMixin, db.Model):
    __tablename__ = 'track_groups'
    __table_args__ = {'schema': 'events'}

    is_track_group = True

    possible_render_modes = {RenderMode.markdown}
    default_render_mode = RenderMode.markdown

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    position = db.Column(db.Integer, nullable=False, default=get_next_position)
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         index=True,
                         nullable=False)
    event = db.relationship('Event',
                            lazy=True,
                            backref=db.backref('track_groups',
                                               cascade='all, delete-orphan',
                                               lazy=True,
                                               order_by=id))

    # relationship backrefs:
    # - tracks (Track.track_group)

    @locator_property
    def locator(self):
        return dict(self.event.locator, track_group_id=self.id)

    def __repr__(self):
        return format_repr(self, 'id', _text=text_to_repr(self.title))
示例#16
0
文件: persons.py 项目: qroques/indico
class SessionBlockPersonLink(PersonLinkBase):
    """Association between EventPerson and SessionBlock.

    Also known as a 'session convener'
    """

    __tablename__ = 'session_block_person_links'
    __auto_table_args = {'schema': 'events'}
    person_link_backref_name = 'session_block_links'
    person_link_unique_columns = ('session_block_id', )
    object_relationship_name = 'session_block'

    session_block_id = db.Column(db.Integer,
                                 db.ForeignKey('events.session_blocks.id'),
                                 index=True,
                                 nullable=False)

    # relationship backrefs:
    # - session_block (SessionBlock.person_links)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'person_id',
                           'session_block_id',
                           _text=self.full_name)
示例#17
0
 def ip_network_group_id(cls):
     if not cls.allow_networks:
         return
     return db.Column(db.Integer,
                      db.ForeignKey('indico.ip_network_groups.id'),
                      nullable=True,
                      index=True)
示例#18
0
 def local_group_id(cls):
     return db.Column(
         db.Integer,
         db.ForeignKey('users.groups.id'),
         nullable=True,
         index=True
     )
示例#19
0
class Chatroom(db.Model):
    __tablename__ = 'chatrooms'
    __table_args__ = (db.UniqueConstraint('jid_node', 'custom_server'), {
        'schema': 'plugin_chat'
    })

    #: Chatroom ID
    id = db.Column(db.Integer, primary_key=True)
    #: Node of the chatroom's JID (the part before `@domain`)
    jid_node = db.Column(db.String, nullable=False)
    #: Name of the chatroom
    name = db.Column(db.String, nullable=False)
    #: Description of the chatroom
    description = db.Column(db.Text, nullable=False, default='')
    #: Password to join the room
    password = db.Column(db.String, nullable=False, default='')
    #: Custom Jabber MUC server hostname
    custom_server = db.Column(db.String, nullable=False, default='')
    #: ID of the creator
    created_by_id = db.Column(db.Integer,
                              db.ForeignKey('users.users.id'),
                              index=True,
                              nullable=False)
    #: Creation timestamp of the chatroom
    created_dt = db.Column(UTCDateTime, nullable=False, default=now_utc)
    #: Modification timestamp of the chatroom
    modified_dt = db.Column(UTCDateTime)

    #: The user who created the chatroom
    created_by_user = db.relationship('User',
                                      lazy=True,
                                      backref=db.backref('chatrooms',
                                                         lazy='dynamic'))

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

    @property
    def server(self):
        """The server name of the chatroom.

        Usually the default one unless a custom one is set.
        """
        from indico_chat.plugin import ChatPlugin

        return self.custom_server or ChatPlugin.settings.get('muc_server')

    @property
    def jid(self):
        return '{}@{}'.format(self.jid_node, self.server)

    @return_ascii
    def __repr__(self):
        server = self.server
        if self.custom_server:
            server = '!' + server
        return '<Chatroom({}, {}, {}, {})>'.format(self.id, self.name,
                                                   self.jid_node, server)
示例#20
0
class AbstractReviewQuestion(db.Model):
    __tablename__ = 'abstract_review_questions'
    __table_args__ = {'schema': 'event_abstracts'}

    id = db.Column(db.Integer, primary_key=True)
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         index=True,
                         nullable=False)
    text = db.Column(db.Text, nullable=False)
    no_score = db.Column(db.Boolean, nullable=False, default=False)
    position = db.Column(db.Integer,
                         nullable=False,
                         default=_get_next_position)
    is_deleted = db.Column(db.Boolean, nullable=False, default=False)
    event_new = db.relationship(
        'Event',
        lazy=True,
        backref=db.backref(
            'abstract_review_questions',
            primaryjoin=
            '(AbstractReviewQuestion.event_id == Event.id) & ~AbstractReviewQuestion.is_deleted',
            order_by=position,
            cascade='all, delete-orphan',
            lazy=True))

    # relationship backrefs:
    # - ratings (AbstractReviewRating.question)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'event_id',
                           no_score=False,
                           is_deleted=False,
                           _text=self.text)

    def get_review_rating(self, review, allow_create=False):
        """Get the rating given in particular review.

        :param review: the review object
        :param allow_create: if there is not rating for that review a new one is created
        """
        results = [
            rating for rating in review.ratings if rating.question == self
        ]
        rating = results[0] if results else None
        if rating is None and allow_create:
            rating = AbstractReviewRating(question=self, review=review)
        return rating
class ZoomMeeting(db.Model):
    __tablename__ = 'zoom_meetings'
    __table_args__ = {'schema': 'plugin_vc_zoom'}

    #: ID of the videoconference room
    vc_room_id = db.Column(db.Integer,
                           db.ForeignKey('events.vc_rooms.id'),
                           primary_key=True)
    meeting = db.Column(db.BigInteger, index=True)
    url_zoom = db.Column(db.Text, index=True, nullable=False)
    owned_by_id = db.Column(db.Integer,
                            db.ForeignKey('users.users.id'),
                            index=True,
                            nullable=False)
    vc_room = db.relationship('VCRoom',
                              lazy=False,
                              backref=db.backref('zoom_meeting',
                                                 cascade='all, delete-orphan',
                                                 uselist=False,
                                                 lazy=False))

    #: The user who owns the Zoom room
    owned_by_user = db.relationship('User',
                                    lazy=True,
                                    backref=db.backref('vc_rooms_zoom',
                                                       lazy='dynamic'))

    @property
    def join_url(self):
        from indico_vc_zoom.plugin import ZoomPlugin
        url = self.vc_room.data['url']
        return url

    @return_ascii
    def __repr__(self):
        return '<ZoomMeeting({}, {}, {})>'.format(self.vc_room, self.meeting,
                                                  self.owned_by_user)
class CERNAccessRequest(db.Model):
    __tablename__ = 'access_requests'
    __table_args__ = {'schema': 'plugin_cern_access'}

    registration_id = db.Column(
        db.ForeignKey('event_registration.registrations.id'), primary_key=True)
    request_state = db.Column(PyIntEnum(CERNAccessRequestState),
                              nullable=False,
                              default=CERNAccessRequestState.not_requested)
    reservation_code = db.Column(db.String, nullable=False)
    birth_date = db.Column(db.Date, nullable=True)
    nationality = db.Column(db.String, nullable=True)
    birth_place = db.Column(db.String, nullable=True)
    license_plate = db.Column(db.String, nullable=True)

    registration = db.relationship('Registration',
                                   uselist=False,
                                   lazy=True,
                                   backref=db.backref('cern_access_request',
                                                      uselist=False,
                                                      lazy=False))

    @hybrid_property
    def is_not_requested(self):
        return self.request_state == CERNAccessRequestState.not_requested

    @hybrid_property
    def is_withdrawn(self):
        return self.request_state == CERNAccessRequestState.withdrawn

    @hybrid_property
    def is_active(self):
        return self.request_state == CERNAccessRequestState.active

    @hybrid_property
    def has_identity_info(self):
        return bool(self.birth_place) and bool(
            self.nationality) and self.birth_date is not None

    @has_identity_info.expression
    def has_identity_info(cls):
        return cls.birth_place.isnot(None) & cls.nationality.isnot(
            None) & cls.birth_date.isnot(None)

    def clear_identity_data(self):
        self.birth_date = None
        self.nationality = None
        self.birth_place = None
        self.license_plate = None
示例#23
0
class ContributionPersonLink(PersonLinkBase):
    """Association between EventPerson and Contribution."""

    __tablename__ = 'contribution_person_links'
    __auto_table_args = {'schema': 'events'}
    person_link_backref_name = 'contribution_links'
    person_link_unique_columns = ('contribution_id',)
    object_relationship_name = 'contribution'

    contribution_id = db.Column(
        db.Integer,
        db.ForeignKey('events.contributions.id'),
        index=True,
        nullable=False
    )
    is_speaker = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    author_type = db.Column(
        PyIntEnum(AuthorType),
        nullable=False,
        default=AuthorType.none
    )

    # relationship backrefs:
    # - contribution (Contribution.person_links)

    @property
    def is_submitter(self):
        if not self.contribution:
            raise Exception("No contribution to check submission rights against")
        return self.person.has_role('submit', self.contribution)

    @property
    def is_author(self):
        return self.author_type != AuthorType.none

    @locator_property
    def locator(self):
        return dict(self.contribution.locator, person_id=self.id)

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'person_id', 'contribution_id', is_speaker=False, author_type=AuthorType.none,
                           _text=self.full_name)
示例#24
0
class Foo(db.Model):
    __tablename__ = 'foo'
    __table_args__ = {'schema': 'plugin_example'}

    id = db.Column(db.Integer, primary_key=True)
    bar = db.Column(db.String, default='')
    location_id = db.Column(db.Integer,
                            db.ForeignKey('roombooking.locations.id'),
                            nullable=False)
    location = db.relationship(
        'Location',
        backref=db.backref('example_foo',
                           cascade='all, delete-orphan',
                           lazy='dynamic'),
    )

    @return_ascii
    def __repr__(self):
        return u'<Foo({}, {}, {})>'.format(self.id, self.bar, self.location)
class CERNAccessRequestRegForm(db.Model):
    __tablename__ = 'access_request_regforms'
    __table_args__ = {'schema': 'plugin_cern_access'}

    form_id = db.Column(db.ForeignKey('event_registration.forms.id'),
                        primary_key=True)
    request_state = db.Column(PyIntEnum(CERNAccessRequestState),
                              nullable=False,
                              default=CERNAccessRequestState.not_requested)

    registration_form = db.relationship('RegistrationForm',
                                        uselist=False,
                                        lazy=False,
                                        backref=db.backref(
                                            'cern_access_request',
                                            uselist=False))

    @hybrid_property
    def is_active(self):
        return self.request_state != CERNAccessRequestState.withdrawn
示例#26
0
class Event(SearchableTitleMixin, DescriptionMixin, LocationMixin,
            ProtectionManagersMixin, AttachedItemsMixin, AttachedNotesMixin,
            PersonLinkDataMixin, db.Model):
    """An Indico event

    This model contains the most basic information related to an event.

    Note that the ACL is currently only used for managers but not for
    view access!
    """
    __tablename__ = 'events'
    disallowed_protection_modes = frozenset()
    inheriting_have_acl = True
    allow_access_key = True
    allow_no_access_contact = True
    location_backref_name = 'events'
    allow_location_inheritance = False
    possible_render_modes = {RenderMode.html}
    default_render_mode = RenderMode.html
    __logging_disabled = False

    ATTACHMENT_FOLDER_ID_COLUMN = 'event_id'

    @strict_classproperty
    @classmethod
    def __auto_table_args(cls):
        return (
            db.Index('ix_events_start_dt_desc', cls.start_dt.desc()),
            db.Index('ix_events_end_dt_desc', cls.end_dt.desc()),
            db.Index('ix_events_not_deleted_category', cls.is_deleted,
                     cls.category_id),
            db.Index('ix_events_not_deleted_category_dates', cls.is_deleted,
                     cls.category_id, cls.start_dt, cls.end_dt),
            db.Index('ix_uq_events_url_shortcut',
                     db.func.lower(cls.url_shortcut),
                     unique=True,
                     postgresql_where=db.text('NOT is_deleted')),
            db.CheckConstraint("category_id IS NOT NULL OR is_deleted",
                               'category_data_set'),
            db.CheckConstraint(
                "(logo IS NULL) = (logo_metadata::text = 'null')",
                'valid_logo'),
            db.CheckConstraint(
                "(stylesheet IS NULL) = (stylesheet_metadata::text = 'null')",
                'valid_stylesheet'),
            db.CheckConstraint("end_dt >= start_dt", 'valid_dates'),
            db.CheckConstraint("url_shortcut != ''", 'url_shortcut_not_empty'),
            db.CheckConstraint("cloned_from_id != id", 'not_cloned_from_self'),
            db.CheckConstraint('visibility IS NULL OR visibility >= 0',
                               'valid_visibility'), {
                                   'schema': 'events'
                               })

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

    #: The ID of the event
    id = db.Column(db.Integer, primary_key=True)
    #: If the event has been deleted
    is_deleted = db.Column(db.Boolean, nullable=False, default=False)
    #: If the event is locked (read-only mode)
    is_locked = db.Column(db.Boolean, nullable=False, default=False)
    #: The ID of the user who created the event
    creator_id = db.Column(db.Integer,
                           db.ForeignKey('users.users.id'),
                           nullable=False,
                           index=True)
    #: The ID of immediate parent category of the event
    category_id = db.Column(db.Integer,
                            db.ForeignKey('categories.categories.id'),
                            nullable=True,
                            index=True)
    #: The ID of the series this events belongs to
    series_id = db.Column(db.Integer,
                          db.ForeignKey('events.series.id'),
                          nullable=True,
                          index=True)
    #: If this event was cloned, the id of the parent event
    cloned_from_id = db.Column(
        db.Integer,
        db.ForeignKey('events.events.id'),
        nullable=True,
        index=True,
    )
    #: The creation date of the event
    created_dt = db.Column(UTCDateTime,
                           nullable=False,
                           index=True,
                           default=now_utc)
    #: The start date of the event
    start_dt = db.Column(UTCDateTime, nullable=False, index=True)
    #: The end date of the event
    end_dt = db.Column(UTCDateTime, nullable=False, index=True)
    #: The timezone of the event
    timezone = db.Column(db.String, nullable=False)
    #: The type of the event
    _type = db.Column('type', PyIntEnum(EventType), nullable=False)
    #: The visibility depth in category overviews
    visibility = db.Column(db.Integer, nullable=True, default=None)
    #: A list of tags/keywords for the event
    keywords = db.Column(
        ARRAY(db.String),
        nullable=False,
        default=[],
    )
    #: The URL shortcut for the event
    url_shortcut = db.Column(db.String, nullable=True)
    #: The metadata of the logo (hash, size, filename, content_type)
    logo_metadata = db.Column(JSON, nullable=False, default=lambda: None)
    #: The logo's raw image data
    logo = db.deferred(db.Column(db.LargeBinary, nullable=True))
    #: The metadata of the stylesheet (hash, size, filename)
    stylesheet_metadata = db.Column(JSON, nullable=False, default=lambda: None)
    #: The stylesheet's raw image data
    stylesheet = db.deferred(db.Column(db.Text, nullable=True))
    #: The ID of the event's default page (conferences only)
    default_page_id = db.Column(db.Integer,
                                db.ForeignKey('events.pages.id'),
                                index=True,
                                nullable=True)
    #: The last user-friendly registration ID
    _last_friendly_registration_id = db.deferred(
        db.Column('last_friendly_registration_id',
                  db.Integer,
                  nullable=False,
                  default=0))
    #: The last user-friendly contribution ID
    _last_friendly_contribution_id = db.deferred(
        db.Column('last_friendly_contribution_id',
                  db.Integer,
                  nullable=False,
                  default=0))
    #: The last user-friendly session ID
    _last_friendly_session_id = db.deferred(
        db.Column('last_friendly_session_id',
                  db.Integer,
                  nullable=False,
                  default=0))

    #: The category containing the event
    category = db.relationship(
        'Category',
        lazy=True,
        backref=db.backref(
            'events',
            primaryjoin=
            '(Category.id == Event.category_id) & ~Event.is_deleted',
            order_by=(start_dt, id),
            lazy=True))
    #: The user who created the event
    creator = db.relationship('User',
                              lazy=True,
                              backref=db.backref('created_events',
                                                 lazy='dynamic'))
    #: The event this one was cloned from
    cloned_from = db.relationship('Event',
                                  lazy=True,
                                  remote_side='Event.id',
                                  backref=db.backref('clones',
                                                     lazy=True,
                                                     order_by=start_dt))
    #: The event's default page (conferences only)
    default_page = db.relationship(
        'EventPage',
        lazy=True,
        foreign_keys=[default_page_id],
        post_update=True,
        # don't use this backref. we just need it so SA properly NULLs
        # this column when deleting the default page
        backref=db.backref('_default_page_of_event', lazy=True))
    #: The ACL entries for the event
    acl_entries = db.relationship('EventPrincipal',
                                  backref='event',
                                  cascade='all, delete-orphan',
                                  collection_class=set)
    #: External references associated with this event
    references = db.relationship('EventReference',
                                 lazy=True,
                                 cascade='all, delete-orphan',
                                 backref=db.backref('event', lazy=True))
    #: Persons associated with this event
    person_links = db.relationship('EventPersonLink',
                                   lazy=True,
                                   cascade='all, delete-orphan',
                                   backref=db.backref('event', lazy=True))
    #: The series this event is part of
    series = db.relationship(
        'EventSeries',
        lazy=True,
        backref=db.backref(
            'events',
            lazy=True,
            order_by=(start_dt, id),
            primaryjoin=
            '(Event.series_id == EventSeries.id) & ~Event.is_deleted',
        ))

    #: Users who can review on all tracks
    global_abstract_reviewers = db.relationship(
        'User',
        secondary='events.track_abstract_reviewers',
        collection_class=set,
        lazy=True,
        backref=db.backref('global_abstract_reviewer_for_events',
                           collection_class=set,
                           lazy=True))

    #: Users who are conveners on all tracks
    global_conveners = db.relationship('User',
                                       secondary='events.track_conveners',
                                       collection_class=set,
                                       lazy=True,
                                       backref=db.backref(
                                           'global_convener_for_events',
                                           collection_class=set,
                                           lazy=True))

    # relationship backrefs:
    # - abstract_email_templates (AbstractEmailTemplate.event)
    # - abstract_review_questions (AbstractReviewQuestion.event)
    # - abstracts (Abstract.event)
    # - agreements (Agreement.event)
    # - all_attachment_folders (AttachmentFolder.event)
    # - all_legacy_attachment_folder_mappings (LegacyAttachmentFolderMapping.event)
    # - all_legacy_attachment_mappings (LegacyAttachmentMapping.event)
    # - all_notes (EventNote.event)
    # - all_vc_room_associations (VCRoomEventAssociation.event)
    # - attachment_folders (AttachmentFolder.linked_event)
    # - clones (Event.cloned_from)
    # - contribution_fields (ContributionField.event)
    # - contribution_types (ContributionType.event)
    # - contributions (Contribution.event)
    # - custom_pages (EventPage.event)
    # - designer_templates (DesignerTemplate.event)
    # - layout_images (ImageFile.event)
    # - legacy_contribution_mappings (LegacyContributionMapping.event)
    # - legacy_mapping (LegacyEventMapping.event)
    # - legacy_session_block_mappings (LegacySessionBlockMapping.event)
    # - legacy_session_mappings (LegacySessionMapping.event)
    # - legacy_subcontribution_mappings (LegacySubContributionMapping.event)
    # - log_entries (EventLogEntry.event)
    # - menu_entries (MenuEntry.event)
    # - note (EventNote.linked_event)
    # - paper_competences (PaperCompetence.event)
    # - paper_review_questions (PaperReviewQuestion.event)
    # - paper_templates (PaperTemplate.event)
    # - persons (EventPerson.event)
    # - registration_forms (RegistrationForm.event)
    # - registrations (Registration.event)
    # - reminders (EventReminder.event)
    # - requests (Request.event)
    # - reservations (Reservation.event)
    # - sessions (Session.event)
    # - settings (EventSetting.event)
    # - settings_principals (EventSettingPrincipal.event)
    # - static_list_links (StaticListLink.event)
    # - static_sites (StaticSite.event)
    # - surveys (Survey.event)
    # - timetable_entries (TimetableEntry.event)
    # - tracks (Track.event)
    # - vc_room_associations (VCRoomEventAssociation.linked_event)

    start_dt_override = _EventSettingProperty(event_core_settings,
                                              'start_dt_override')
    end_dt_override = _EventSettingProperty(event_core_settings,
                                            'end_dt_override')
    organizer_info = _EventSettingProperty(event_core_settings,
                                           'organizer_info')
    additional_info = _EventSettingProperty(event_core_settings,
                                            'additional_info')
    contact_title = _EventSettingProperty(event_contact_settings, 'title')
    contact_emails = _EventSettingProperty(event_contact_settings, 'emails')
    contact_phones = _EventSettingProperty(event_contact_settings, 'phones')

    @classmethod
    def category_chain_overlaps(cls, category_ids):
        """
        Create a filter that checks whether the event has any of the
        provided category ids in its parent chain.

        :param category_ids: A list of category ids or a single
                             category id
        """
        from indico.modules.categories import Category
        if not isinstance(category_ids, (list, tuple, set)):
            category_ids = [category_ids]
        cte = Category.get_tree_cte()
        return (cte.c.id
                == Event.category_id) & cte.c.path.overlap(category_ids)

    @classmethod
    def is_visible_in(cls, category):
        """
        Create a filter that checks whether the event is visible in
        the specified category.
        """
        cte = category.visible_categories_cte
        return (db.exists(db.select([1])).where(
            db.and_(
                cte.c.id == Event.category_id,
                db.or_(Event.visibility.is_(None),
                       Event.visibility > cte.c.level))))

    @property
    @memoize_request
    def as_legacy(self):
        """Return a legacy `Conference` object"""
        from indico.modules.events.legacy import LegacyConference
        return LegacyConference(self)

    @property
    def event(self):
        """Convenience property so all event entities have it"""
        return self

    @property
    def has_logo(self):
        return self.logo_metadata is not None

    @property
    def has_stylesheet(self):
        return self.stylesheet_metadata is not None

    @property
    def theme(self):
        from indico.modules.events.layout import layout_settings, theme_settings
        theme = layout_settings.get(self, 'timetable_theme')
        if theme and theme in theme_settings.get_themes_for(self.type):
            return theme
        else:
            return theme_settings.defaults[self.type]

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

    @property
    def logo_url(self):
        return url_for('event_images.logo_display',
                       self,
                       slug=self.logo_metadata['hash'])

    @property
    def participation_regform(self):
        return next(
            (form
             for form in self.registration_forms if form.is_participation),
            None)

    @property
    @memoize_request
    def published_registrations(self):
        from indico.modules.events.registration.util import get_published_registrations
        return get_published_registrations(self)

    @property
    def protection_parent(self):
        return self.category

    @property
    def start_dt_local(self):
        return self.start_dt.astimezone(self.tzinfo)

    @property
    def end_dt_local(self):
        return self.end_dt.astimezone(self.tzinfo)

    @property
    def start_dt_display(self):
        """
        The 'displayed start dt', which is usually the actual start dt,
        but may be overridden for a conference.
        """
        if self.type_ == EventType.conference and self.start_dt_override:
            return self.start_dt_override
        else:
            return self.start_dt

    @property
    def end_dt_display(self):
        """
        The 'displayed end dt', which is usually the actual end dt,
        but may be overridden for a conference.
        """
        if self.type_ == EventType.conference and self.end_dt_override:
            return self.end_dt_override
        else:
            return self.end_dt

    @property
    def type(self):
        # XXX: this should eventually be replaced with the type_
        # property returning the enum - but there are too many places
        # right now that rely on the type string
        return self.type_.name

    @hybrid_property
    def type_(self):
        return self._type

    @type_.setter
    def type_(self, value):
        old_type = self._type
        self._type = value
        if old_type is not None and old_type != value:
            signals.event.type_changed.send(self, old_type=old_type)

    @property
    def url(self):
        return url_for('events.display', self)

    @property
    def external_url(self):
        return url_for('events.display', self, _external=True)

    @property
    def short_url(self):
        id_ = self.url_shortcut or self.id
        return url_for('events.shorturl', confId=id_)

    @property
    def short_external_url(self):
        id_ = self.url_shortcut or self.id
        return url_for('events.shorturl', confId=id_, _external=True)

    @property
    def tzinfo(self):
        return pytz.timezone(self.timezone)

    @property
    def display_tzinfo(self):
        """The tzinfo of the event as preferred by the current user"""
        return get_display_tz(self, as_timezone=True)

    @property
    @contextmanager
    def logging_disabled(self):
        """Temporarily disables event logging

        This is useful when performing actions e.g. during event
        creation or at other times where adding entries to the event
        log doesn't make sense.
        """
        self.__logging_disabled = True
        try:
            yield
        finally:
            self.__logging_disabled = False

    @hybrid_method
    def happens_between(self, from_dt=None, to_dt=None):
        """Check whether the event takes place within two dates"""
        if from_dt is not None and to_dt is not None:
            # any event that takes place during the specified range
            return overlaps((self.start_dt, self.end_dt), (from_dt, to_dt),
                            inclusive=True)
        elif from_dt is not None:
            # any event that starts on/after the specified date
            return self.start_dt >= from_dt
        elif to_dt is not None:
            # any event that ends on/before the specifed date
            return self.end_dt <= to_dt
        else:
            return True

    @happens_between.expression
    def happens_between(cls, from_dt=None, to_dt=None):
        if from_dt is not None and to_dt is not None:
            # any event that takes place during the specified range
            return db_dates_overlap(cls,
                                    'start_dt',
                                    from_dt,
                                    'end_dt',
                                    to_dt,
                                    inclusive=True)
        elif from_dt is not None:
            # any event that starts on/after the specified date
            return cls.start_dt >= from_dt
        elif to_dt is not None:
            # any event that ends on/before the specifed date
            return cls.end_dt <= to_dt
        else:
            return True

    @hybrid_method
    def starts_between(self, from_dt=None, to_dt=None):
        """Check whether the event starts within two dates"""
        if from_dt is not None and to_dt is not None:
            return from_dt <= self.start_dt <= to_dt
        elif from_dt is not None:
            return self.start_dt >= from_dt
        elif to_dt is not None:
            return self.start_dt <= to_dt
        else:
            return True

    @starts_between.expression
    def starts_between(cls, from_dt=None, to_dt=None):
        if from_dt is not None and to_dt is not None:
            return cls.start_dt.between(from_dt, to_dt)
        elif from_dt is not None:
            return cls.start_dt >= from_dt
        elif to_dt is not None:
            return cls.start_dt <= to_dt
        else:
            return True

    @hybrid_method
    def ends_after(self, dt):
        """Check whether the event ends on/after the specified date"""
        return self.end_dt >= dt if dt is not None else True

    @ends_after.expression
    def ends_after(cls, dt):
        return cls.end_dt >= dt if dt is not None else True

    @hybrid_property
    def duration(self):
        return self.end_dt - self.start_dt

    def can_lock(self, user):
        """Check whether the user can lock/unlock the event"""
        return user and (user.is_admin or user == self.creator
                         or self.category.can_manage(user))

    def get_relative_event_ids(self):
        """Get the first, last, previous and next event IDs.

        Any of those values may be ``None`` if there is no matching
        event or if it would be the current event.

        :return: A dict containing ``first``, ``last``, ``prev`` and ``next``.
        """
        subquery = (select([
            Event.id,
            db.func.first_value(
                Event.id).over(order_by=(Event.start_dt,
                                         Event.id)).label('first'),
            db.func.last_value(Event.id).over(order_by=(Event.start_dt,
                                                        Event.id),
                                              range_=(None,
                                                      None)).label('last'),
            db.func.lag(Event.id).over(order_by=(Event.start_dt,
                                                 Event.id)).label('prev'),
            db.func.lead(Event.id).over(order_by=(Event.start_dt,
                                                  Event.id)).label('next')
        ]).where((Event.category_id == self.category_id)
                 & ~Event.is_deleted).alias())
        rv = (db.session.query(
            subquery.c.first, subquery.c.last, subquery.c.prev,
            subquery.c.next).filter(subquery.c.id == self.id).one()._asdict())
        if rv['first'] == self.id:
            rv['first'] = None
        if rv['last'] == self.id:
            rv['last'] = None
        return rv

    def get_verbose_title(self, show_speakers=False, show_series_pos=False):
        """Get the event title with some additional information

        :param show_speakers: Whether to prefix the title with the
                              speakers of the event.
        :param show_series_pos: Whether to suffix the title with the
                                position and total count in the event's
                                series.
        """
        title = self.title
        if show_speakers and self.person_links:
            speakers = ', '.join(
                sorted([pl.full_name for pl in self.person_links],
                       key=unicode.lower))
            title = '{}, "{}"'.format(speakers, title)
        if show_series_pos and self.series and self.series.show_sequence_in_title:
            title = '{} ({}/{})'.format(title, self.series_pos,
                                        self.series_count)
        return title

    def get_non_inheriting_objects(self):
        """Get a set of child objects that do not inherit protection"""
        return get_non_inheriting_objects(self)

    def get_contribution(self, id_):
        """Get a contribution of the event"""
        return get_related_object(self, 'contributions', {'id': id_})

    def get_session(self, id_=None, friendly_id=None):
        """Get a session of the event"""
        if friendly_id is None and id_ is not None:
            criteria = {'id': id_}
        elif id_ is None and friendly_id is not None:
            criteria = {'friendly_id': friendly_id}
        else:
            raise ValueError('Exactly one kind of id must be specified')
        return get_related_object(self, 'sessions', criteria)

    def get_session_block(self, id_, scheduled_only=False):
        """Get a session block of the event"""
        from indico.modules.events.sessions.models.blocks import SessionBlock
        query = SessionBlock.query.filter(
            SessionBlock.id == id_,
            SessionBlock.session.has(event=self, is_deleted=False))
        if scheduled_only:
            query.filter(SessionBlock.timetable_entry != None)  # noqa
        return query.first()

    def get_allowed_sender_emails(self,
                                  include_current_user=True,
                                  include_creator=True,
                                  include_managers=True,
                                  include_contact=True,
                                  include_chairs=True,
                                  extra=None):
        """
        Return the emails of people who can be used as senders (or
        rather Reply-to contacts) in emails sent from within an event.

        :param include_current_user: Whether to include the email of
                                     the currently logged-in user
        :param include_creator: Whether to include the email of the
                                event creator
        :param include_managers: Whether to include the email of all
                                 event managers
        :param include_contact: Whether to include the "event contact"
                                emails
        :param include_chairs: Whether to include the emails of event
                               chairpersons (or lecture speakers)
        :param extra: An email address that is always included, even
                      if it is not in any of the included lists.
        :return: An OrderedDict mapping emails to pretty names
        """
        emails = {}
        # Contact/Support
        if include_contact:
            for email in self.contact_emails:
                emails[email] = self.contact_title
        # Current user
        if include_current_user and has_request_context() and session.user:
            emails[session.user.email] = session.user.full_name
        # Creator
        if include_creator:
            emails[self.creator.email] = self.creator.full_name
        # Managers
        if include_managers:
            emails.update((p.principal.email, p.principal.full_name)
                          for p in self.acl_entries
                          if p.type == PrincipalType.user and p.full_access)
        # Chairs
        if include_chairs:
            emails.update((pl.email, pl.full_name) for pl in self.person_links
                          if pl.email)
        # Extra email (e.g. the current value in an object from the DB)
        if extra:
            emails.setdefault(extra, extra)
        # Sanitize and format emails
        emails = {
            to_unicode(email.strip().lower()):
            '{} <{}>'.format(to_unicode(name), to_unicode(email))
            for email, name in emails.iteritems() if email and email.strip()
        }
        own_email = session.user.email if has_request_context(
        ) and session.user else None
        return OrderedDict(
            sorted(emails.items(),
                   key=lambda x: (x[0] != own_email, x[1].lower())))

    @memoize_request
    def has_feature(self, feature):
        """Checks if a feature is enabled for the event"""
        from indico.modules.events.features.util import is_feature_enabled
        return is_feature_enabled(self, feature)

    @property
    @memoize_request
    def scheduled_notes(self):
        from indico.modules.events.notes.util import get_scheduled_notes
        return get_scheduled_notes(self)

    def log(self,
            realm,
            kind,
            module,
            summary,
            user=None,
            type_='simple',
            data=None):
        """Creates a new log entry for the event

        :param realm: A value from :class:`.EventLogRealm` indicating
                      the realm of the action.
        :param kind: A value from :class:`.EventLogKind` indicating
                     the kind of the action that was performed.
        :param module: A human-friendly string describing the module
                       related to the action.
        :param summary: A one-line summary describing the logged action.
        :param user: The user who performed the action.
        :param type_: The type of the log entry. This is used for custom
                      rendering of the log message/data
        :param data: JSON-serializable data specific to the log type.

        In most cases the ``simple`` log type is fine. For this type,
        any items from data will be shown in the detailed view of the
        log entry.  You may either use a dict (which will be sorted)
        alphabetically or a list of ``key, value`` pairs which will
        be displayed in the given order.
        """
        if self.__logging_disabled:
            return
        entry = EventLogEntry(user=user,
                              realm=realm,
                              kind=kind,
                              module=module,
                              type=type_,
                              summary=summary,
                              data=data or {})
        self.log_entries.append(entry)

    def get_contribution_field(self, field_id):
        return next((v for v in self.contribution_fields if v.id == field_id),
                    '')

    def move_start_dt(self, start_dt):
        """Set event start_dt and adjust its timetable entries"""
        diff = start_dt - self.start_dt
        for entry in self.timetable_entries.filter(
                TimetableEntry.parent_id.is_(None)):
            new_dt = entry.start_dt + diff
            entry.move(new_dt)
        self.start_dt = start_dt

    def iter_days(self, tzinfo=None):
        start_dt = self.start_dt
        end_dt = self.end_dt
        if tzinfo:
            start_dt = start_dt.astimezone(tzinfo)
            end_dt = end_dt.astimezone(tzinfo)
        duration = (end_dt - start_dt).days
        for offset in xrange(duration + 1):
            yield (start_dt + timedelta(days=offset)).date()

    def preload_all_acl_entries(self):
        db.m.Contribution.preload_acl_entries(self)
        db.m.Session.preload_acl_entries(self)

    def move(self, category):
        old_category = self.category
        self.category = category
        db.session.flush()
        signals.event.moved.send(self, old_parent=old_category)

    def delete(self, reason, user=None):
        from indico.modules.events import logger, EventLogRealm, EventLogKind
        self.is_deleted = True
        signals.event.deleted.send(self, user=user)
        db.session.flush()
        logger.info('Event %r deleted [%s]', self, reason)
        self.log(EventLogRealm.event,
                 EventLogKind.negative,
                 'Event',
                 'Event deleted',
                 user,
                 data={'Reason': reason})

    @property
    @memoize_request
    def cfa(self):
        from indico.modules.events.abstracts.models.call_for_abstracts import CallForAbstracts
        return CallForAbstracts(self)

    @property
    @memoize_request
    def cfp(self):
        from indico.modules.events.papers.models.call_for_papers import CallForPapers
        return CallForPapers(self)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'start_dt',
                           'end_dt',
                           is_deleted=False,
                           is_locked=False,
                           _text=text_to_repr(self.title, max_length=75))
示例#27
0
class EventPerson(PersonMixin, db.Model):
    """A person inside an event, e.g. a speaker/author etc."""

    __tablename__ = 'persons'
    __table_args__ = (db.UniqueConstraint('event_id', 'user_id'),
                      db.CheckConstraint('email = lower(email)',
                                         'lowercase_email'),
                      db.Index(None,
                               'event_id',
                               'email',
                               unique=True,
                               postgresql_where=db.text("email != ''")), {
                                   'schema': 'events'
                               })

    id = db.Column(db.Integer, primary_key=True)
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         nullable=False,
                         index=True)
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        nullable=True,
                        index=True)
    first_name = db.Column(db.String, nullable=False, default='')
    last_name = db.Column(db.String, nullable=False)
    email = db.Column(db.String, nullable=False, index=True, default='')
    # the title of the user - you usually want the `title` property!
    _title = db.Column('title',
                       PyIntEnum(UserTitle),
                       nullable=False,
                       default=UserTitle.none)
    affiliation = db.Column(db.String, nullable=False, default='')
    address = db.Column(db.Text, nullable=False, default='')
    phone = db.Column(db.String, nullable=False, default='')
    invited_dt = db.Column(UTCDateTime, nullable=True)
    is_untrusted = db.Column(db.Boolean, nullable=False, default=False)

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

    # relationship backrefs:
    # - abstract_links (AbstractPersonLink.person)
    # - contribution_links (ContributionPersonLink.person)
    # - event_links (EventPersonLink.person)
    # - session_block_links (SessionBlockPersonLink.person)
    # - subcontribution_links (SubContributionPersonLink.person)

    @locator_property
    def locator(self):
        return dict(self.event.locator, person_id=self.id)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           is_untrusted=False,
                           _text=self.full_name)

    @property
    def principal(self):
        if self.user is not None:
            return self.user
        elif self.email:
            return EmailPrincipal(self.email)
        return None

    @classmethod
    def create_from_user(cls, user, event=None, is_untrusted=False):
        return EventPerson(user=user,
                           event=event,
                           first_name=user.first_name,
                           last_name=user.last_name,
                           email=user.email,
                           affiliation=user.affiliation,
                           address=user.address,
                           phone=user.phone,
                           is_untrusted=is_untrusted)

    @classmethod
    def for_user(cls, user, event=None, is_untrusted=False):
        """Return EventPerson for a matching User in Event creating if needed"""
        person = event.persons.filter_by(user=user).first() if event else None
        return person or cls.create_from_user(
            user, event, is_untrusted=is_untrusted)

    @classmethod
    def merge_users(cls, target, source):
        """Merge the EventPersons of two users.

        :param target: The target user of the merge
        :param source: The user that is being merged into `target`
        """
        existing_persons = {ep.event_id: ep for ep in target.event_persons}
        for event_person in source.event_persons:
            existing = existing_persons.get(event_person.event_id)
            if existing is None:
                event_person.user = target
            else:
                existing.merge_person_info(event_person)
                db.session.delete(event_person)
        db.session.flush()

    @classmethod
    def link_user_by_email(cls, user):
        """
        Links all email-based persons matching the user's
        email addresses with the user.

        :param user: A User object.
        """
        from indico.modules.events.models.events import Event
        query = (cls.query.join(EventPerson.event).filter(
            ~Event.is_deleted, cls.email.in_(user.all_emails),
            cls.user_id.is_(None)))
        for event_person in query:
            existing = (cls.query.filter_by(
                user_id=user.id, event_id=event_person.event_id).one_or_none())
            if existing is None:
                event_person.user = user
            else:
                existing.merge_person_info(event_person)
                db.session.delete(event_person)
        db.session.flush()

    @no_autoflush
    def merge_person_info(self, other):
        from indico.modules.events.contributions.models.persons import AuthorType
        for column_name in {
                '_title', 'affiliation', 'address', 'phone', 'first_name',
                'last_name'
        }:
            value = getattr(self, column_name) or getattr(other, column_name)
            setattr(self, column_name, value)

        for event_link in other.event_links:
            existing_event_link = next(
                (link for link in self.event_links
                 if link.event_id == event_link.event_id), None)
            if existing_event_link is None:
                event_link.person = self
            else:
                other.event_links.remove(event_link)

        for abstract_link in other.abstract_links:
            existing_abstract_link = next(
                (link for link in self.abstract_links
                 if link.abstract_id == abstract_link.abstract_id), None)

            if existing_abstract_link is None:
                abstract_link.person = self
            else:
                existing_abstract_link.is_speaker |= abstract_link.is_speaker
                existing_abstract_link.author_type = AuthorType.get_highest(
                    existing_abstract_link.author_type,
                    abstract_link.author_type)
                other.abstract_links.remove(abstract_link)

        for contribution_link in other.contribution_links:
            existing_contribution_link = next(
                (link for link in self.contribution_links
                 if link.contribution_id == contribution_link.contribution_id),
                None)

            if existing_contribution_link is None:
                contribution_link.person = self
            else:
                existing_contribution_link.is_speaker |= contribution_link.is_speaker
                existing_contribution_link.author_type = AuthorType.get_highest(
                    existing_contribution_link.author_type,
                    contribution_link.author_type)
                other.contribution_links.remove(contribution_link)

        for subcontribution_link in other.subcontribution_links:
            existing_subcontribution_link = next(
                (link for link in self.subcontribution_links
                 if link.subcontribution_id ==
                 subcontribution_link.subcontribution_id), None)
            if existing_subcontribution_link is None:
                subcontribution_link.person = self
            else:
                other.subcontribution_links.remove(subcontribution_link)

        for session_block_link in other.session_block_links:
            existing_session_block_link = next(
                (link
                 for link in self.session_block_links if link.session_block_id
                 == session_block_link.session_block_id), None)
            if existing_session_block_link is None:
                session_block_link.person = self
            else:
                other.session_block_links.remove(session_block_link)

        db.session.flush()

    def has_role(self, role, obj):
        """Whether the person has a role in the ACL list of a given object"""
        principals = [
            x for x in obj.acl_entries
            if x.has_management_permission(role, explicit=True)
        ]
        return any(
            x for x in principals
            if ((self.user_id is not None and self.user_id == x.user_id) or (
                self.email is not None and self.email == x.email)))
示例#28
0
class Room(versioned_cache(_cache, 'id'), db.Model, Serializer):
    __tablename__ = 'rooms'
    __table_args__ = (db.UniqueConstraint('id', 'location_id'),  # useless but needed for the LocationMixin fkey
                      {'schema': 'roombooking'})

    __public__ = [
        'id', 'name', 'location_name', 'floor', 'number', 'building',
        'booking_url', 'capacity', 'comments', 'owner_id', 'details_url',
        'large_photo_url', 'small_photo_url', 'has_photo', 'is_active',
        'is_reservable', 'is_auto_confirm', 'marker_description', 'kind',
        'booking_limit_days'
    ]

    __public_exhaustive__ = __public__ + [
        'has_webcast_recording', 'has_vc', 'has_projector', 'is_public', 'has_booking_groups'
    ]

    __calendar_public__ = [
        'id', 'building', 'name', 'floor', 'number', 'kind', 'booking_url', 'details_url', 'location_name',
        'max_advance_days'
    ]

    __api_public__ = (
        'id', 'building', 'name', 'floor', 'longitude', 'latitude', ('number', 'roomNr'), ('location_name', 'location'),
        ('full_name', 'fullName'), ('booking_url', 'bookingUrl')
    )

    __api_minimal_public__ = (
        'id', ('full_name', 'fullName')
    )

    id = db.Column(
        db.Integer,
        primary_key=True
    )
    location_id = db.Column(
        db.Integer,
        db.ForeignKey('roombooking.locations.id'),
        nullable=False
    )
    photo_id = db.Column(
        db.Integer,
        db.ForeignKey('roombooking.photos.id')
    )
    name = db.Column(
        db.String,
        nullable=False
    )
    site = db.Column(
        db.String,
        default=''
    )
    division = db.Column(
        db.String
    )
    building = db.Column(
        db.String,
        nullable=False
    )
    floor = db.Column(
        db.String,
        default='',
        nullable=False
    )
    number = db.Column(
        db.String,
        default='',
        nullable=False
    )
    notification_before_days = db.Column(
        db.Integer
    )
    notification_before_days_weekly = db.Column(
        db.Integer
    )
    notification_before_days_monthly = db.Column(
        db.Integer
    )
    notification_for_assistance = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    reservations_need_confirmation = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    notifications_enabled = db.Column(
        db.Boolean,
        nullable=False,
        default=True
    )
    telephone = db.Column(
        db.String
    )
    key_location = db.Column(
        db.String
    )
    capacity = db.Column(
        db.Integer,
        default=20
    )
    surface_area = db.Column(
        db.Integer
    )
    latitude = db.Column(
        db.String
    )
    longitude = db.Column(
        db.String
    )
    comments = db.Column(
        db.String
    )
    owner_id = db.Column(
        db.Integer,
        db.ForeignKey('users.users.id'),
        index=True,
        nullable=False
    )
    is_active = db.Column(
        db.Boolean,
        nullable=False,
        default=True,
        index=True
    )
    is_reservable = db.Column(
        db.Boolean,
        nullable=False,
        default=True
    )
    max_advance_days = db.Column(
        db.Integer
    )
    booking_limit_days = db.Column(
        db.Integer
    )

    attributes = db.relationship(
        'RoomAttributeAssociation',
        backref='room',
        cascade='all, delete-orphan',
        lazy='dynamic'
    )

    blocked_rooms = db.relationship(
        'BlockedRoom',
        backref='room',
        cascade='all, delete-orphan',
        lazy='dynamic'
    )

    bookable_hours = db.relationship(
        'BookableHours',
        backref='room',
        order_by=BookableHours.start_time,
        cascade='all, delete-orphan',
        lazy='dynamic'
    )

    available_equipment = db.relationship(
        'EquipmentType',
        secondary=RoomEquipmentAssociation,
        backref='rooms',
        lazy='dynamic'
    )

    nonbookable_periods = db.relationship(
        'NonBookablePeriod',
        backref='room',
        order_by=NonBookablePeriod.end_dt.desc(),
        cascade='all, delete-orphan',
        lazy='dynamic'
    )

    photo = db.relationship(
        'Photo',
        backref='room',
        cascade='all, delete-orphan',
        single_parent=True,
        lazy=True
    )

    reservations = db.relationship(
        'Reservation',
        backref='room',
        cascade='all, delete-orphan',
        lazy='dynamic'
    )

    #: The owner of the room. If the room has the `manager-group`
    #: attribute set, any users in that group are also considered
    #: owners when it comes to management privileges.
    #: Use :meth:`is_owned_by` for ownership checks that should
    #: also check against the management group.
    owner = db.relationship(
        'User',
        # subquery load since a normal joinedload breaks `get_with_data`
        lazy='subquery',
        backref=db.backref(
            'owned_rooms',
            lazy='dynamic'
        )
    )

    # relationship backrefs:
    # - breaks (Break.own_room)
    # - contributions (Contribution.own_room)
    # - events (Event.own_room)
    # - location (Location.rooms)
    # - session_blocks (SessionBlock.own_room)
    # - sessions (Session.own_room)

    @hybrid_property
    def is_auto_confirm(self):
        return not self.reservations_need_confirmation

    @is_auto_confirm.expression
    def is_auto_confirm(self):
        return ~self.reservations_need_confirmation

    @property
    def booking_url(self):
        if self.id is None:
            return None
        return url_for('rooms.room_book', self)

    @property
    def details_url(self):
        if self.id is None:
            return None
        return url_for('rooms.roomBooking-roomDetails', self)

    @property
    def large_photo_url(self):
        if self.id is None:
            return None
        return url_for('rooms.photo', self, size='large')

    @property
    def small_photo_url(self):
        if self.id is None:
            return None
        return url_for('rooms.photo', self, size='small')

    @property
    def map_url(self):
        if self.location.map_url_template:
            return self.location.map_url_template.format(
                building=self.building,
                floor=self.floor,
                number=self.number
            )
        else:
            return None

    @property
    def has_photo(self):
        return self.photo_id is not None

    @property
    def full_name(self):
        if self.has_special_name:
            return u'{} - {}'.format(self.generate_name(), self.name)
        else:
            return u'{}'.format(self.generate_name())

    @property
    def has_special_name(self):
        return self.name and self.name != self.generate_name()

    @property
    @cached(_cache)
    def has_booking_groups(self):
        return self.has_attribute('allowed-booking-group')

    @property
    @cached(_cache)
    def has_projector(self):
        return self.has_equipment(u'Computer Projector', u'Video projector 4:3', u'Video projector 16:9')

    @property
    @cached(_cache)
    def has_webcast_recording(self):
        return self.has_equipment('Webcast/Recording')

    @property
    @cached(_cache)
    def has_vc(self):
        return self.has_equipment('Video conference')

    @property
    @cached(_cache)
    def is_public(self):
        return self.is_reservable and not self.has_booking_groups

    @property
    def kind(self):
        if not self.is_reservable or self.has_booking_groups:
            return 'privateRoom'
        elif self.reservations_need_confirmation:
            return 'moderatedRoom'
        else:
            return 'basicRoom'

    @property
    def location_name(self):
        return self.location.name

    @property
    def marker_description(self):
        infos = []

        infos.append(u'{capacity} {label}'.format(capacity=self.capacity,
                                                  label=_(u'person') if self.capacity == 1 else _(u'people')))
        infos.append(_(u'public') if self.is_public else _(u'private'))
        infos.append(_(u'auto-confirmation') if self.is_auto_confirm else _(u'needs confirmation'))
        if self.has_vc:
            infos.append(_(u'videoconference'))

        return u', '.join(map(unicode, infos))

    @property
    def manager_emails(self):
        manager_group = self.get_attribute_value('manager-group')
        if not manager_group:
            return set()
        group = GroupProxy.get_named_default_group(manager_group)
        return {u.email for u in group.get_members()}

    @property
    def notification_emails(self):
        return set(filter(None, map(unicode.strip, self.get_attribute_value(u'notification-email', u'').split(u','))))

    @return_ascii
    def __repr__(self):
        return u'<Room({0}, {1}, {2})>'.format(
            self.id,
            self.location_id,
            self.name
        )

    @cached(_cache)
    def has_equipment(self, *names):
        return self.available_equipment.filter(EquipmentType.name.in_(names)).count() > 0

    def find_available_vc_equipment(self):
        vc_equipment = (self.available_equipment
                        .correlate(Room)
                        .with_entities(EquipmentType.id)
                        .filter_by(name='Video conference')
                        .as_scalar())
        return self.available_equipment.filter(EquipmentType.parent_id == vc_equipment)

    def get_attribute_by_name(self, attribute_name):
        return (self.attributes
                .join(RoomAttribute)
                .filter(RoomAttribute.name == attribute_name)
                .first())

    def has_attribute(self, attribute_name):
        return self.get_attribute_by_name(attribute_name) is not None

    @cached(_cache)
    def get_attribute_value(self, name, default=None):
        attr = self.get_attribute_by_name(name)
        return attr.value if attr else default

    def set_attribute_value(self, name, value):
        attr = self.get_attribute_by_name(name)
        if attr:
            if value:
                attr.value = value
            else:
                self.attributes.filter(RoomAttributeAssociation.attribute_id == attr.attribute_id) \
                    .delete(synchronize_session='fetch')
        elif value:
            attr = self.location.get_attribute_by_name(name)
            if not attr:
                raise ValueError("Attribute {} not supported in location {}".format(name, self.location_name))
            attr_assoc = RoomAttributeAssociation()
            attr_assoc.value = value
            attr_assoc.attribute = attr
            self.attributes.append(attr_assoc)
        db.session.flush()

    @locator_property
    def locator(self):
        return {'roomLocation': self.location_name, 'roomID': self.id}

    def generate_name(self):
        return u'{}-{}-{}'.format(
            self.building,
            self.floor,
            self.number
        )

    def update_name(self):
        if not self.has_special_name and self.building and self.floor and self.number:
            self.name = self.generate_name()

    @classmethod
    def find_all(cls, *args, **kwargs):
        """Retrieves rooms, sorted by location and full name"""
        rooms = super(Room, cls).find_all(*args, **kwargs)
        rooms.sort(key=lambda r: natural_sort_key(r.location_name + r.full_name))
        return rooms

    @classmethod
    def find_with_attribute(cls, attribute):
        """Search rooms which have a specific attribute"""
        return (Room.query
                .with_entities(Room, RoomAttributeAssociation.value)
                .join(Room.attributes, RoomAttributeAssociation.attribute)
                .filter(RoomAttribute.name == attribute)
                .all())

    @staticmethod
    def get_with_data(*args, **kwargs):
        from indico.modules.rb.models.locations import Location

        only_active = kwargs.pop('only_active', True)
        filters = kwargs.pop('filters', None)
        order = kwargs.pop('order', [Location.name, Room.building, Room.floor, Room.number, Room.name])
        if kwargs:
            raise ValueError('Unexpected kwargs: {}'.format(kwargs))

        query = Room.query
        entities = [Room]

        if 'equipment' in args:
            entities.append(static_array.array_agg(EquipmentType.name))
            query = query.outerjoin(RoomEquipmentAssociation).outerjoin(EquipmentType)
        if 'vc_equipment' in args or 'non_vc_equipment' in args:
            vc_id_subquery = db.session.query(EquipmentType.id) \
                                       .correlate(Room) \
                                       .filter_by(name='Video conference') \
                                       .join(RoomEquipmentAssociation) \
                                       .filter(RoomEquipmentAssociation.c.room_id == Room.id) \
                                       .as_scalar()

            if 'vc_equipment' in args:
                # noinspection PyTypeChecker
                entities.append(static_array.array(
                    db.session.query(EquipmentType.name)
                    .join(RoomEquipmentAssociation)
                    .filter(
                        RoomEquipmentAssociation.c.room_id == Room.id,
                        EquipmentType.parent_id == vc_id_subquery
                    )
                    .order_by(EquipmentType.name)
                    .as_scalar()
                ))
            if 'non_vc_equipment' in args:
                # noinspection PyTypeChecker
                entities.append(static_array.array(
                    db.session.query(EquipmentType.name)
                    .join(RoomEquipmentAssociation)
                    .filter(
                        RoomEquipmentAssociation.c.room_id == Room.id,
                        (EquipmentType.parent_id == None) | (EquipmentType.parent_id != vc_id_subquery)
                    )
                    .order_by(EquipmentType.name)
                    .as_scalar()
                ))

        query = (query.with_entities(*entities)
                 .outerjoin(Location, Location.id == Room.location_id)
                 .group_by(Location.name, Room.id))

        if only_active:
            query = query.filter(Room.is_active)
        if filters:  # pragma: no cover
            query = query.filter(*filters)
        if order:  # pragma: no cover
            query = query.order_by(*order)

        keys = ('room',) + tuple(args)
        return (dict(zip(keys, row if args else [row])) for row in query)

    @classproperty
    @staticmethod
    def max_capacity():
        return db.session.query(db.func.max(Room.capacity)).scalar() or 0

    @staticmethod
    def filter_available(start_dt, end_dt, repetition, include_pre_bookings=True, include_pending_blockings=True):
        """Returns a SQLAlchemy filter criterion ensuring that the room is available during the given time."""
        # Check availability against reservation occurrences
        dummy_occurrences = ReservationOccurrence.create_series(start_dt, end_dt, repetition)
        overlap_criteria = ReservationOccurrence.filter_overlap(dummy_occurrences)
        reservation_criteria = [Reservation.room_id == Room.id,
                                ReservationOccurrence.is_valid,
                                overlap_criteria]
        if not include_pre_bookings:
            reservation_criteria.append(Reservation.is_accepted)
        occurrences_filter = Reservation.occurrences.any(and_(*reservation_criteria))
        # Check availability against blockings
        if include_pending_blockings:
            valid_states = (BlockedRoom.State.accepted, BlockedRoom.State.pending)
        else:
            valid_states = (BlockedRoom.State.accepted,)
        blocking_criteria = [BlockedRoom.blocking_id == Blocking.id,
                             BlockedRoom.state.in_(valid_states),
                             Blocking.start_date <= start_dt.date(),
                             Blocking.end_date >= end_dt.date()]
        blockings_filter = Room.blocked_rooms.any(and_(*blocking_criteria))
        return ~occurrences_filter & ~blockings_filter

    @staticmethod
    def find_with_filters(filters, user=None):
        from indico.modules.rb.models.locations import Location

        equipment_count = len(filters.get('available_equipment', ()))
        equipment_subquery = None
        if equipment_count:
            equipment_subquery = (
                db.session.query(RoomEquipmentAssociation)
                .with_entities(func.count(RoomEquipmentAssociation.c.room_id))
                .filter(
                    RoomEquipmentAssociation.c.room_id == Room.id,
                    RoomEquipmentAssociation.c.equipment_id.in_(eq.id for eq in filters['available_equipment'])
                )
                .correlate(Room)
                .as_scalar()
            )

        capacity = filters.get('capacity')
        q = (
            Room.query
            .join(Location.rooms)
            .filter(
                Location.id == filters['location'].id if filters.get('location') else True,
                ((Room.capacity >= (capacity * 0.8)) | (Room.capacity == None)) if capacity else True,
                Room.is_reservable if filters.get('is_only_public') else True,
                Room.is_auto_confirm if filters.get('is_auto_confirm') else True,
                Room.is_active if filters.get('is_only_active', False) else True,
                (equipment_subquery == equipment_count) if equipment_subquery is not None else True)
        )

        if filters.get('available', -1) != -1:
            repetition = RepeatMapping.convert_legacy_repeatability(ast.literal_eval(filters['repeatability']))
            is_available = Room.filter_available(filters['start_dt'], filters['end_dt'], repetition,
                                                 include_pre_bookings=filters.get('include_pre_bookings', True),
                                                 include_pending_blockings=filters.get('include_pending_blockings',
                                                                                       True))
            # Filter the search results
            if filters['available'] == 0:  # booked/unavailable
                q = q.filter(~is_available)
            elif filters['available'] == 1:  # available
                q = q.filter(is_available)
            else:
                raise ValueError('Unexpected availability value')

        free_search_columns = (
            'name', 'site', 'division', 'building', 'floor', 'number', 'telephone', 'key_location', 'comments'
        )
        if filters.get('details'):
            # Attributes are stored JSON-encoded, so we need to JSON-encode the provided string and remove the quotes
            # afterwards since PostgreSQL currently does not expose a function to decode a JSON string:
            # http://www.postgresql.org/message-id/[email protected]
            details = filters['details'].lower()
            details_str = u'%{}%'.format(escape_like(details))
            details_json = u'%{}%'.format(escape_like(json.dumps(details)[1:-1]))
            free_search_criteria = [getattr(Room, c).ilike(details_str) for c in free_search_columns]
            free_search_criteria.append(Room.attributes.any(cast(RoomAttributeAssociation.value, db.String)
                                                            .ilike(details_json)))
            q = q.filter(or_(*free_search_criteria))

        q = q.order_by(Room.capacity)
        rooms = q.all()
        # Apply a bunch of filters which are *much* easier to do here than in SQL!
        if filters.get('is_only_public'):
            # This may trigger additional SQL queries but is_public is cached and doing this check here is *much* easier
            rooms = [r for r in rooms if r.is_public]
        if filters.get('is_only_my_rooms'):
            assert user is not None
            rooms = [r for r in rooms if r.is_owned_by(user)]
        if capacity:
            # Unless it would result in an empty resultset we don't want to show rooms with >20% more capacity
            # than requested. This cannot be done easily in SQL so we do that logic here after the SQL query already
            # weeded out rooms that are too small
            matching_capacity_rooms = [r for r in rooms if r.capacity is None or r.capacity <= capacity * 1.2]
            if matching_capacity_rooms:
                rooms = matching_capacity_rooms
        return rooms

    def has_live_reservations(self):
        return self.reservations.filter_by(
            is_archived=False,
            is_cancelled=False,
            is_rejected=False
        ).count() > 0

    def get_blocked_rooms(self, *dates, **kwargs):
        states = kwargs.get('states', (BlockedRoom.State.accepted,))
        return (self.blocked_rooms
                .join(BlockedRoom.blocking)
                .options(contains_eager(BlockedRoom.blocking))
                .filter(or_(Blocking.is_active_at(d) for d in dates),
                        BlockedRoom.state.in_(states))
                .all())

    @unify_user_args
    def _can_be_booked(self, user, prebook=False, ignore_admin=False):
        if not user or not rb_check_user_access(user):
            return False

        if (not ignore_admin and rb_is_admin(user)) or (self.is_owned_by(user) and self.is_active):
            return True

        if self.is_active and self.is_reservable and (prebook or not self.reservations_need_confirmation):
            group_name = self.get_attribute_value('allowed-booking-group')
            if not group_name or user in GroupProxy.get_named_default_group(group_name):
                return True

        return False

    def can_be_booked(self, user, ignore_admin=False):
        """
        Reservable rooms which does not require pre-booking can be booked by anyone.
        Other rooms - only by their responsibles.
        """
        return self._can_be_booked(user, ignore_admin=ignore_admin)

    def can_be_prebooked(self, user, ignore_admin=False):
        """
        Reservable rooms can be pre-booked by anyone.
        Other rooms - only by their responsibles.
        """
        return self._can_be_booked(user, prebook=True, ignore_admin=ignore_admin)

    def can_be_overridden(self, user):
        if not user:
            return False
        return rb_is_admin(user) or self.is_owned_by(user)

    def can_be_modified(self, user):
        """Only admin can modify rooms."""
        if not user:
            return False
        return rb_is_admin(user)

    def can_be_deleted(self, user):
        return self.can_be_modified(user)

    @unify_user_args
    @cached(_cache)
    def is_owned_by(self, user):
        """Checks if the user is managing the room (owner or manager)"""
        if self.owner == user:
            return True
        manager_group = self.get_attribute_value('manager-group')
        if not manager_group:
            return False
        return user in GroupProxy.get_named_default_group(manager_group)

    @classmethod
    def get_owned_by(cls, user):
        return [room for room in cls.find(is_active=True) if room.is_owned_by(user)]

    @classmethod
    def user_owns_rooms(cls, user):
        return any(room for room in cls.find(is_active=True) if room.is_owned_by(user))

    def check_advance_days(self, end_date, user=None, quiet=False):
        if not self.max_advance_days:
            return True
        if user and (rb_is_admin(user) or self.is_owned_by(user)):
            return True
        advance_days = (end_date - date.today()).days
        ok = advance_days < self.max_advance_days
        if quiet or ok:
            return ok
        else:
            msg = _(u'You cannot book this room more than {} days in advance')
            raise NoReportError(msg.format(self.max_advance_days))

    def check_bookable_hours(self, start_time, end_time, user=None, quiet=False):
        if user and (rb_is_admin(user) or self.is_owned_by(user)):
            return True
        bookable_hours = self.bookable_hours.all()
        if not bookable_hours:
            return True
        for bt in bookable_hours:
            if bt.fits_period(start_time, end_time):
                return True
        if quiet:
            return False
        raise NoReportError(u'Room cannot be booked at this time')
示例#29
0
文件: reviews.py 项目: vintas/indico
class PaperReview(ProposalReviewMixin, RenderModeMixin, db.Model):
    """A paper review, emitted by a layout or content reviewer."""

    possible_render_modes = {RenderMode.markdown}
    default_render_mode = RenderMode.markdown

    revision_attr = 'revision'
    group_attr = 'type'
    group_proxy_cls = PaperTypeProxy

    __tablename__ = 'reviews'
    __table_args__ = (db.UniqueConstraint('revision_id', 'user_id', 'type'), {
        'schema': 'event_paper_reviewing'
    })
    TIMELINE_TYPE = 'review'

    id = db.Column(db.Integer, primary_key=True)
    revision_id = db.Column(
        db.Integer,
        db.ForeignKey('event_paper_reviewing.revisions.id'),
        index=True,
        nullable=False)
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        index=True,
                        nullable=False)
    created_dt = db.Column(
        UTCDateTime,
        nullable=False,
        default=now_utc,
    )
    modified_dt = db.Column(UTCDateTime, nullable=True)
    _comment = db.Column('comment', db.Text, nullable=False, default='')
    type = db.Column(PyIntEnum(PaperReviewType), nullable=False)
    proposed_action = db.Column(PyIntEnum(PaperAction), nullable=False)

    revision = db.relationship('PaperRevision',
                               lazy=True,
                               backref=db.backref('reviews',
                                                  lazy=True,
                                                  order_by=created_dt.desc()))
    user = db.relationship('User',
                           lazy=True,
                           backref=db.backref('paper_reviews', lazy='dynamic'))

    # relationship backrefs:
    # - ratings (PaperReviewRating.review)

    comment = RenderModeMixin.create_hybrid_property('_comment')

    @locator_property
    def locator(self):
        return dict(self.revision.locator, review_id=self.id)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'type',
                           'revision_id',
                           'user_id',
                           proposed_action=None)

    def can_edit(self, user, check_state=False):
        from indico.modules.events.papers.models.revisions import PaperRevisionState
        if user is None:
            return False
        if check_state and self.revision.state != PaperRevisionState.submitted:
            return False
        return self.user == user

    def can_view(self, user):
        if user is None:
            return False
        elif user == self.user:
            return True
        elif self.revision.paper.can_judge(user):
            return True
        return False

    @property
    def visibility(self):
        return PaperCommentVisibility.reviewers

    @property
    def score(self):
        ratings = [
            r for r in self.ratings if not r.question.is_deleted
            and r.question.field_type == 'rating' and r.value is not None
        ]
        if not ratings:
            return None
        return sum(x.value for x in ratings) / len(ratings)
示例#30
0
class AbstractEmailLogEntry(db.Model):
    __tablename__ = 'email_logs'
    __table_args__ = {'schema': 'event_abstracts'}

    id = db.Column(db.Integer, primary_key=True)
    abstract_id = db.Column(db.Integer,
                            db.ForeignKey('event_abstracts.abstracts.id'),
                            index=True,
                            nullable=False)
    email_template_id = db.Column(
        db.Integer,
        db.ForeignKey('event_abstracts.email_templates.id'),
        index=True,
        nullable=True)
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        index=True,
                        nullable=True)
    sent_dt = db.Column(UTCDateTime, nullable=False, default=now_utc)
    recipients = db.Column(ARRAY(db.String), nullable=False)
    subject = db.Column(db.String, nullable=False)
    body = db.Column(db.Text, nullable=False)
    data = db.Column(JSON, nullable=False)

    abstract = db.relationship('Abstract',
                               lazy=True,
                               backref=db.backref('email_logs',
                                                  order_by=sent_dt,
                                                  lazy=True))
    email_template = db.relationship('AbstractEmailTemplate',
                                     lazy=True,
                                     backref=db.backref('logs',
                                                        lazy='dynamic'))
    user = db.relationship('User',
                           lazy=True,
                           backref=db.backref('abstract_email_log_entries',
                                              lazy='dynamic'))

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'abstract_id', _text=self.subject)

    @classmethod
    def create_from_email(cls, email_data, email_tpl, user=None):
        """Create a new log entry from the data used to send an email

        :param email_data: email data as returned from `make_email`
        :param email_tpl: the abstract email template that created the
                          email
        :param user: the user who performed the action causing the
                     notification
        """
        recipients = sorted(email_data['toList'] | email_data['ccList']
                            | email_data['bccList'])
        data = {'template_name': email_tpl.title}
        return cls(email_template=email_tpl,
                   user=user,
                   recipients=recipients,
                   subject=email_data['subject'],
                   body=email_data['body'],
                   data=data)