class UserRole(Base, db.Model): __tablename__ = 'user_roles' # Override default from `ContextRBAC` to provide backref context = db.relationship('Context', backref='user_roles') role_id = db.Column(db.Integer(), db.ForeignKey('roles.id'), nullable=False) role = db.relationship('Role', backref=backref('user_roles', cascade='all, delete-orphan')) person_id = db.Column(db.Integer(), db.ForeignKey('people.id'), nullable=False) person = db.relationship('Person', backref=backref('user_roles', cascade='all, delete-orphan')) @staticmethod def _extra_table_args(cls): return (db.Index('ix_user_roles_person', 'person_id'), ) _publish_attrs = ['role', 'person'] @classmethod def role_assignments_for(cls, context): context_id = context.id if type(context) is Context else context all_assignments = db.session.query(UserRole)\ .filter(UserRole.context_id == context_id) assignments_by_user = {} for assignment in all_assignments: assignments_by_user.setdefault(assignment.person.email, [])\ .append(assignment.role) return assignments_by_user @classmethod def eager_query(cls): from sqlalchemy import orm query = super(UserRole, cls).eager_query() return query.options(orm.subqueryload('role'), orm.subqueryload('person'), orm.subqueryload('context')) def _display_name(self): if self.context and self.context.related_object_type and \ self.context.related_object: context_related = ' in ' + self.context.related_object.display_name elif hasattr(self, '_display_related_title'): context_related = ' in ' + self._display_related_title elif self.context: logger.warning('Unable to identify context.related for UserRole') context_related = '' else: context_related = '' return u'{0} <-> {1}{2}'.format(self.person.display_name, self.role.display_name, context_related)
class CalendarEvent(Relatable, Base, db.Model): """Model representing events for Calendar API.""" __tablename__ = 'calendar_events' external_event_id = db.Column(db.String) title = db.Column(db.String) description = db.Column(db.String) due_date = db.Column(db.Date) last_synced_at = db.Column(db.DateTime) attendee_id = db.Column(db.Integer(), db.ForeignKey('people.id'), nullable=False) @declared_attr def attendee(cls): # pylint: disable=no-self-argument """Relationship to user referenced by attendee_id.""" return db.relationship( 'Person', primaryjoin='{0}.attendee_id == Person.id'.format(cls.__name__), foreign_keys='{0}.attendee_id'.format(cls.__name__), remote_side='Person.id', uselist=False, ) @validates("due_date") def validate_due_date(self, _, value): """Validator for due_date""" # pylint: disable=no-self-use return value.date() if isinstance(value, datetime.datetime) else value @property def calendar_end_date(self): """Returns end date for all-day event in calendar api.""" return (self.due_date + datetime.timedelta(days=1)).strftime("%Y-%m-%d") @property def calendar_start_date(self): """Returns start date for all-day event in calendar api.""" return self.due_date.strftime("%Y-%m-%d") @property def is_synced(self): """Indicates whether the event was synced or not.""" return self.last_synced_at is not None @property def needs_sync(self): """Indicates should we send this event to user or not.""" return self.attendee.profile.send_calendar_events def json_equals(self, event_response): """Checks if event is equal to json representation.""" return (event_response['description'] == self.description and event_response['summary'] == self.title and event_response['end']['date'] == self.calendar_end_date and event_response['start']['date'] == self.calendar_start_date)
class ContextImplication(Base, db.Model): '''A roles implication between two contexts. An implication may be scoped with additional scoping properties on the target and source contexts. The meaning of the scoping properties is determined by the module that contributed the implication. For example, an implication may be scoped based on the related objects of the contexts such as from a Program context to an Audit context. ''' __tablename__ = 'context_implications' context_id = db.Column(db.Integer(), db.ForeignKey('contexts.id'), nullable=True) source_context_id = db.Column(db.Integer(), db.ForeignKey('contexts.id'), nullable=True) context_scope = db.Column(db.String, nullable=True) source_context_scope = db.Column(db.String, nullable=True) context = db.relationship( 'Context', uselist=False, foreign_keys=[context_id], ) source_context = db.relationship( 'Context', uselist=False, foreign_keys=[source_context_id], ) def _display_name(self): if self.source_context: source_context_display_name = self.source_context.display_name else: source_context_display_name = 'Default Context' if self.context: context_display_name = self.context.display_name else: context_display_name = 'Default Context' return u'{source_context} -> {context}'.format( source_context=source_context_display_name, context=context_display_name, )
class Role(base.ContextRBAC, Base, Described, db.Model): """A user role. All roles have a unique name. This name could be a simple string, an email address, or some other form of string identifier. Example: .. code-block:: python { 'create': ['Program', 'Control'], 'read': ['Program', 'Control'], 'update': ['Program', 'Control'], 'delete': ['Program'], } """ __tablename__ = 'roles' name = db.Column(db.String(128), nullable=False) permissions_json = db.Column(db.Text(), nullable=False) scope = db.Column(db.String(64), nullable=True) role_order = db.Column(db.Integer(), nullable=True) @simple_property def permissions(self): if self.permissions_json == DECLARED_ROLE: declared_role = get_declared_role(self.name) permissions = declared_role.permissions else: permissions = json.loads(self.permissions_json) or {} # make sure not to omit actions for action in ['create', 'read', 'update', 'delete']: if action not in permissions: permissions[action] = [] return permissions @permissions.setter def permissions(self, value): self.permissions_json = json.dumps(value) _api_attrs = reflection.ApiAttributes( 'name', 'permissions', 'scope', 'role_order', ) def _display_name(self): return self.name
class Context(Base, db.Model): """Context class. Sign permissions object for specific user.""" __tablename__ = 'contexts' # This block and the 'description' attrs are taken from the Described mixin # which we do not use for Context because indexing Context descriptions # for fulltext search leads to undesirable results @declared_attr def description(cls): # pylint: disable=no-self-argument return deferred(db.Column(db.Text, nullable=False, default=u""), cls.__name__) name = deferred(db.Column(db.String(128), nullable=True), 'Context') related_object_id = deferred( db.Column(db.Integer(), nullable=True), 'Context') related_object_type = deferred( db.Column(db.String(128), nullable=True), 'Context') @property def related_object_attr(self): return '{0}_related_object'.format(self.related_object_type) @property def related_object(self): return getattr(self, self.related_object_attr) @related_object.setter def related_object(self, value): self.related_object_id = value.id if value is not None else None self.related_object_type = value.__class__.__name__ if value is not None \ else None return setattr(self, self.related_object_attr, value) @staticmethod def _extra_table_args(_): return ( db.Index( 'ix_context_related_object', 'related_object_type', 'related_object_id'), ) _api_attrs = reflection.ApiAttributes('name', 'related_object', 'description') _sanitize_html = ['name', 'description'] _include_links = []
class Context(Base, db.Model): __tablename__ = 'contexts' # This block and the 'description' attrs are taken from the Described mixin # which we do not use for Context because indexing Context descriptions # for fulltext search leads to undesirable results @declared_attr def description(cls): return deferred(db.Column(db.Text), cls.__name__) name = deferred(db.Column(db.String(128), nullable=True), 'Context') related_object_id = deferred( db.Column(db.Integer(), nullable=True), 'Context') related_object_type = deferred( db.Column(db.String(128), nullable=True), 'Context') @property def related_object_attr(self): return '{0}_related_object'.format(self.related_object_type) @property def related_object(self): return getattr(self, self.related_object_attr) @related_object.setter def related_object(self, value): self.related_object_id = value.id if value is not None else None self.related_object_type = value.__class__.__name__ if value is not None \ else None return setattr(self, self.related_object_attr, value) @staticmethod def _extra_table_args(cls): return ( db.Index( 'ix_context_related_object', 'related_object_type', 'related_object_id'), ) _publish_attrs = ['name', 'related_object', 'description'] _sanitize_html = ['name', 'description'] _include_links = []
class UserRole(rest_handable.WithDeleteHandable, rest_handable.WithPostHandable, base.ContextRBAC, Base, db.Model): """`UserRole` model represents mapping between `User` and `Role` models.""" __tablename__ = 'user_roles' # Override default from `ContextRBAC` to provide backref context = db.relationship('Context', backref='user_roles') role_id = db.Column(db.Integer(), db.ForeignKey('roles.id'), nullable=False) role = db.relationship('Role', backref=backref('user_roles', cascade='all, delete-orphan')) person_id = db.Column(db.Integer(), db.ForeignKey('people.id'), nullable=False) person = db.relationship('Person', backref=backref('user_roles', cascade='all, delete-orphan')) @staticmethod def _extra_table_args(model): return (db.UniqueConstraint('person_id', name='uq_{}'.format(model.__tablename__)), db.Index('ix_user_roles_person', 'person_id')) _api_attrs = reflection.ApiAttributes('role', 'person') @classmethod def role_assignments_for(cls, context): context_id = context.id if type(context) is Context else context all_assignments = db.session.query(UserRole)\ .filter(UserRole.context_id == context_id) assignments_by_user = {} for assignment in all_assignments: assignments_by_user.setdefault(assignment.person.email, [])\ .append(assignment.role) return assignments_by_user @classmethod def eager_query(cls, **kwargs): from sqlalchemy import orm query = super(UserRole, cls).eager_query(**kwargs) return query.options(orm.joinedload('role'), orm.subqueryload('person'), orm.subqueryload('context')) def _display_name(self): if self.context and self.context.related_object_type and \ self.context.related_object: context_related = ' in ' + self.context.related_object.display_name elif hasattr(self, '_display_related_title'): context_related = ' in ' + self._display_related_title elif self.context: logger.warning('Unable to identify context.related for UserRole') context_related = '' else: context_related = '' return u'{0} <-> {1}{2}'.format(self.person.display_name, self.role.display_name, context_related) def _recalculate_permissions_cache(self): """Recalculate permissions cache for user `UserRole` relates to.""" with utils.benchmark( "Invalidate permissions cache for user in UserRole"): from ggrc_basic_permissions import load_permissions_for load_permissions_for(self.person, expire_old=True) def handle_delete(self): """Handle `model_deleted` signals invoked on `UserRole` instance. HTTP DELTE method on `UserRole` model triggers following actions: - Recalculate permissions cache for user the `UserRole` object is related to. """ self._recalculate_permissions_cache() def handle_post(self): """Handle `model_posted` signals invoked on `UserRole` instance. HTTP POST method on `UserRole` model triggers following actions: - Recalculate permissions cache for user the `UserRole` object is related to. """ self._recalculate_permissions_cache()