Mongoengine: regression from 0.9.0: assert on reverse_delete_rule=CASCADE with source.id.field != dest.id.field

Created on 27 Oct 2015  路  4Comments  路  Source: MongoEngine/mongoengine

The regression seems to be in queryset/base.py BaseQuerySet.delete, in creating the queryset in 0.9.0 there is only one clause, but in 0.10.0 there is an additional 'id__nin' clause that implicitly assumes the id of both objects is of the same field, and breaks when they are not, as in this example.

Additionally it doesn't make sense to check for id of a different collection not in this collection.

The following example breaks in 0.10.0 but works in 0.9.0:

from mongoengine import connect, Document, StringField, ReferenceField, CASCADE

client = connect("alon-test")
#db = client.get_database('alon-test')
#idtest = db.create_collection('idtest') # only in 0.10.0?

class Client(Document):
    id = StringField(primary_key=True)
    foo = StringField()


class Token(Document):
    client = ReferenceField('Client', dbref=False, reverse_delete_rule=CASCADE)
    somethingelse = StringField()


client = Client(id="1234", foo="bar")
client.save()

token = Token(client=client, somethingelse='something else entirely')
token.save()

client.delete()

The error in 0.10.0 results from the 'id__nin' clause on the rule's queryset that is checking the id of Token, which is ObjectId, but should be checking the id of Client, which is a StringField:

$ python mongo_test.py 
Traceback (most recent call last):
  File "mongo_test.py", line 24, in <module>
    client.delete()
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/document.py", line 491, in delete
    **self._object_key).delete(write_concern=write_concern, _from_doc_delete=True)
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 409, in delete
    ref_q_count = ref_q.count()
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/queryset.py", line 104, in count
    return super(QuerySet, self).count(with_limit_and_skip)
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 347, in count
    return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 1445, in _cursor
    self._cursor_obj = self._collection.find(self._query,
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 1479, in _query
    self._mongo_query = self._query_obj.to_query(self._document)
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/visitor.py", line 90, in to_query
    query = query.accept(QueryCompilerVisitor(document))
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/visitor.py", line 155, in accept
    return visitor.visit_query(self)
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/visitor.py", line 78, in visit_query
    return transform.query(self.document, **query.query)
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/queryset/transform.py", line 102, in query
    value = [field.prepare_query_value(op, v) for v in value]
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/base/fields.py", line 446, in prepare_query_value
    return self.to_mongo(value)
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/base/fields.py", line 442, in to_mongo
    self.error(unicode(e))
  File "/home2/alon/consumer/env_main/lib/python2.7/site-packages/mongoengine/base/fields.py", line 144, in error
    raise ValidationError(message, errors=errors, field_name=field_name)
mongoengine.errors.ValidationError: u'1234' is not a valid ObjectId, it must be a 12-byte input or a 24-character hex string

The query in 0.9.0:

ref_q = document_cls.objects(**{field_name + '__in': self})

in 0.10.0:

ref_q = document_cls.objects(**{field_name + '__in': self, 'id__nin': cascade_refs})

Bug

All 4 comments

Seems like related to this bug one more case.
For example we have models with custom name of field which used as primary key:

class User(Document):
    """Collection of users profiles."""

    email = EmailField(required=True, unique=True)
    username = StringField(regex=r'[a-zA-Z0-9_-]+$', max_length=120,
                           required=True, unique=True)
    password = StringField(max_length=64, required=True)
    is_superuser = BooleanField(default=False)
    is_disabled = BooleanField(default=False)

    meta = {
        'collection': 'users',
        'db_alias': 'makechat_test' if TEST_MODE else 'makechat',
        'indexes': ['email', 'username', 'password']
    }

    def __str__(self):
        """Standart python magic __str__ method."""
        return self.username

class Session(Document):
    """Collection of users sessions."""

    user = ReferenceField(User, reverse_delete_rule=CASCADE)
    created = DateTimeField(default=datetime.now)
    value = StringField(max_length=64, primary_key=True)

    meta = {
        'collection': 'sessions',
        'db_alias': 'makechat_test' if TEST_MODE else 'makechat',
        'indexes': [
            {'fields': ['created'], 'expireAfterSeconds': SESSION_TTL}
        ]
    }

then delete() will cause error:

>>> from makechat.models import User

>>> User.objects.all().delete()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/base.py", line 409, in delete
    ref_q_count = ref_q.count()
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/queryset.py", line 104, in count
    return super(QuerySet, self).count(with_limit_and_skip)
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/base.py", line 347, in count
    return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/base.py", line 1481, in _cursor
    self._cursor_obj = self._collection.find(self._query,
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/base.py", line 1515, in _query
    self._mongo_query = self._query_obj.to_query(self._document)
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/visitor.py", line 90, in to_query
    query = query.accept(QueryCompilerVisitor(document))
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/visitor.py", line 155, in accept
    return visitor.visit_query(self)
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/visitor.py", line 78, in visit_query
    return transform.query(self.document, **query.query)
  File "/home/buran/envs/py3/lib/python3.4/site-packages/mongoengine/queryset/transform.py", line 61, in query
    raise InvalidQueryError(e)
mongoengine.errors.InvalidQueryError: Cannot resolve field "id"
Cannot resolve field "id"

but changing name to id will solve this problem:


class Session(Document):
    """Collection of users sessions."""

    user = ReferenceField(User, reverse_delete_rule=CASCADE)
    created = DateTimeField(default=datetime.now)
    id = StringField(max_length=64, primary_key=True)

    meta = {
        'collection': 'sessions',
        'db_alias': 'makechat_test' if TEST_MODE else 'makechat',
        'indexes': [
            {'fields': ['created'], 'expireAfterSeconds': SESSION_TTL}
        ]
    }
>>> from makechat.models import User

>>> User.objects.all().delete()
0

>>> 

Python 3.4.3
mongoengine 0.10.6

Can also confirm this issue....

>>> ProblemSet.objects[0].problems
[<Problem: (_id=5679a27069684a06db90ef7a, type=56f1673c1e08170c7b6189e5)>, <Problem: (_id=5679a87369684a06fed59567, type=56f1673c1e08170c7b6189e5)>]
>>> p = ProblemSet.objects[0].problems[0]
>>> ProblemSet.objects(problems__in=p)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/queryset.py", line 58, in __repr__
    self._populate_cache()
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/queryset.py", line 92, in _populate_cache
    self._result_cache.append(next(self))
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/base.py", line 1407, in __next__
    raw_doc = next(self._cursor)
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/base.py", line 1481, in _cursor
    self._cursor_obj = self._collection.find(self._query,
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/base.py", line 1515, in _query
    self._mongo_query = self._query_obj.to_query(self._document)
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/visitor.py", line 90, in to_query
    query = query.accept(QueryCompilerVisitor(document))
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/visitor.py", line 155, in accept
    return visitor.visit_query(self)
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/visitor.py", line 78, in visit_query
    return transform.query(self.document, **query.query)
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/transform.py", line 102, in query
    value = [field.prepare_query_value(op, v) for v in value]
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/queryset/transform.py", line 102, in <listcomp>
    value = [field.prepare_query_value(op, v) for v in value]
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/fields.py", line 706, in prepare_query_value
    return self.field.prepare_query_value(op, value)
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/fields.py", line 992, in prepare_query_value
    return self.to_mongo(value)
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/fields.py", line 969, in to_mongo
    id_ = id_field.to_mongo(id_)
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/base/fields.py", line 453, in to_mongo
    self.error(str(e))
  File "/Users/robert/.envs/api/lib/python3.4/site-packages/mongoengine/base/fields.py", line 155, in error
    raise ValidationError(message, errors=errors, field_name=field_name)
mongoengine.errors.ValidationError: 'id' is not a valid ObjectId, it must be a 12-byte input or a 24-character hex string

This is fixed now, sorry it took that long.

(this is also a dupe of #1224)

Was this page helpful?
0 / 5 - 0 ratings