Exemplo n.º 1
0
class LegacyCategoryMapping(db.Model):
    """Legacy category ID mapping

    Legacy categories have non-numeric IDs which are not supported by
    any new code. This mapping maps them to proper integer IDs to
    avoid breaking things.
    """

    __tablename__ = 'legacy_id_map'
    __table_args__ = {'schema': 'categories'}

    legacy_category_id = db.Column(db.String, primary_key=True, index=True)
    category_id = db.Column(db.Integer,
                            db.ForeignKey('categories.categories.id'),
                            index=True,
                            primary_key=True,
                            autoincrement=False)

    category = db.relationship('Category',
                               lazy=True,
                               backref=db.backref('legacy_mapping',
                                                  uselist=False,
                                                  lazy=True))

    @return_ascii
    def __repr__(self):
        return '<LegacyCategoryMapping({}, {})>'.format(
            self.legacy_category_id, self.category_id)
Exemplo n.º 2
0
class SessionPrincipal(PrincipalRolesMixin, db.Model):
    __tablename__ = 'session_principals'
    principal_backref_name = 'in_session_acls'
    principal_for = 'Session'
    unique_columns = ('session_id', )
    disallowed_protection_modes = frozenset()
    allow_emails = True

    @declared_attr
    def __table_args__(cls):
        return auto_table_args(cls, schema='events')

    #: The ID of the acl entry
    id = db.Column(db.Integer, primary_key=True)
    #: The ID of the associated session
    session_id = db.Column(db.Integer,
                           db.ForeignKey('events.sessions.id'),
                           nullable=False,
                           index=True)

    # relationship backrefs:
    # - session (Session.acl_entries)

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'session_id',
                           'principal',
                           read_access=False,
                           full_access=False,
                           roles=[])
