Beispiel #1
0
    def write(self, vals):
        """ Write values to the documents in the current set"""
        # Perform write access check
        check_access(self._name, "write", self.env.context["uid"])
        raw_data = self.validate(vals, True)

        # Create two separate dictionaries for handling base and x2many fields
        base_dict = dict()
        x2m_dict = dict()

        # Map provided values to their dictionaries
        for field_name, value in raw_data.items():
            if isinstance(self._fields[field_name], Many2many) or \
                    isinstance(self._fields[field_name], One2many):
                x2m_dict[field_name] = value
            else:
                base_dict[field_name] = value

        # Load base_dict into write cache
        self.env.cache.append("write", self._name, self.ids(), base_dict)

        # Load x2many field data into write cache
        for field_name, value in x2m_dict.items():
            setattr(self, field_name, value)

        self.env.cache.flush()
        return
Beispiel #2
0
    def browse(self, ids):
        """ Given a list of ObjectIds or strs representing
        an ObjectId, return a document set with the
        corresponding elements.
        """
        items = []
        if isinstance(ids, ObjectId):
            # Handle singleton browse (OId)
            items.append(ids)
        elif isinstance(ids, str):
            # Handle singleton browse (str)
            items.append(ObjectId(ids))
        elif isinstance(ids, list):
            # Iterate over list of OId's
            for oid in ids:
                if isinstance(oid, ObjectId):
                    items.append(oid)
                elif isinstance(oid, str):
                    items.append(ObjectId(oid))
                else:
                    raise TypeError("Expected str or ObjectId, "
                                    "got {} instead".format(
                                        oid.__class__.__name__))
        else:
            raise TypeError("Expected list, str or ObjectId, "
                            "got {} instead".format(ids.__class__.__name__))

        # Create a new docset out of the requested OIDs
        docset_instance = self.__class__(self.env, {"_id": {"$in": items}})

        # Make sure the user has read access
        check_access(docset_instance, "read")

        return docset_instance
Beispiel #3
0
    def search(self, query):
        """ Return a new set of documents """
        # Check read access, but skip DLS check.
        # We perform the DLS check right here because
        # we want no exceptions to be raised in case
        # access is denied, but to restrict the domain
        # of the search.
        check_access(self, "read", skip_DLS=True)

        # Create a list of groups this user belongs to
        uid = self.env.context["uid"]
        user_group_rels = conn.db["base.user.group.rel"].find(
            {"user_oid": uid})
        groups = [rel["group_oid"] for rel in user_group_rels]

        # Build DLS query (Document Level Security)
        dls_query = build_DLS_query(self._name, "read", groups, uid,
                                    self.env.session)
        if dls_query:
            # Constraint the provided query by intersecting it with the DLS query.
            new_query = {"$and": [query, dls_query]}
        else:
            # No DLS query
            new_query = query

        # Perform the requested query
        cursor = conn.db[self._name].find(new_query, session=self.env.session)
        ids = [item["_id"] for item in cursor]

        return self.__class__(self.env, {"_id": {"$in": ids}})
Beispiel #4
0
    def create(self, vals_list):
        # Perform create access check
        check_access(self._name, "create", self.env.context["uid"])

        # Convert vals to list if a dict was provided
        if isinstance(vals_list, dict):
            vals_list = [vals_list]

        # For collecting inserted ids
        ids = list()
        docs = list()

        for vals in vals_list:
            # Make sure x2many assignment does not contain
            # any forbidden operation.
            for field, value in vals.items():
                if field in self._fields and (
                        isinstance(self._fields[field], One2many)
                        or isinstance(self._fields[field], Many2many)):
                    for item in value:
                        if item[0] in ["write", "purge", "remove", "clear"]:
                            raise ValueError(
                                "Cannot use x2many operation '{}' on create()".
                                format(item[0]))

            # Validate and Flush, return new DocSet
            raw_data = self.validate(vals)

            # Create two separate dictionaries for handling base and x2many fields
            base_dict = dict()
            x2m_dict = dict()

            # Map provided values to their dictionaries
            for field_name, value in raw_data.items():
                if isinstance(self._fields[field_name], Many2many) or \
                   isinstance(self._fields[field_name], One2many):
                    x2m_dict[field_name] = value
                else:
                    base_dict[field_name] = value

            # Append to buffer lists
            ids.append(base_dict["_id"])
            docs.append(base_dict)

        # Load base_dict into write cache & flush caché
        if len(docs) == 1:
            docs = docs[0]

        self.env.cache.append("create", self._name, None, docs)
        self.env.cache.flush()
        doc = self.__class__(self.env, {"_id": {"$in": ids}})

        # Load x2many field data into write cache
        for field_name, value in x2m_dict.items():
            setattr(doc, field_name, value)

        return doc
