class BaseNotification(base.ContextRBAC, Base, db.Model): """Base notifications and notifications history model.""" __abstract__ = True object_id = db.Column(db.Integer, nullable=False) object_type = db.Column(db.String, nullable=False) send_on = db.Column(db.DateTime, nullable=False) sent_at = db.Column(db.DateTime, nullable=True) custom_message = db.Column(db.Text, nullable=False, default=u"") force_notifications = db.Column(db.Boolean, default=False, nullable=False) repeating = db.Column(db.Boolean, nullable=False, default=False) object = utils.PolymorphicRelationship("object_id", "object_type", "{}_notifiable") @declared_attr def notification_type_id(cls): # pylint: disable=no-self-argument return db.Column(db.Integer, db.ForeignKey('notification_types.id'), nullable=False) @declared_attr def notification_type(cls): # pylint: disable=no-self-argument return db.relationship('NotificationType', foreign_keys='{}.notification_type_id'.format( cls.__name__))
class Notification(Base, db.Model): __tablename__ = 'notifications' object_id = db.Column(db.Integer, nullable=False) object_type = db.Column(db.String, nullable=False) send_on = db.Column(db.DateTime, nullable=False) sent_at = db.Column(db.DateTime, nullable=True) custom_message = db.Column(db.Text, nullable=True) force_notifications = db.Column(db.Boolean, default=False, nullable=False) notification_type_id = db.Column( db.Integer, db.ForeignKey('notification_types.id'), nullable=False) notification_type = db.relationship( 'NotificationType', foreign_keys='Notification.notification_type_id') object = utils.PolymorphicRelationship("object_id", "object_type", "{}_notifiable")
class ObjectLabel(base.ContextRBAC, Base, db.Model): """ObjectLabel Model.""" __tablename__ = 'object_labels' label_id = db.Column(db.Integer, db.ForeignKey(Label.id), nullable=False, primary_key=True) object_id = db.Column(db.Integer, nullable=False) object_type = db.Column(db.String, nullable=False) label = db.relationship( 'Label', primaryjoin='remote(Label.id) == ObjectLabel.label_id', ) labeled_object = utils.PolymorphicRelationship("object_id", "object_type", "{}_labeled") _extra_table_args = [ db.UniqueConstraint('label_id', 'object_id', 'object_type'), ]
class Comment(Roleable, Relatable, Described, Notifiable, base.ContextRBAC, Base, Indexed, db.Model): """Basic comment model.""" __tablename__ = "comments" assignee_type = db.Column(db.String, nullable=False, default=u"") revision_id = deferred( db.Column( db.Integer, db.ForeignKey('revisions.id', ondelete='SET NULL'), nullable=True, ), 'Comment') revision = db.relationship( 'Revision', uselist=False, ) custom_attribute_definition_id = deferred( db.Column( db.Integer, db.ForeignKey('custom_attribute_definitions.id', ondelete='SET NULL'), nullable=True, ), 'Comment') custom_attribute_definition = db.relationship( 'CustomAttributeDefinition', uselist=False, ) initiator_instance_id = db.Column(db.Integer, nullable=True) initiator_instance_type = db.Column(db.String, nullable=True) INITIATOR_INSTANCE_TMPL = "{}_comment_initiated_by" initiator_instance = utils.PolymorphicRelationship( "initiator_instance_id", "initiator_instance_type", INITIATOR_INSTANCE_TMPL) # REST properties _api_attrs = reflection.ApiAttributes( "assignee_type", reflection.Attribute("custom_attribute_revision", create=False, update=False), reflection.Attribute("custom_attribute_revision_upd", read=False), reflection.Attribute("header_url_link", create=False, update=False), ) _sanitize_html = [ "description", ] def get_objects_to_reindex(self): """Return list required objects for reindex if comment C.U.D.""" source_qs = db.session.query( Relationship.destination_type, Relationship.destination_id).filter( Relationship.source_type == self.__class__.__name__, Relationship.source_id == self.id) destination_qs = db.session.query( Relationship.source_type, Relationship.source_id).filter( Relationship.destination_type == self.__class__.__name__, Relationship.destination_id == self.id) result_qs = source_qs.union(destination_qs) klass_dict = defaultdict(set) for klass, object_id in result_qs: klass_dict[klass].add(object_id) queries = [] for klass, object_ids in klass_dict.iteritems(): model = inflector.get_model(klass) if not model: continue if issubclass(model, (Indexed, Commentable)): queries.append( model.query.filter(model.id.in_(list(object_ids)))) return list(itertools.chain(*queries)) AUTO_REINDEX_RULES = [ ReindexRule("Comment", lambda x: x.get_objects_to_reindex()), ReindexRule("Relationship", reindex_by_relationship), ] @builder.simple_property def header_url_link(self): """Return header url link to comment if that comment related to proposal and that proposal is only proposed.""" if self.initiator_instance_type != "Proposal": return "" proposed_status = self.initiator_instance.STATES.PROPOSED if self.initiator_instance.status == proposed_status: return "proposal_link" return "" @classmethod def eager_query(cls): query = super(Comment, cls).eager_query() return query.options( orm.joinedload('revision'), orm.joinedload('custom_attribute_definition').undefer_group( 'CustomAttributeDefinition_complete'), ) def log_json(self): """Log custom attribute revisions.""" res = super(Comment, self).log_json() res["custom_attribute_revision"] = self.custom_attribute_revision return res @builder.simple_property def custom_attribute_revision(self): """Get the historical value of the relevant CA value.""" if not self.revision: return None revision = self.revision.content cav_stored_value = revision['attribute_value'] cad = self.custom_attribute_definition return { 'custom_attribute': { 'id': cad.id if cad else None, 'title': cad.title if cad else 'DELETED DEFINITION', }, 'custom_attribute_stored_value': cav_stored_value, } def custom_attribute_revision_upd(self, value): """Create a Comment-CA mapping with current CA value stored.""" ca_revision_dict = value.get('custom_attribute_revision_upd') if not ca_revision_dict: return ca_val_dict = self._get_ca_value(ca_revision_dict) ca_val_id = ca_val_dict['id'] ca_val_revision = Revision.query.filter_by( resource_type='CustomAttributeValue', resource_id=ca_val_id, ).order_by(Revision.created_at.desc(), ).limit(1).first() if not ca_val_revision: raise BadRequest( "No Revision found for CA value with id provided under " "'custom_attribute_value': {}".format(ca_val_dict)) self.revision_id = ca_val_revision.id self.revision = ca_val_revision # Here *attribute*_id is assigned to *definition*_id, strange but, # as you can see in src/ggrc/models/custom_attribute_value.py # custom_attribute_id is link to custom_attribute_definitions.id # possible best way is use definition id from request: # ca_revision_dict["custom_attribute_definition"]["id"] # but needs to be checked that is always exist in request self.custom_attribute_definition_id = ca_val_revision.content.get( 'custom_attribute_id', ) self.custom_attribute_definition = CustomAttributeDefinition.query.get( self.custom_attribute_definition_id, ) @staticmethod def _get_ca_value(ca_revision_dict): """Get CA value dict from json and do a basic validation.""" ca_val_dict = ca_revision_dict.get('custom_attribute_value') if not ca_val_dict: raise ValueError( "CA value expected under " "'custom_attribute_value': {}".format(ca_revision_dict)) if not ca_val_dict.get('id'): raise ValueError( "CA value id expected under 'id': {}".format(ca_val_dict)) return ca_val_dict
class IssuetrackerIssue(base.ContextRBAC, Base, db.Model): """Class representing IssuetrackerIssue.""" __tablename__ = 'issuetracker_issues' object_id = db.Column(db.Integer, nullable=False) object_type = db.Column(db.String(250), nullable=False) enabled = db.Column(db.Boolean, nullable=False, default=False) title = db.Column(db.String(250), nullable=True) component_id = db.Column(db.String(50), nullable=True) hotlist_id = db.Column(db.String(50), nullable=True) issue_type = db.Column(db.String(50), nullable=True) issue_priority = db.Column(db.String(50), nullable=True) issue_severity = db.Column(db.String(50), nullable=True) assignee = db.Column(db.String(250), nullable=True) cc_list = db.Column(db.Text, nullable=False, default="") due_date = db.Column(db.Date, nullable=True) issue_id = db.Column(db.String(50), nullable=True) issue_url = db.Column(db.String(250), nullable=True) issue_tracked_obj = utils.PolymorphicRelationship("object_id", "object_type", "{}_issue_tracked") @classmethod def get_issue(cls, object_type, object_id): """Returns an issue object by given type and ID or None. Args: object_type: A string representing a model. object_id: An integer identifier of model's instance. Returns: An instance of IssuetrackerIssue or None. """ return cls.query.filter(cls.object_type == object_type, cls.object_id == object_id).first() def to_dict(self, include_issue=False, include_private=False): """Returns representation of object as a dict. Args: include_issue: A boolean whether to include issue related properties. include_private: A boolean whether to include private properties. Returns: A dict representing an instance of IssuetrackerIssue. """ res = { 'enabled': self.enabled, 'component_id': self.component_id, 'hotlist_id': self.hotlist_id, 'issue_type': self.issue_type, 'issue_priority': self.issue_priority, 'issue_severity': self.issue_severity, } if include_issue: res['issue_id'] = self.issue_id res['issue_url'] = self.issue_url res['title'] = self.title if include_private: res['object_id'] = self.object_id res['object_type'] = self.object_type res['assignee'] = self.assignee res['cc_list'] = self.cc_list.split(',') if self.cc_list else [] return res @classmethod def create_or_update_from_dict(cls, obj, info): """Creates or updates issue with given parameters. Args: obj: An object which is an IssueTracked instance. info: A dict with issue properties. Returns: An instance of IssuetrackerIssue. """ if not info: raise ValueError('Issue tracker info cannot be empty.') issue_obj = cls.get_issue(obj.type, obj.id) info = dict(info, issue_tracked_obj=obj) if issue_obj is not None: issue_obj.update_from_dict(info) else: issue_obj = cls.create_from_dict(info) db.session.add(issue_obj) return issue_obj @classmethod def create_from_dict(cls, info): """Creates issue with given parameters. Args: info: A dict with issue properties. Returns: An instance of IssuetrackerIssue. """ cc_list = info.get('cc_list') if cc_list is not None: cc_list = ','.join(cc_list) return cls( issue_tracked_obj=info['issue_tracked_obj'], enabled=bool(info.get('enabled')), title=info.get('title'), component_id=info.get('component_id'), hotlist_id=info.get('hotlist_id'), issue_type=info.get('issue_type'), issue_priority=info.get('issue_priority'), issue_severity=info.get('issue_severity'), assignee=info.get('assignee'), cc_list=cc_list, issue_id=info.get('issue_id'), issue_url=info.get('issue_url'), ) def update_from_dict(self, info): """Updates issue with given parameters. Args: info: A dict with issue properties. Returns: An instance of IssuetrackerIssue. """ cc_list = info.pop('cc_list', None) info = dict(self.to_dict(include_issue=True, include_private=True), **info) if cc_list is not None: info['cc_list'] = cc_list if info['cc_list'] is not None: info['cc_list'] = ','.join(info['cc_list']) self.object_type = info['object_type'] self.object_id = info['object_id'] self.enabled = info['enabled'] self.title = info['title'] self.component_id = info['component_id'] self.hotlist_id = info['hotlist_id'] self.issue_type = info['issue_type'] self.issue_priority = info['issue_priority'] self.issue_severity = info['issue_severity'] self.assignee = info['assignee'] self.cc_list = info['cc_list'] self.issue_id = info['issue_id'] self.issue_url = info['issue_url'] if info.get('due_date'): self.due_date = info.get('due_date') @staticmethod def get_issuetracker_issue_stub(): """Returns dict with all Issue Tracker fields with empty values.""" return { 'enabled': False, 'component_id': None, 'hotlist_id': None, 'issue_type': None, 'issue_priority': None, 'issue_severity': None, 'title': None, 'issue_id': None, 'issue_url': None }