Exemplo n.º 3
0
class ReferenceType(db.Model):
    __tablename__ = 'reference_types'

    @declared_attr
    def __table_args__(cls):
        return (db.Index('ix_uq_reference_types_name_lower',
                         db.func.lower(cls.name),
                         unique=True), {
                             'schema': 'fossir'
                         })

    #: The unique ID of the reference type
    id = db.Column(db.Integer, primary_key=True)
    #: The name of the referenced system
    name = db.Column(db.String, nullable=False)
    #: The scheme used to build an URN for the reference
    scheme = db.Column(db.String, nullable=False, default='')
    #: A URL template to build a link to a referenced entity
    url_template = db.Column(db.String, nullable=False, default='')

    # relationship backrefs:
    # - contribution_references (ContributionReference.reference_type)
    # - event_references (EventReference.reference_type)
    # - subcontribution_references (SubContributionReference.reference_type)

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

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'url_template', _text=self.name)
class SuggestedCategory(db.Model):
    __tablename__ = 'suggested_categories'
    __table_args__ = {'schema': 'users'}

    user_id = db.Column(
        db.Integer,
        db.ForeignKey('users.users.id'),
        primary_key=True,
        index=True,
        autoincrement=False
    )
    category_id = db.Column(
        db.Integer,
        db.ForeignKey('categories.categories.id'),
        primary_key=True,
        index=True,
        autoincrement=False
    )
    is_ignored = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    score = db.Column(
        db.Float,
        nullable=False,
        default=0
    )

    category = db.relationship(
        'Category',
        lazy=False,
        backref=db.backref(
            'suggestions',
            lazy=True,
            cascade='all, delete-orphan'
        )
    )

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

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'user_id', 'category_id', 'score', is_ignored=False)

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

        :param target: The target user of the merge.
        :param source: The user that is being merged into `target`.
        """
        target_suggestions = {x.category: x for x in target.suggested_categories}
        for suggestion in source.suggested_categories:
            new_suggestion = target_suggestions.get(suggestion.category) or cls(user=target,
                                                                                category=suggestion.category)
            new_suggestion.score = max(new_suggestion.score, suggestion.score)
            new_suggestion.is_ignored = new_suggestion.is_ignored or suggestion.is_ignored
        db.session.flush()
Exemplo n.º 5
0
class EquipmentType(db.Model):
    __tablename__ = 'equipment_types'
    __table_args__ = (db.UniqueConstraint('name', 'location_id'), {
        'schema': 'roombooking'
    })

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

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

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

    @return_ascii
    def __repr__(self):
        return u'<EquipmentType({0}, {1}, {2})>'.format(
            self.id, self.name, self.location_id)
Exemplo n.º 6
0
class EventPrincipal(PrincipalRolesMixin, db.Model):
    __tablename__ = 'principals'
    principal_backref_name = 'in_event_acls'
    principal_for = 'Event'
    unique_columns = ('event_id', )
    allow_emails = True
    allow_networks = True

    @declared_attr
    def __table_args__(cls):
        return auto_table_args(cls, schema='events')

    #: The ID of the acl entry
    id = db.Column(db.Integer, primary_key=True)
    #: The ID of the associated event
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         nullable=False,
                         index=True)

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

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'event_id',
                           'principal',
                           read_access=False,
                           full_access=False,
                           roles=[])
Exemplo n.º 7
0
class DesignerImageFile(StoredFileMixin, db.Model):
    __tablename__ = 'designer_image_files'
    __table_args__ = {'schema': 'fossir'}

    # Image files are not version-controlled
    version_of = None

    #: The ID of the file
    id = db.Column(
        db.Integer,
        primary_key=True
    )
    #: The designer template the image belongs to
    template_id = db.Column(
        db.Integer,
        db.ForeignKey('fossir.designer_templates.id'),
        nullable=False,
        index=True
    )

    template = db.relationship(
        'DesignerTemplate',
        lazy=False,
        foreign_keys=template_id,
        backref=db.backref(
            'images',
            cascade='all, delete-orphan',
            lazy=True
        )
    )

    @property
    def download_url(self):
        return url_for('designer.download_image', self)

    @property
    def locator(self):
        return dict(self.template.locator, image_id=self.id, filename=self.filename)

    def _build_storage_path(self):
        path_segments = ['designer_templates', strict_unicode(self.template.id), 'images']
        self.assign_id()
        filename = '{}-{}'.format(self.id, self.filename)
        path = posixpath.join(*(path_segments + [filename]))
        return config.ATTACHMENT_STORAGE, path

    @return_ascii
    def __repr__(self):
        return '<DesignerImageFile({}, {}, {}, {})>'.format(
            self.id,
            self.template_id,
            self.filename,
            self.content_type
        )
Exemplo n.º 8
0
class StaticSite(StoredFileMixin, db.Model):
    """Static site for an fossir event."""

    __tablename__ = 'static_sites'
    __table_args__ = {'schema': 'events'}

    # StoredFileMixin settings
    add_file_date_column = False
    file_required = False

    #: Entry ID
    id = db.Column(db.Integer, primary_key=True)
    #: ID of the event
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.events.id'),
                         index=True,
                         nullable=False)
    #: The state of the static site (a :class:`StaticSiteState` member)
    state = db.Column(PyIntEnum(StaticSiteState),
                      default=StaticSiteState.pending,
                      nullable=False)
    #: The date and time the static site was requested
    requested_dt = db.Column(UTCDateTime, default=now_utc, nullable=False)
    #: ID of the user who created the static site
    creator_id = db.Column(db.Integer,
                           db.ForeignKey('users.users.id'),
                           index=True,
                           nullable=False)

    #: The user who created the static site
    creator = db.relationship('User',
                              lazy=False,
                              backref=db.backref('static_sites',
                                                 lazy='dynamic'))
    #: The Event this static site is associated with
    event = db.relationship('Event',
                            lazy=True,
                            backref=db.backref('static_sites', lazy='dynamic'))

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

    def _build_storage_path(self):
        path_segments = ['event', strict_unicode(self.event.id), 'static']
        self.assign_id()
        filename = '{}-{}'.format(self.id, self.filename)
        path = posixpath.join(*(path_segments + [filename]))
        return config.STATIC_SITE_STORAGE, path

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'event_id', 'state')
Exemplo n.º 9
0
class SettingsBase(object):
    """Base class for any kind of setting tables"""

    id = db.Column(db.Integer, primary_key=True)
    module = db.Column(db.String, index=True, nullable=False)
    name = db.Column(db.String, index=True, nullable=False)

    @strict_classproperty
    @staticmethod
    def __auto_table_args():
        return (db.CheckConstraint('module = lower(module)',
                                   'lowercase_module'),
                db.CheckConstraint('name = lower(name)', 'lowercase_name'))

    @classmethod
    def delete(cls, module, *names, **kwargs):
        if not names:
            return
        cls.find(cls.name.in_(names), cls.module == module,
                 **kwargs).delete(synchronize_session='fetch')
        db.session.flush()
        cls._clear_cache()

    @classmethod
    def delete_all(cls, module, **kwargs):
        cls.find(module=module, **kwargs).delete()
        db.session.flush()
        cls._clear_cache()

    @classmethod
    def _get_cache(cls, kwargs):
        if not has_request_context():
            # disable the cache by always returning an empty one
            return defaultdict(dict), False
        key = (cls, frozenset(kwargs.viewitems()))
        try:
            return g.global_settings_cache[key], True
        except AttributeError:
            # no cache at all
            g.global_settings_cache = cache = dict()
            cache[key] = rv = defaultdict(dict)
            return rv, False
        except KeyError:
            # no cache for this settings class / kwargs
            return g.global_settings_cache.setdefault(key,
                                                      defaultdict(dict)), False

    @staticmethod
    def _clear_cache():
        if has_request_context():
            g.pop('global_settings_cache', None)
class RoomAttribute(db.Model):
    __tablename__ = 'room_attributes'
    __table_args__ = (db.UniqueConstraint('name', 'location_id'), {
        'schema': 'roombooking'
    })

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

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

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

    @return_ascii
    def __repr__(self):
        return u'<RoomAttribute({}, {}, {})>'.format(self.id, self.name,
                                                     self.location.name)
Exemplo n.º 11
0
class Photo(db.Model):
    __tablename__ = 'photos'
    __table_args__ = {'schema': 'roombooking'}

    id = db.Column(db.Integer, primary_key=True)
    thumbnail = db.Column(db.LargeBinary, nullable=True)
    data = db.Column(db.LargeBinary, nullable=True)

    # relationship backrefs:
    # - room (Room.photo)

    @return_ascii
    def __repr__(self):
        return u'<Photo({0})>'.format(self.id)
Exemplo n.º 12
0
class Break(DescriptionMixin, ColorMixin, LocationMixin, db.Model):
    __tablename__ = 'breaks'
    __auto_table_args = {'schema': 'events'}
    location_backref_name = 'breaks'
    default_colors = ColorTuple('#202020', '#90c0f0')
    possible_render_modes = {RenderMode.markdown}
    default_render_mode = RenderMode.markdown

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

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    duration = db.Column(db.Interval, nullable=False)

    # relationship backrefs:
    # - timetable_entry (TimetableEntry.break_)

    def can_access(self, user):
        parent = self.timetable_entry.parent
        if parent:
            return parent.object.can_access(user)
        else:
            return self.event.can_access(user)

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

    @property
    def location_parent(self):
        return (self.event if self.timetable_entry.parent_id is None else
                self.timetable_entry.parent.session_block)

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

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

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

    @locator_property
    def locator(self):
        return dict(self.event.locator, break_id=self.id)
Exemplo n.º 13
0
class PaperCompetence(db.Model):
    __tablename__ = 'competences'
    __table_args__ = (db.UniqueConstraint('user_id', 'event_id'),
                      {'schema': 'event_paper_reviewing'})

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

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

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'id', 'user_id', 'event_id', _text=', '.join(self.competences))
Exemplo n.º 14
0
 def contribution_field_id(cls):
     return db.Column(
         db.Integer,
         db.ForeignKey('events.contribution_fields.id', name='fk_{}_contribution_field'.format(cls.__tablename__)),
         primary_key=True,
         index=True
     )
Exemplo n.º 15
0
 def revision_id(cls):
     return db.Column(
         db.Integer,
         db.ForeignKey('event_paper_reviewing.revisions.id'),
         index=True,
         nullable=False
     )
Exemplo n.º 16
0
 def access_key(cls):
     if cls.allow_access_key:
         return db.Column(
             db.String,
             nullable=False,
             default=''
         )
 def event_id(cls):
     return db.Column(
         db.Integer,
         db.ForeignKey('events.events.id'),
         nullable=False,
         index=True
     )
Exemplo n.º 18
0
 def abstract_id(cls):
     return db.Column(
         db.Integer,
         db.ForeignKey('event_abstracts.abstracts.id'),
         index=True,
         nullable=False
     )
Exemplo n.º 19
0
class CategorySetting(JSONSettingsBase, db.Model):
    @strict_classproperty
    @staticmethod
    def __auto_table_args():
        return (db.Index(None, 'category_id', 'module',
                         'name'), db.Index(None, 'category_id', 'module'),
                db.UniqueConstraint('category_id', 'module', 'name'), {
                    'schema': 'categories'
                })

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

    category_id = db.Column(db.Integer,
                            db.ForeignKey('categories.categories.id'),
                            index=True,
                            nullable=False)

    category = db.relationship('Category',
                               lazy=True,
                               backref=db.backref('settings', lazy='dynamic'))

    @return_ascii
    def __repr__(self):
        return '<CategorySetting({}, {}, {}, {!r})>'.format(
            self.category_id, self.module, self.name, self.value)
Exemplo n.º 20
0
class IPNetwork(db.Model):
    __tablename__ = 'ip_networks'
    __table_args__ = {'schema': 'fossir'}

    group_id = db.Column(db.Integer,
                         db.ForeignKey('fossir.ip_network_groups.id'),
                         primary_key=True,
                         autoincrement=False)
    network = db.Column(PyIPNetwork, primary_key=True, nullable=False)

    # relationship backrefs:
    # - group (IPNetworkGroup._networks)

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'group_id', 'network')
Exemplo n.º 21
0
class UserSetting(JSONSettingsBase, db.Model):
    """User-specific settings"""
    __table_args__ = (db.Index(None, 'user_id', 'module',
                               'name'), db.Index(None, 'user_id', 'module'),
                      db.UniqueConstraint('user_id', 'module', 'name'),
                      db.CheckConstraint('module = lower(module)',
                                         'lowercase_module'),
                      db.CheckConstraint('name = lower(name)',
                                         'lowercase_name'), {
                                             'schema': 'users'
                                         })

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

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

    @return_ascii
    def __repr__(self):
        return '<UserSetting({}, {}, {}, {!r})>'.format(
            self.user_id, self.module, self.name, self.value)
Exemplo n.º 22
0
class ContributionFieldValueBase(db.Model):
    __abstract__ = True
    #: The name of the backref on the `ContributionField`
    contribution_field_backref_name = None

    data = db.Column(
        JSON,
        nullable=False
    )

    @declared_attr
    def contribution_field_id(cls):
        return db.Column(
            db.Integer,
            db.ForeignKey('events.contribution_fields.id', name='fk_{}_contribution_field'.format(cls.__tablename__)),
            primary_key=True,
            index=True
        )

    @declared_attr
    def contribution_field(cls):
        return db.relationship(
            'ContributionField',
            lazy=False,
            backref=db.backref(
                cls.contribution_field_backref_name,
                cascade='all, delete-orphan',
                lazy=True
            )
        )

    @property
    def friendly_data(self):
        return self.contribution_field.field.get_friendly_value(self.data)
Exemplo n.º 23
0
 def own_no_access_contact(cls):
     if cls.allow_no_access_contact:
         return db.Column(
             'no_access_contact',
             db.String,
             nullable=False,
             default=''
         )
Exemplo n.º 24
0
class LocalGroup(db.Model):
    __tablename__ = 'groups'

    @declared_attr
    def __table_args__(cls):
        return (db.Index('ix_uq_groups_name_lower',
                         db.func.lower(cls.name),
                         unique=True), {
                             'schema': 'users'
                         })

    #: the unique id of the group
    id = db.Column(db.Integer, primary_key=True)
    #: the name of the group
    name = db.Column(db.String, nullable=False, index=True)

    #: the local groups this user belongs to
    members = db.relationship(
        'User',
        secondary='users.group_members',
        lazy=True,
        collection_class=set,
        backref=db.backref('local_groups', lazy=True, collection_class=set),
    )

    # relationship backrefs:
    # - in_attachment_acls (AttachmentPrincipal.local_group)
    # - in_attachment_folder_acls (AttachmentFolderPrincipal.local_group)
    # - in_blocking_acls (BlockingPrincipal.local_group)
    # - in_category_acls (CategoryPrincipal.local_group)
    # - in_contribution_acls (ContributionPrincipal.local_group)
    # - in_event_acls (EventPrincipal.local_group)
    # - in_event_settings_acls (EventSettingPrincipal.local_group)
    # - in_session_acls (SessionPrincipal.local_group)
    # - in_settings_acls (SettingPrincipal.local_group)

    @return_ascii
    def __repr__(self):
        return '<LocalGroup({}, {})>'.format(self.id, self.name)

    @property
    def proxy(self):
        """Returns a GroupProxy wrapping this group"""
        from fossir.modules.groups import GroupProxy
        return GroupProxy(self.id, _group=self)
Exemplo n.º 25
0
class ReferenceModelBase(db.Model):
    __abstract__ = True
    #: The name of the backref on the `ReferenceType`
    reference_backref_name = None

    id = db.Column(db.Integer, primary_key=True)
    value = db.Column(db.String, nullable=False)

    @declared_attr
    def reference_type_id(cls):
        return db.Column(db.Integer,
                         db.ForeignKey('fossir.reference_types.id'),
                         nullable=False,
                         index=True)

    @declared_attr
    def reference_type(cls):
        return db.relationship('ReferenceType',
                               lazy=False,
                               backref=db.backref(cls.reference_backref_name,
                                                  cascade='all, delete-orphan',
                                                  lazy=True))

    @property
    def url(self):
        """The URL of the referenced entity.

        ``None`` if no URL template is defined.
        """
        template = self.reference_type.url_template
        if not template:
            return None
        # XXX: Should the value be urlencoded?
        return template.replace('{value}', self.value)

    @property
    def urn(self):
        """The URN of the referenced entity.

        ``None`` if no scheme is defined.
        """
        scheme = self.reference_type.scheme
        if not scheme:
            return None
        return '{}:{}'.format(scheme, self.value)
Exemplo n.º 26
0
 def created_dt(cls):
     """The date/time when the file was uploaded"""
     if not cls.add_file_date_column:
         return None
     return db.Column(
         UTCDateTime,
         nullable=not cls.file_required,
         default=now_utc
     )
Exemplo n.º 27
0
 def render_mode(cls):
     # Only add the column if there's a choice
     # between several alternatives
     if len(cls.possible_render_modes) > 1:
         return db.Column(PyIntEnum(RenderMode),
                          default=cls.default_render_mode,
                          nullable=False)
     else:
         return cls.default_render_mode
class BlockingPrincipal(PrincipalMixin, db.Model):
    __tablename__ = 'blocking_principals'
    __table_args__ = {'schema': 'roombooking'}
    principal_backref_name = 'in_blocking_acls'
    unique_columns = ('blocking_id', )

    id = db.Column(db.Integer, primary_key=True)
    blocking_id = db.Column(db.Integer,
                            db.ForeignKey('roombooking.blockings.id'),
                            nullable=False)

    # relationship backrefs:
    # - blocking (Blocking._allowed)

    @return_ascii
    def __repr__(self):
        return '<BlockingPrincipal({}, {}, {})>'.format(
            self.id, self.blocking_id, self.principal)
Exemplo n.º 29
0
class LegacyImageMapping(db.Model):
    """Legacy image id mapping

    Legacy images had event-unique numeric ids. Using this
    mapping we can resolve old ones to their new id.
    """

    __tablename__ = 'legacy_image_id_map'
    __table_args__ = {'schema': 'events'}

    event_id = db.Column(
        db.Integer,
        db.ForeignKey('events.events.id'),
        primary_key=True,
        index=True,
        autoincrement=False
    )
    legacy_image_id = db.Column(
        db.Integer,
        primary_key=True,
        index=True,
        autoincrement=False
    )
    image_id = db.Column(
        db.Integer,
        db.ForeignKey('events.image_files.id'),
        nullable=False,
        index=True
    )

    image = db.relationship(
        'ImageFile',
        lazy=False,
        backref=db.backref(
            'legacy_mapping',
            cascade='all, delete-orphan',
            uselist=False,
            lazy=True
        )
    )

    @return_ascii
    def __repr__(self):
        return format_repr(self, 'legacy_image_id', 'image_id')
Exemplo n.º 30
0
class AbstractFile(StoredFileMixin, db.Model):
    __tablename__ = 'files'
    __table_args__ = {'schema': 'event_abstracts'}

    # StoredFileMixin settings
    add_file_date_column = False

    id = db.Column(db.Integer, primary_key=True)
    abstract_id = db.Column(db.Integer,
                            db.ForeignKey('event_abstracts.abstracts.id'),
                            nullable=False,
                            index=True)
    abstract = db.relationship('Abstract',
                               lazy=True,
                               backref=db.backref(
                                   'files',
                                   lazy=True,
                                   cascade='all, delete-orphan'))

    @property
    def locator(self):
        return dict(self.abstract.locator,
                    file_id=self.id,
                    filename=self.filename)

    def _build_storage_path(self):
        self.abstract.assign_id()
        path_segments = [
            'event',
            strict_unicode(self.abstract.event.id), 'abstracts',
            strict_unicode(self.abstract.id)
        ]
        self.assign_id()
        filename = '{}-{}'.format(self.id, self.filename)
        path = posixpath.join(*(path_segments + [filename]))
        return config.ATTACHMENT_STORAGE, path

    @return_ascii
    def __repr__(self):
        return format_repr(self,
                           'id',
                           'abstract_id',
                           content_type=None,
                           _text=text_to_repr(self.filename))