Beispiel #5
0
    def unlink(self):
        """ Deletes all the documents in the set.
        Return the amount of deleted elements.
        """
        # Perform unlink access check
        check_access(self, "unlink")
        ids = self.ids
        if self._name in registry.__deletion_constraints__:
            for constraint in registry.__deletion_constraints__[self._name]:
                mod, fld, cons = constraint
                if cons == "RESTRICT":
                    related = self.sudo().env[mod].search({fld: {"$in": ids}})
                    if related.count() > 0:
                        raise DeletionConstraintError(
                            "There are one or more records referencing "
                            "the current set. Deletion aborted.")
                elif cons == "CASCADE":
                    related = self.env[mod].search({fld: {"$in": ids}})
                    if related.count() > 0:
                        related.unlink()
                elif cons == "SET NULL":
                    intrm = getattr(self.env[mod], "_intermediate", False)
                    related = self.sudo().env[mod].search({fld: {"$in": ids}})
                    if related.count() > 0:
                        if not intrm:
                            related.write({fld: None})
                        else:
                            # If the model we're working with is intermediate,
                            # we've got to delete the relation instead,
                            # in order to avoid a NOT NULL constraint error
                            # and keep the collection clean.
                            if related.count() > 0:
                                related.unlink()
                else:
                    raise ValueError(
                        "Invalid deletion constraint '{}'".format(cons))

        # Delete documents
        outcome = conn.db[self._name].delete_many(self._query,
                                                  session=self.env.session)

        # Delete any base.model.data documents
        # referencing any of the deleted documents
        conn.db["base.model.data"].delete_many({
            "model": self._name,
            "res_id": {
                "$in": ids
            }
        })

        return outcome.deleted_count
Beispiel #6
0
    def get(self, external_id):
        """ Finds a single document by its external id """
        mod_data = conn.db["base.model.data"].find_one(
            {
                "model": self._name,
                "name": external_id
            },
            session=self.env.session)

        if not mod_data:
            return None

        # Create a singleton docset
        docset_instance = self.__class__(self.env, {"_id": mod_data["res_id"]})

        # Make sure the user has read access
        check_access(docset_instance, "read")

        return docset_instance
Beispiel #7
0
    def unlink(self):
        """ Deletes all the documents in the set.
        Return the amount of deleted elements.
        """
        # Perform unlink access check
        check_access(self._name, "unlink", self.env.context["uid"])
        ids = self.ids()
        if self._name in registry.__deletion_constraints__:
            for constraint in registry.__deletion_constraints__[self._name]:
                mod, fld, cons = constraint
                related = self.env[mod].search({fld: {"$in": ids}})
                if cons == "RESTRICT":
                    if related.count() > 0:
                        raise DeletionConstraintError(
                            "There are one or more records referencing "
                            "the current set. Deletion aborted.")
                elif cons == "CASCADE":
                    if related.count() > 0:
                        related.unlink()
                elif cons == "SET NULL":
                    if related.count() > 0:
                        related.write({fld: None})
                else:
                    raise ValueError(
                        "Invalid deletion constraint '{}'".format(cons))

        # Delete documents
        outcome = conn.db[self._name].delete_many(self._query,
                                                  session=self.env.session)

        # Delete any base.model.data documents
        # referencing any of the deleted documents
        conn.db["base.model.data"].delete_many({
            "model": self._name,
            "res_id": {
                "$in": ids
            }
        })

        return outcome.deleted_count
