예제 #1
0
class ItemFridgeRelationship(StructuredRel):
    purchased = DateTimeProperty(default=datetime.now)
예제 #2
0
class Item(StructuredNode):
    name = StringProperty()
    expiry = DateTimeProperty()
예제 #3
0
    class BuildAttachedRel(StructuredRel):
        """Definition of a relationship between an Advisory and a KojiBuild attached to it."""

        time_attached = DateTimeProperty()
예제 #4
0
class Question_Question_Rel(StructuredRel):
    reason = StringProperty(choices={
        'language': 'language',
        'similar': 'similar'
    })
    addedOn = DateTimeProperty(default_now=True)
예제 #5
0
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')
예제 #6
0
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))
예제 #7
0
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
예제 #8
0
class BillOf(StructuredRel):
    timestamp = DateTimeProperty()
    billUUIDHash = StringProperty()
예제 #9
0
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()
예제 #10
0
class SupplierRel(StructuredRel):
    since = DateTimeProperty(default=datetime.now)
    courier = StringProperty()
예제 #11
0
class TaggedRel(StructuredRel):
    by = StringProperty()
    created_date = DateTimeProperty(default=lambda: datetime.now())
예제 #12
0
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
예제 #13
0
class FriendRel(StructuredRel):
    since = DateTimeProperty(default=lambda: datetime.now(pytz.utc))