예제 #1
0
 def __auto_table_args(cls):
     checks = [
         db.CheckConstraint(
             'read_access OR full_access OR array_length(roles, 1) IS NOT NULL',
             'has_privs')
     ]
     if cls.allow_networks:
         # you can match a network acl entry without being logged in.
         # we never want that for anything but simple read access
         checks.append(
             db.CheckConstraint(
                 'type != {} OR (NOT full_access AND array_length(roles, 1) IS NULL)'
                 .format(PrincipalType.network), 'networks_read_only'))
     return tuple(checks)
예제 #2
0
 def __auto_table_args(cls):
     uniques = ()
     if cls.unique_columns:
         uniques = [
             db.Index('ix_uq_{}_user'.format(cls.__tablename__),
                      'user_id',
                      *cls.unique_columns,
                      unique=True,
                      postgresql_where=db.text('type = {}'.format(
                          PrincipalType.user))),
             db.Index('ix_uq_{}_local_group'.format(cls.__tablename__),
                      'local_group_id',
                      *cls.unique_columns,
                      unique=True,
                      postgresql_where=db.text('type = {}'.format(
                          PrincipalType.local_group))),
             db.Index('ix_uq_{}_mp_group'.format(cls.__tablename__),
                      'mp_group_provider',
                      'mp_group_name',
                      *cls.unique_columns,
                      unique=True,
                      postgresql_where=db.text('type = {}'.format(
                          PrincipalType.multipass_group)))
         ]
         if cls.allow_emails:
             uniques.append(
                 db.Index('ix_uq_{}_email'.format(cls.__tablename__),
                          'email',
                          *cls.unique_columns,
                          unique=True,
                          postgresql_where=db.text('type = {}'.format(
                              PrincipalType.email))))
     indexes = [db.Index(None, 'mp_group_provider', 'mp_group_name')]
     checks = [
         _make_check(PrincipalType.user, cls.allow_emails,
                     cls.allow_networks, 'user_id'),
         _make_check(PrincipalType.local_group, cls.allow_emails,
                     cls.allow_networks, 'local_group_id'),
         _make_check(PrincipalType.multipass_group, cls.allow_emails,
                     cls.allow_networks, 'mp_group_provider',
                     'mp_group_name')
     ]
     if cls.allow_emails:
         checks.append(
             _make_check(PrincipalType.email, cls.allow_emails,
                         cls.allow_networks, 'email'))
         checks.append(
             db.CheckConstraint('email IS NULL OR email = lower(email)',
                                'lowercase_email'))
     if cls.allow_networks:
         checks.append(
             _make_check(PrincipalType.network, cls.allow_emails,
                         cls.allow_networks, 'ip_network_group_id'))
     return tuple(uniques + indexes + checks)
예제 #3
0
def _make_check(type_, allow_emails, allow_networks, *cols):
    all_cols = {
        'user_id', 'local_group_id', 'mp_group_provider', 'mp_group_name'
    }
    if allow_emails:
        all_cols.add('email')
    if allow_networks:
        all_cols.add('ip_network_group_id')
    required_cols = all_cols & set(cols)
    forbidden_cols = all_cols - required_cols
    criteria = ['{} IS NULL'.format(col) for col in sorted(forbidden_cols)]
    criteria += ['{} IS NOT NULL'.format(col) for col in sorted(required_cols)]
    condition = 'type != {} OR ({})'.format(type_, ' AND '.join(criteria))
    return db.CheckConstraint(condition, 'valid_{}'.format(type_.name))
예제 #4
0
 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'})
예제 #5
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 fossir.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 fossir.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_role(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)))
from __future__ import unicode_literals

from fossir.core.db.sqlalchemy import db