Beispiel #8
0
    def read(self, fields=[]):
        """ Returns a list of dictionaries representing
        each document in the set. The optional parameter fields
        allows to specify which field values should be retrieved from
        database. If omitted, all fields will be read. This method also
        renders the representation of relational fields (Many2one and x2many).
        """
        # Perform read access check
        check_access(self._name, "read", self.env.context["uid"])

        if len(fields) == 0:
            fields = self._fields.keys()
        cache = dict()
        # By calling the list constructor on a PyMongo cursor we retrieve all the records
        # in a single call. This is faster but may take lots of memory.
        data = list(conn.db[self._name].find(self._query,
                                             {field: 1
                                              for field in fields},
                                             session=self.env.session))
        for field in fields:
            if issubclass(self._fields[field].__class__, RelationalField):
                # Create a caché dict with the representation
                # value of each related document in the dataset
                represent = self._fields[field]._represent
                if isinstance(self._fields[field], Many2one):
                    # Many2one Prefetch
                    related_ids = [
                        item[field] for item in data if item[field] is not None
                    ]
                    rels = list(
                        conn.db[self._fields[field]._comodel_name].find(
                            {"_id": {
                                "$in": related_ids
                            }}, {represent: 1},
                            session=self.env.session))
                    related_docs = {
                        rel["_id"]: (rel["_id"], rel[represent])
                        for rel in rels
                    }
                elif isinstance(self._fields[field], One2many):
                    # One2many Prefetch
                    inversed_by = self._fields[field]._inversed_by
                    related_ids = [item["_id"] for item in data]
                    # Retrieve all the records in the co-model for the current
                    # field that are related to any of the oids in the dataset
                    rels = list(
                        conn.db[self._fields[field]._comodel_name].find(
                            {inversed_by: {
                                "$in": related_ids
                            }}, {
                                represent: 1,
                                inversed_by: 1
                            },
                            session=self.env.session))
                    # Build dictionary with the model record oid as key
                    # and a list of tuples with its co-model relations as value.
                    related_docs = dict()
                    for rel in rels:
                        if rel[inversed_by] not in related_docs:
                            related_docs[rel[inversed_by]] = list()
                        related_docs[rel[inversed_by]].append(
                            (rel["_id"], rel[represent]))
                elif isinstance(self._fields[field], Many2many):
                    # Many2many Prefetch
                    related_ids = [item["_id"] for item in data]
                    rel_model_name = self._fields[field]._relation
                    field_a = self._fields[field]._field_a
                    field_b = self._fields[field]._field_b
                    rels_int = list(conn.db[rel_model_name].find(
                        {field_a: {
                            "$in": related_ids
                        }},
                        session=self.env.session))
                    rels_rep = list(
                        conn.db[self._fields[field]._comodel_name].find(
                            {
                                "_id": {
                                    "$in": [rel[field_b] for rel in rels_int]
                                }
                            }, {represent: 1},
                            session=self.env.session))
                    rels_rep_dict = {
                        rel["_id"]: rel[represent]
                        for rel in rels_rep
                    }
                    rels_dict = dict()
                    for rel in rels_int:
                        if rel[field_a] not in rels_dict:
                            rels_dict[rel[field_a]] = list()
                        rels_dict[rel[field_a]].append(rel[field_b])

                    related_docs = dict()
                    for oid, rels in rels_dict.items():
                        if oid not in related_docs:
                            related_docs[oid] = list()
                        for rel in rels:
                            related_docs[oid].append((rel, rels_rep_dict[rel]))

                # Add relations to the caché dictionary
                cache[field] = related_docs

        result = list()
        # Iterate over data
        for dictitem in data:
            doc = dict()
            for field in fields:
                field_inst = self._fields[field]
                if field_inst._exclude:
                    continue
                if issubclass(field_inst.__class__, RelationalField):
                    if isinstance(field_inst, Many2one):
                        # Get Many2one representation from caché
                        value = dictitem.get(field, None)
                        doc[field] = cache[field].get(value, None)
                    elif isinstance(field_inst, One2many) or isinstance(
                            field_inst, Many2many):
                        # Get x2many representation from caché
                        doc[field] = cache[field].get(dictitem["_id"], list())
                else:
                    doc[field] = dictitem[field] if field in dictitem else None
            result.append(doc)
        return result