class ItemFridgeRelationship(StructuredRel): purchased = DateTimeProperty(default=datetime.now)
class Item(StructuredNode): name = StringProperty() expiry = DateTimeProperty()
class BuildAttachedRel(StructuredRel): """Definition of a relationship between an Advisory and a KojiBuild attached to it.""" time_attached = DateTimeProperty()
class Question_Question_Rel(StructuredRel): reason = StringProperty(choices={ 'language': 'language', 'similar': 'similar' }) addedOn = DateTimeProperty(default_now=True)
class Tweet(StructuredNode): body = StringProperty() tweeted_on = DateTimeProperty(default_now=True) poster = Relationship('User', 'TWEETED_BY') liked_by = Relationship('User', 'LIKED_BY') comment_on = Relationship('Tweet', 'COMMENT_ON')
class Advisory(EstuaryStructuredNode): """Definition of an Errata advisory in Neo4j.""" class BuildAttachedRel(StructuredRel): """Definition of a relationship between an Advisory and a KojiBuild attached to it.""" time_attached = DateTimeProperty() actual_ship_date = DateTimeProperty() advisory_name = StringProperty(unique=True, index=True) created_at = DateTimeProperty() id_ = UniqueIdProperty(db_property='id') issue_date = DateTimeProperty() product_name = StringProperty() release_date = DateTimeProperty() security_impact = StringProperty() security_sla = DateTimeProperty() state = StringProperty() status_time = DateTimeProperty() synopsis = StringProperty() update_date = DateTimeProperty(index=True) assigned_to = RelationshipTo('.user.User', 'ASSIGNED_TO', cardinality=ZeroOrOne) attached_bugs = RelationshipTo('.bugzilla.BugzillaBug', 'ATTACHED') attached_builds = RelationshipTo('.koji.KojiBuild', 'ATTACHED', model=BuildAttachedRel) reporter = RelationshipTo('.user.User', 'REPORTED_BY', cardinality=ZeroOrOne) triggered_freshmaker_event = RelationshipFrom('.freshmaker.FreshmakerEvent', 'TRIGGERED_BY') @property def display_name(self): """Get intuitive (human readable) display name for the node.""" return self.advisory_name @property def timeline_datetime(self): """Get the DateTime property used for the Estuary timeline.""" return self.created_at @classmethod def attached_build_time(self, advisory, build): """ Get the time that a build related to the advisory was attached. :param node build: a Neo4j node representing an attached build :return: the time the build was attached :rtype: datetime object """ rel = advisory.attached_builds.relationship(build) if rel: return rel.time_attached else: return None @classmethod def find_or_none(cls, identifier): """ Find the node using the supplied identifier. :param str identifier: the identifier to search the node by :return: the node or None :rtype: EstuaryStructuredNode or None """ if re.match(r'^\d+$', identifier): # The identifier is an ID return cls.nodes.get_or_none(id_=identifier) elif re.match(r'^RH[A-Z]{2}-\d{4}:\d+-\d+$', identifier): # The identifier is a full advisory name return cls.nodes.get_or_none(advisory_name=identifier) elif re.match(r'^RH[A-Z]{2}-\d{4}:\d+$', identifier): # The identifier is most of the advisory name, so return the latest iteration of this # advisory return cls.nodes.filter(advisory_name__regex=r'^{0}-\d+$'.format(identifier))\ .order_by('advisory_name').first_or_none() else: raise ValidationError('"{0}" is not a valid identifier'.format(identifier))
class User(StructuredNode, UserInterface): """ User Node A user cannot exist without being in a Organization (even if that Organization is just "Ian McCarty, Inc") A user can belong to only one company A company can have multiple users A user can be have access to multiple applications, and be in multiple groups """ STATUSES = {'ENABLED': 'Enabled', 'DISABLED': 'Disabled'} # properties uid = StringProperty(unique_index=True, default=generate_uuid4) name = StringProperty(required=True) email = EmailProperty(required=True, unique_index=True) username = StringProperty(required=True, unique_index=True) password = StringProperty(required=True) status = StringProperty(default="ENABLED", choices=STATUSES) created_at = DateTimeProperty(default_now=True) modified_at = DateTimeProperty(default_now=True) password_modified_at = DateTimeProperty(default_now=True) org_admin = BooleanProperty() # relationships # outgoing relationships data = RelationshipTo('.data.Data', 'HAS_DATA') organization = RelationshipTo('.organization.Organization', 'IN_ORGANIZATION', cardinality=One) applications = RelationshipTo('.application.Application', 'IN_APPLICATION') groups = RelationshipTo('.group.Group', 'IN_GROUP') apikeys = RelationshipTo('.apikey.ApiKey', 'AUTHENTICATES_USER') def __init__(self, *args, **kwargs): """ Initialize a User Set some private properties that can be used as a "cache" for custom queries to retrieve some of these objects so that we don't make tons of extra queries to pull associated data :param args: :param kwargs: """ super().__init__(*args, **kwargs) self._organization = None self._data = list() self._apikeys = list() self._applications = list() self._groups = list() def get_data(self): if not self._data: self._data = list(self.data.all()) return {_x.to_object() for _x in self._data} def get_organization(self): if not self._organization: self._organization = self.organization.get_or_none() return self._organization.to_object() def get_applications(self): if not self._applications: self._applications = list(self.applications.all()) return {_x.to_object() for _x in self._applications} def get_groups(self): if not self._groups: self._groups = list(self.groups.all()) return {_x.to_object() for _x in self._groups} def get_apikeys(self): if not self._apikeys: self._apikeys = list(self.apikeys.all()) return {_x.to_object() for _x in self._apikeys} def connect_object(self, relationship, o): """ Maybe I want to use this, but also likely not... :param relationship: :param o: :return: """ r = getattr(self, relationship) r.connect(o) cached_r = f"_{relationship}" cr = getattr(self, cached_r) if cr and isinstance(cr, 'list'): cr.append(o) elif cr and isinstance(cr, None): cr = o @staticmethod def create_from_object(o: EUser) -> EUser: """ Usernames are unique uids are unique passwords should be hashed (bcrypt) password_modified should be autoset :param o: :return: """ _org = o.organization._object if not _org: _org = Organization.nodes.filter(uid=o.organization.uid) _apps = [a._object for a in o.applications] if not all(_apps): _apps = Application.nodes.filter( uid__in=[a.uid for a in o.applications]) _groups = [g._object for g in o.groups] if not all(_groups): _groups = Group.nodes.filter(uid__in=[g.uid for g in o.groups]) if not o.username: raise Exception("A username is required") with db.transaction: _o = User(uid=o.uid or generate_uuid4(), name=o.name, email=o.email, username=o.username, password=o.password, status=o.status, created_at=o.created_at, modified_at=o.modified_at, password_modified_at=o.password_modified_at, org_admin=o.org_admin) _o.save() _o.organization.connect(_org) _o._organization = _org for _a in _apps: _o.applications.connect(_a) _o._applications.append(_a) for _g in _groups: _o.groups.connect(_g) _o._groups.append(_g) # attach any data nodes for x in o.data: _data = Data(key=x.key, value=x.value) _data.save() _data.user.connect(_o) _o._data.append(_data) return _o.to_object(organization_flag=True, application_flag=True, group_flag=True, apikey_flag=True) @staticmethod def get_object(o: EUser): if not o._object: o._object = User.nodes.get( uid=o.uid) # will raise exception if not found return o._object @staticmethod def attach_application(o: EUser, a: EApplication) -> EUser: """ Attach an Application to a User :param o: :param a: :return: """ _o = User.get_object(o) _a = Application.get_object(a) _o.applications.connect(_a) _o._applications.append(_a) return o @staticmethod def detach_application(o: EUser, a: EApplication) -> EUser: """ Detach an Application from a User - this also removes a User from group memberships from that Application :param o: :param a: :return: """ with db.transaction: _o = User.get_object(o) _a = Application.get_object(a) # remove from any groups associated with the app for _g in _a.groups.all(): _o.groups.disconnect(_g) if _g in _o._groups: _o._groups.remove(_g) # disconnect from the app _o.applications.disconnect(_a) if _a in _o._applications: _o._applications.remove(_a) return o @staticmethod def attach_group(o: EUser, g: EGroup) -> EUser: """ Attach a Group to a User Note: The User must already be a member of the associated Application :param o: :param g: :return: """ _o = User.get_object(o) _g = Group.get_object(g) member_application_ids = {x.uid for x in _o.applications.all()} group_application_id = _g.application.get().uid if group_application_id not in member_application_ids: raise Exception( "User must be a member of application %s to join group %s" % (group_application_id, g.id)) _o.groups.connect(_g) _o._groups.append(_g) return o @staticmethod def detach_group(o: EUser, g: EGroup) -> EUser: """ Detach a Group from a User :param o: :param g: :return: """ with db.transaction: _o = User.get_object(o) _g = Group.get_object(g) _o.groups.disconnect(_g) if _g in _o._groups: _o._groups.remove(_g) return o @staticmethod def update_from_object(o: EUser) -> EUser: if not o.edits: return o _o = User.get_object(o) with db.transaction: local_fields = { 'name', 'username', 'email', 'password', 'status', 'org_admin' } local_field_edits = {(s[0], s[1]) for s in o.edits if s[0] in local_fields} if local_field_edits: for field, new_value in local_field_edits: setattr(_o, field, new_value) _o.modified_at = datetime.datetime.now() _o.save() foreign_fields = { 'data', } foreign_field_edits = { s[0]: s[1] for s in o.edits if s[0] in foreign_fields } if foreign_field_edits: # CRUD on data Data.update_data_nodes(foreign_field_edits.get('data'), _o, 'user') return _o.to_object(organization_flag=True, application_flag=True, group_flag=True, apikey_flag=True) def to_object(self, organization_flag=False, application_flag=False, group_flag=False, apikey_flag=False, *args, **kwargs) -> EUser: o = EUser(uid=self.uid, name=self.name, email=self.email, username=self.username, password=self.password, status=self.status, created_at=self.created_at, modified_at=self.modified_at, password_modified_at=self.password_modified_at, org_admin=self.org_admin) o.data = {_data.to_object() for _data in self.data.all()} if organization_flag: o.organization = self.get_organization() if application_flag: o.applications = self.get_applications() if group_flag: o.groups = self.get_groups() if apikey_flag: o.apikeys = self.get_apikeys() o._object = self return o @staticmethod def get_all(organization_flag=False, application_flag=False, group_flag=False, apikey_flag=False, limit=25, offset=0, order_by='email', *args, **kwargs) -> SetUser: orgs = [ x.to_object(organization_flag=organization_flag, application_flag=application_flag, group_flag=group_flag, apikey_flag=apikey_flag) for x in User.nodes.order_by(order_by)[offset:offset + limit] ] return orgs @staticmethod def get_by_uid(uid, organization_flag=False, application_flag=False, group_flag=False, apikey_flag=False, *args, **kwargs) -> EUser: """ Retrieve a User based on its ID :param uid: :param organization_flag: :param application_flag: :param group_flag: :param apikey_flag: :param args: :param kwargs: :return: """ user = None _user = User.nodes.get_or_none(uid=uid) if _user: user = _user.to_object(organization_flag=organization_flag, application_flag=application_flag, group_flag=group_flag, apikey_flag=apikey_flag) return user @staticmethod def get_by_username(username, organization_flag=False, application_flag=False, group_flag=False, apikey_flag=False, *args, **kwargs) -> EUser: """ Retrieve a User based on its username :param username: :param organization_flag: :param application_flag: :param group_flag: :param apikey_flag: :param args: :param kwargs: :return: """ user = None _user = User.nodes.get_or_none(username=username) if _user: user = _user.to_object(organization_flag=organization_flag, application_flag=application_flag, group_flag=group_flag, apikey_flag=apikey_flag) return user @staticmethod def search(search, organization_flag=False, application_flag=False, group_flag=False, apikey_flag=False, limit=25, offset=0, order_by='email', *args, **kwargs) -> SetUser: """ Search for Users @see documentation for econtextauth.mappers.neo4j.__init__ for instructions on creating the index :param search: :param organization_flag: :param application_flag: :param group_flag: :param apikey_flag: :param args: :param kwargs: :return: """ users_dict = dict() ordered_user_ids = list() users = list() params = {'search_term': search} # full-text search against organizations and users query = "CALL db.index.fulltext.queryNodes('broad_search_index', $search_term) YIELD node, score " \ "OPTIONAL MATCH (node:Organization)<-[:IN_ORGANIZATION]-(u:User) " \ "RETURN node, u" results, meta = db.cypher_query(query, params) for node, u in results: # user match (name, email, username) if "User" in node.labels and node['uid'] not in users_dict: users_dict[node['uid']] = User.inflate(node) if u and u['uid'] not in users_dict: users_dict[u['uid']] = User.inflate(u) # substring matches against API keys query = "MATCH (k:ApiKey)<--(u:User) WHERE k.key CONTAINS toUpper($search_term) RETURN u" results, meta = db.cypher_query(query, params) for (u, ) in results: if u and u['uid'] not in users_dict: users_dict[u['uid']] = User.inflate(u) # populate Users with appropriate info query = "MATCH (u:User)-->(n) WHERE u.uid IN $user_ids RETURN u.uid, n" results, meta = db.cypher_query(query, {'user_ids': list(users_dict.keys())}) for uid, node in results: user = users_dict.get(uid) if "Organization" in node.labels: user._organization = Organization.inflate(node) elif "Application" in node.labels: user._applications.append(Application.inflate(node)) elif "Group" in node.labels: user._groups.append(Group.inflate(node)) elif "ApiKey" in node.labels: user._apikeys.append(ApiKey.inflate(node)) return { u.to_object(organization_flag=organization_flag, application_flag=application_flag, group_flag=group_flag, apikey_flag=apikey_flag) for u in users_dict.values() } @staticmethod def delete_from_object(o: EUser) -> bool: _o = User.get_object(o) with db.transaction: # delete associated data nodes first: for data in _o.data.all(): data.delete() _o.delete() return True
class BillOf(StructuredRel): timestamp = DateTimeProperty() billUUIDHash = StringProperty()
class VersionedNode(SemiStructuredNode): """ .. py:class:: This class is the base for all data in the SIIM2 project. """ __index__ = 'CaliopeStorage' __extended_classes__ = dict() uuid = StringProperty(default=uuidGenerator, unique_index=True) timestamp = DateTimeProperty(default=timeStampGenerator) change_info = StringProperty(index=True) __special_fields__ = set(['timestamp', 'parent', 'uuid']) def __new__(cls, *args, **kwargs): cls.parent = RelationshipTo(cls, 'PARENT', ZeroOrOne) cls.__extended_classes__[cls.__name__] = cls return super(VersionedNode, cls).__new__(cls, *args, **kwargs) def __init__(self, *args, **kwargs): super(VersionedNode, self).__init__(*args, **kwargs) def _attributes_to_diff(self, special=False): if special: return [a for a in self.__dict__ if a[:1] != '_'] return [ a for a in self.__dict__ if a[:1] != '_' and a not in self.__special_fields__ ] def _should_save_history(self, stored_node): for field in set(self._attributes_to_diff() + stored_node._attributes_to_diff()): # If versioned nodes have different fields they are different. if not hasattr(stored_node, field) or not hasattr(self, field): return True # A field has a different value. if getattr(self, field) != getattr(stored_node, field): return True # Versioned nodes have the save fields and field values. return False @classmethod def _copy_relationships(cls, old_node, new_node): for key, val in cls.__dict__.items(): if issubclass(val.__class__, RelationshipDefinition): if key != 'parent' and hasattr(new_node, key): new_rel = getattr(new_node, key) old_rel = getattr(old_node, key) for related_node in old_rel.all(): new_rel.connect(related_node) @classmethod def push(cls, *args, **kwargs): """ Creates a single node of one class and return it. """ new_node = cls(*args, **kwargs) new_node.save() return new_node @classmethod def inflate(cls, node): if "uuid" in node._properties: return cls.pull(node._properties["uuid"]) return super(VersionedNode, cls).inflate(node) @classmethod def pull(cls, uuid, only_class=False): """Useful when you have and UUID and you need the inflated object in the class it was saved. This methods lookups for the relationship which has the `__instance__` property and traceback the class from the registered classes in `py:class:cid.core.entities.VersionedNode`. The default class for an object is `py:class:cid.core.entities .VersionedNode` when no class was traced. :param uuid: The UUID of the object you want :param only_class: True if you only want to get the class for the uuid. :return: An instance of the class of the object with the data. `py::const None` if does not exists. """ try: versioned_node = VersionedNode.index.get(uuid=uuid) node = versioned_node.__node__ graph_db = node._graph_db node_rels = graph_db.match(end_node=node) for relationship in node_rels: if "__instance__" in relationship._properties \ and relationship._properties["__instance__"]: category_node = relationship.start_node category_node.get_properties() node_class = VersionedNode.__extended_classes__[ category_node._properties["category"] if "category" in category_node._properties else "VersionedNode"] if only_class: return node_class assert issubclass(node_class, cls) return node_class.inflate(node) except DoesNotExist as dne: #: TODO LOG return None def _get_node_data(self): return self.__properties__ def _get_data(self): return { k: self._format_data(v) for k, v in self._get_node_data().iteritems() } def _format_data(self, value): if isinstance(value, list): return [self._format_data(item) for item in value] if isinstance(value, dict): return {k: self._format_data(v) for k, v in value.iteritems()} return {'value': value} def _format_relationships(self, rel_name): """ Format relationship in JSON friendly way. :param: rel_name: The name of the relationship to be parsed :return: A relationship in json friendly dict, example { 'uuid_1': {'property_a': 1, 'property_b': 2}, 'uuid_2': {'property_a': 2, 'property_b': 3} 'uuid_3': {'property_a': 1, 'property_b': 2}, 'uuid_4': {'property_a': 2, 'property_b': 3} } """ rv = {} if hasattr(self, rel_name): relations = getattr(self, rel_name) assert issubclass(relations.__class__, RelationshipManager) if self.__node__ is not None: for target in relations.all(): rel_inst = relations.relationship(target) rv[target.uuid] = dict(rel_inst._properties) #TODO: fix workaround rv[target.uuid]['uuid'] = target.uuid return rv else: raise ValueError("{} not a relationship".format(rel_name)) def add_or_update_relationship_target(self, rel_name, target_uuid, new_properties=None, delete=False): """ Add or update a relationship target. If the relationship target exists, the properties get overwritten. Otherwise the relationship target is created with the new properties. :param: rel_name: The name of the relationship. :param: target_uuid: The uuid of the target node. :param: new_properties : If set, a dictionary with the new properties. If not supplied, all the properties will be deleted. """ target_node = self.__class__.index.get(uuid=target_uuid).__node__ reldef = getattr(self, rel_name) rels = self.__node__.match_one(rel_type=reldef.relation_type, end_node=target_node, bidirectional=True) #: check if just to delete. if delete: #check if there is a rel to delete if len(rels) == 1: #delete the rel rels[0].delete() else: #check if already exists if len(rels) == 1: #set the new properties if new_properties: rels[0].update_properties(new_properties) else: #delete properties rels[0].delete_properties() else: # a new relationship, so connect. target_vnode = VersionedNode.pull(target_uuid) other_node = reldef.single() #TODO: Add support for the different types of cardinality if isinstance(reldef, ZeroOrOne) and other_node: reldef.reconnect(other_node, target_vnode) return reldef.connect(target_vnode, new_properties) def delete_relationship(self, rel_name, target_uuid): self.add_or_update_relationship_target(rel_name, target_uuid, delete=True) def _get_relationships(self): rv = {} for k, v in self.__class__.__dict__.items(): if k not in self.__special_fields__ and \ isinstance(v, RelationshipDefinition): rv[k] = v return rv def serialize(self): rv = self._get_data() rv.update(self._serialize_relationships()) return rv def _serialize_relationships(self): rv = {} for rel_name in self._get_relationships(): assert not rel_name in rv # TODO : remove if rel_name not in self.__special_fields__: rv[rel_name] = self._format_relationships(rel_name) return rv def save(self, skip_difference=False): """ This will save a copy of the previous version of the new, and update the changes. :param skip_difference: True when saving a previous version, should not be modified unless you know what you're doing. :return: The saved object. """ if not skip_difference: # TODO(nel): Don't use an exception here. try: stored_node = self.__class__.index.get(uuid=self.uuid) except DoesNotExist: stored_node = None if stored_node and self._should_save_history(stored_node): # The following operations should be atomic. copy = stored_node.__class__() for field in stored_node._attributes_to_diff(): if not isinstance(getattr(stored_node, field), RelationshipManager): setattr(copy, field, getattr(stored_node, field)) copy.save(skip_difference=True) #Don't keep track of old information self._remove_indexes(copy) if len(self.parent): copy.parent.connect(self.parent.get()) self.parent.disconnect(self.parent.get()) self.parent.connect(copy) self._copy_relationships(self, copy) self.timestamp = timeStampGenerator() super(VersionedNode, self).save() return self def _remove_indexes(self, vnode): batch = CustomBatch(connection(), vnode.index.name, vnode.__node__.id) props = self.deflate(vnode.__properties__, vnode.__node__.id) self._remove_prop_indexes(vnode.__node__, props, batch) batch.submit() def update_field(self, field_name, new_value, field_id=None, special=False): """ Allow granular update of individual fields, including granular items and specific keys within dicts. Also, allows to update specific index within lists or keys within dicts. If the field_name is of type `list` the field_id should be the index to be update, if index is -1 a new value will be appended at the end of the list. In case the index is not a valid index, will raise an exception. If the type of field_name is `dict` the field_id will be the key, if the key already exists will be updated if does not exists will be create. If field_name is not a property or attribute, raise an exception. :param field_name: Name of the field to update :param new_value: Value that needs to be saved or updated :param field_id: If is a list the index to be updated -1 to add a new item, if a dict the key to be updated or added. :raise ValueError: If the field_name is not a property of the object. """ if field_name in self._attributes_to_diff(special=special) or \ getattr(self, field_name, None) is not None: if field_id is not None: curr_value = getattr(self, field_name) if isinstance(curr_value, list) and isinstance(field_id, int): if field_id == -1: curr_value.append(new_value) elif len(curr_value) > field_id: curr_value[field_id] = new_value else: raise IndexError( "Index does {} not exists in {}".format( field_id, field_name)) elif isinstance(curr_value, dict) \ and isinstance(field_id, (str, unicode)): curr_value[field_id] = new_value elif isinstance(curr_value, CaliopeJSONProperty): #: Empty dict propety setattr(self, field_name, {field_id: new_value}) else: setattr(self, field_name, new_value) #: Moved to services.commit #self.save() else: #: TODO: change the following line to support adding new # properties. raise ValueError( "{} not a property or attribute".format(field_name)) def _get_change_history(self, history={}): previous = self.parent.single() if previous: p_data = previous._get_node_data() #p_data.update(previous._serialize_relationships()) c_data = self._get_node_data() #c_data.update(self._serialize_relationships()) diff = DictDiffer(c_data, p_data) history[previous.uuid] = \ {'changed': {k:v for k,v in p_data.iteritems() if k in diff.changed()},\ 'added': {k:v for k,v in p_data.iteritems() if k in diff.added()},\ 'removed': {k:v for k,v in p_data.iteritems() if k in diff.removed()},\ 'unchanged': {k:v for k,v in p_data.iteritems() if k in diff.unchanged()}, 'change_info': p_data['change_info'] if 'change_info' in p_data else 'None'} previous._get_change_history(history=history) rels = self._serialize_relationships() allrels_history = {} if rels: for k, v in rels.items(): rel_history = {} if v: for r_uuid in v.keys(): rel_history[r_uuid] = VersionedNode.pull( r_uuid)._get_change_history(history={}) allrels_history[k] = rel_history history.update(allrels_history) return history def get_history(self, format='json'): if format == 'json': return {k:self._format_data(v) for k, v in self ._get_change_history(history={})\ .iteritems()} else: return self._get_change_history()
class SupplierRel(StructuredRel): since = DateTimeProperty(default=datetime.now) courier = StringProperty()
class TaggedRel(StructuredRel): by = StringProperty() created_date = DateTimeProperty(default=lambda: datetime.now())
class Application(StructuredNode, ApplicationInterface): """ An Application represents an Entity that may be "logged into" """ STATUSES = {'ENABLED': 'Enabled', 'DISABLED': 'Disabled'} # properties uid = StringProperty(unique_index=True, default=generate_uuid4) name = StringProperty(unique_index=True, required=True) status = StringProperty(default="ENABLED", choices=STATUSES) created_at = DateTimeProperty(default_now=True) modified_at = DateTimeProperty(default_now=True) description = StringProperty() jwt_secret = StringProperty(default=generate_uuid4) # relationships # incoming parameters = RelationshipTo('.application_parameter.ApplicationParameter', 'HAS_PARAMETER') data = RelationshipTo('.data.Data', 'HAS_DATA') # outgoing groups = RelationshipFrom('.group.Group', 'HAS_APPLICATION') users = RelationshipFrom('.user.User', 'IN_APPLICATION') def to_object(self, group_flag=False, user_flag=False, *args, **kwargs) -> EApplication: app = EApplication(uid=self.uid, name=self.name, description=self.description, status=self.status, jwt_secret=self.jwt_secret, created_at=self.created_at, modified_at=self.modified_at) app.data = {x.to_object() for x in self.data.all()} app.parameters = {x.to_object() for x in self.parameters.all()} if group_flag: app.groups = {x.to_object() for x in self.groups.all()} if user_flag: app.users = {x.to_object() for x in self.users.all()} app._object = self return app @staticmethod def get_object(o: EApplication): if not o._object: o._object = Application.nodes.get( uid=o.uid) # will raise exception if not found return o._object @staticmethod def get_by_uid(uid, group_flag=False, user_flag=False, *args, **kwargs) -> EApplication: """ Retrieve an Application based on its ID :param uid: :return: """ app = None _app = Application.nodes.get_or_none(uid=uid) if _app: app = _app.to_object(group_flag=group_flag, user_flag=user_flag) return app @staticmethod def get_by_uids(uids, group_flag=False, user_flag=False, *args, **kwargs) -> SetApplication: """ Retrieve an Application based on its ID :param uid: :return: """ if not uids: return set() _apps = Application.nodes.filter(uid__in=tuple(uids)) return { o.to_object(group_flag=group_flag, user_flag=user_flag) for o in _apps } @staticmethod def get_all(group_flag=False, user_flag=False, limit=25, offset=0, order_by='name', *args, **kwargs) -> set: apps = [ x.to_object(group_flag=group_flag, user_flag=user_flag) for x in Application.nodes.order_by(order_by)[offset:offset + limit] ] return apps @staticmethod def create_from_object(app: EApplication) -> EApplication: with db.transaction: _app = Application(uid=app.uid or generate_uuid4(), name=app.name, description=app.description, status=app.status, created_at=app.created_at, modified_at=app.modified_at, jwt_secret=app.jwt_secret or generate_uuid4()) _app.save() # attach any parameters for x in app.parameters: _p = ApplicationParameter(key=x.key, datatype=x.datatype, default=x.default, description=x.description) _p.save() _p.application.connect(_app) # attach any data nodes for x in app.data: _data = Data(key=x.key, value=x.value) _data.save() _data.application.connect(_app) return _app.to_object() @staticmethod def update_from_object(o: EApplication) -> EApplication: if not o.edits: return o _o = Application.get_object(o) with db.transaction: local_fields = {'name', 'description', 'status', 'jwt_secret'} local_field_edits = {(s[0], s[1]) for s in o.edits if s[0] in local_fields} if local_field_edits: for field, new_value in local_field_edits: setattr(_o, field, new_value) _o.modified_at = datetime.datetime.now() _o.save() foreign_fields = {'data', 'parameters'} foreign_field_edits = { s[0]: s[1] for s in o.edits if s[0] in foreign_fields } if foreign_field_edits: # CRUD on data Data.update_data_nodes(foreign_field_edits.get('data'), _o, 'application') # CRUD on parameters ApplicationParameter.update_param_node( foreign_field_edits.get('parameters'), _o, 'application') return _o.to_object() @staticmethod def delete_from_object(o: EApplication) -> bool: _o = Application.get_object(o) with db.transaction: # delete associated parameters and data nodes first: for data in _o.data.all(): data.delete() for param in _o.parameters.all(): param.delete() # also need to remove groups, else those will be stranded... for group in _o.groups.all(): group.delete() _o.delete() return True
class FriendRel(StructuredRel): since = DateTimeProperty(default=lambda: datetime.now(pytz.utc))