_track_abstract_reviewers_table = db.Table(
    'track_abstract_reviewers',
    db.metadata,
    db.Column('id', db.Integer, primary_key=True),
    db.Column('user_id',
              db.Integer,
              db.ForeignKey('users.users.id'),
              index=True,
              nullable=False),
    db.Column('event_id',
              db.Integer,
              db.ForeignKey('events.events.id'),
              index=True),
    db.Column('track_id',
              db.Integer,
              db.ForeignKey('events.tracks.id'),
              index=True),
    db.CheckConstraint('(track_id IS NULL) != (event_id IS NULL)',
                       name='track_xor_event_id_null'),
    schema='events')
예제 #7
0
class AbstractReview(ProposalReviewMixin, RenderModeMixin, db.Model):
    """Represents an abstract review, emitted by a reviewer"""

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

    revision_attr = 'abstract'
    group_attr = 'track'

    marshmallow_aliases = {'_comment': 'comment'}

    __tablename__ = 'abstract_reviews'
    __table_args__ = (
        db.UniqueConstraint('abstract_id', 'user_id', 'track_id'),
        db.CheckConstraint(
            "proposed_action = {} OR (proposed_contribution_type_id IS NULL)".
            format(AbstractAction.accept),
            name='prop_contrib_id_only_accepted'),
        db.CheckConstraint(
            "(proposed_action IN ({}, {})) = (proposed_related_abstract_id IS NOT NULL)"
            .format(AbstractAction.mark_as_duplicate, AbstractAction.merge),
            name='prop_abstract_id_only_duplicate_merge'), {
                '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)
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.users.id'),
                        index=True,
                        nullable=False)
    track_id = db.Column(db.Integer,
                         db.ForeignKey('events.tracks.id'),
                         index=True,
                         nullable=True)
    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='')
    proposed_action = db.Column(PyIntEnum(AbstractAction), nullable=False)
    proposed_related_abstract_id = db.Column(
        db.Integer,
        db.ForeignKey('event_abstracts.abstracts.id'),
        index=True,
        nullable=True)
    proposed_contribution_type_id = db.Column(
        db.Integer,
        db.ForeignKey('events.contribution_types.id'),
        nullable=True,
        index=True)
    abstract = db.relationship('Abstract',
                               lazy=True,
                               foreign_keys=abstract_id,
                               backref=db.backref('reviews',
                                                  cascade='all, delete-orphan',
                                                  lazy=True))
    user = db.relationship('User',
                           lazy=True,
                           backref=db.backref('abstract_reviews',
                                              lazy='dynamic'))
    track = db.relationship('Track',
                            lazy=True,
                            foreign_keys=track_id,
                            backref=db.backref('abstract_reviews',
                                               lazy='dynamic'))
    proposed_related_abstract = db.relationship(
        'Abstract',
        lazy=True,
        foreign_keys=proposed_related_abstract_id,
        backref=db.backref('proposed_related_abstract_reviews',
                           lazy='dynamic'))
    proposed_tracks = db.relationship(
        'Track',
        secondary='event_abstracts.proposed_for_tracks',
        lazy=True,
        collection_class=set,
        backref=db.backref('proposed_abstract_reviews',
                           lazy='dynamic',
                           passive_deletes=True))
    proposed_contribution_type = db.relationship('ContributionType',
                                                 lazy=True,
                                                 backref=db.backref(
                                                     'abstract_reviews',
                                                     lazy='dynamic'))

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

    comment = RenderModeMixin.create_hybrid_property('_comment')

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

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

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

    @property
    def score(self):
        ratings = [
            r for r in self.ratings
            if not r.question.no_score and not r.question.is_deleted
        ]
        if not ratings:
            return None
        return sum(x.value for x in ratings) / len(ratings)

    def can_edit(self, user, check_state=False):
        if user is None:
            return False
        if check_state and self.abstract.public_state.name != 'under_review':
            return False
        return self.user == user

    def can_view(self, user):
        if user is None:
            return False
        elif user == self.user:
            return True
        if self.abstract.can_judge(user):
            return True
        else:
            return self.track.can_convene(user)