MongoEngine / MongoEngine/mongoengine

Error querying CachedReferenceField - Only accept a document object

Open
#880 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug
Dominant language
Python
Stars
4.3k
Forks
1.2k
Avg merge
4h 41m
Merged PRs (30d)
11

Description

shift = Shift.objects().first()
query = Q(date=datetime.datetime.now()) & Q(shift=shift)         
Confirmation.objects(query).first()

produces the following error in mongoengine 0.9

Traceback (most recent call last):
File "F:/_Projects/api/development/_tmp/test.py", line 29, in
Confirmation.objects(query).first()
File "F:_Projects\api\development\mongoengine\queryset\base.py", line 309, in first
result = queryset[0]
File "F:_Projects\api\development\mongoengine\queryset\base.py", line 160, in getitem
return queryset._document._from_son(queryset._cursor[key],
File "F:_Projects\api\development\mongoengine\queryset\base.py", line 1409, in _cursor
self._cursor_obj = self._collection.find(self._query,
File "F:_Projects\api\development\mongoengine\queryset\base.py", line 1443, in _query
self._mongo_query = self._query_obj.to_query(self._document)
File "F:_Projects\api\development\mongoengine\queryset\visitor.py", line 92, in to_query
query = self.accept(SimplificationVisitor())
File "F:_Projects\api\development\mongoengine\queryset\visitor.py", line 142, in accept
return visitor.visit_combination(self)
File "F:_Projects\api\development\mongoengine\queryset\visitor.py", line 42, in visit_combination
return Q(**self._query_conjunction(queries))
File "F:_Projects\api\development\mongoengine\queryset\visitor.py", line 62, in _query_conjunction
combined_query.update(copy.deepcopy(query))
File "C:\Programming\Python278\Lib\copy.py", line 163, in deepcopy
y = copier(x, memo)
File "C:\Programming\Python278\Lib\copy.py", line 257, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Programming\Python278\Lib\copy.py", line 182, in deepcopy
rv = reductor(2)
File "F:_Projects\api\development\mongoengine\base\document.py", line 192, in getstate
data['_data'] = self.to_mongo()
File "F:_Projects\api\development\mongoengine\base\document.py", line 330, in to_mongo
value = field.to_mongo(value)
File "F:_Projects\api\development\mongoengine\fields.py", line 1078, in to_mongo
self.error('Only accept a document object')
File "F:_Projects\api\development\mongoengine\base\fields.py", line 136, in error
raise ValidationError(message, errors=errors, field_name=field_name)
mongoengine.errors.ValidationError: Only accept a document object

this happens at line 1066 at CachedReferenceField class defined in mongoengine\fields.py

def to_mongo(self, document):
        id_field_name = self.document_type._meta['id_field']
        id_field = self.document_type._fields[id_field_name]
        doc_tipe = self.document_type

        if isinstance(document, Document):
            # We need the id from the saved object to create the DBRef
            id_ = document.pk
            if id_ is None:
                self.error('You can only reference documents once they have'
                           ' been saved to the database')
        else:
            self.error('Only accept a document object')

this is screenshoft of debug session:
http://snag.gy/UUdsn.jpg

class Department(Document):
    code        = StringField(max_length=255, required=True)
    name        = StringField(max_length=255, required=True)
    supervisor  = ReferenceField(Supervisor)
    timezone    = StringField(max_length=50,  required=True)
    holidays    = ListField(DateTimeField())
    start_year  = StringField(max_length=5)
    end_year    = StringField(max_length=5)
    created_at  = DateTimeField()
    updated_at  = DateTimeField()
    deactivated_at = DateTimeField()

    # speed up fields
    supervisor_name = StringField(max_length=255)
    supervisor_phone = StringField(max_length=255)

class District(Document):
    code        = StringField(max_length=255, required=True)
    name        = StringField(max_length=255, required=True)
    department  = ReferenceField(Department,  required=True)
    timezone    = StringField(max_length=50, required=True)
    holidays    = ListField(DateTimeField())
    start_year  = StringField(max_length=5)
    end_year    = StringField(max_length=5)
    created_at  = DateTimeField()
    updated_at  = DateTimeField()
    deactivated_at = DateTimeField()

class Shift(Document):
    parent      = ReferenceField('Shift')  # shift parent ID, if we have type=reschedule or reassigned
    type        = StringField(max_length=50, required=True, choices=['regular', 'holiday', 'newshift', 'reschedule', 'reassignment'])
    date        = DateTimeField() #only if exception is created
    school      = ReferenceField(School, required=True)
    location    = ReferenceField(Location, required=True)
    guard       = ReferenceField(Guard, required=True) #only if exception is created
    name        = StringField(max_length=255)
    weekday     = IntField(required=True, min_value=1, max_value=7)  # isoweekday, Monday = 1
    start       = TimeField(required=True)
    finish      = TimeField(required=True)
    start_sec   = IntField(required=True)
    finish_sec  = IntField(required=True)
    weight      = IntField(required=True) # used for sorting - this is hh_mm_ss for local of this school timezone
    holidays    = ListField(DateTimeField()) # only for regular shift, let's not create a custom exception for each holiday date
    comment     = StringField(max_length=2000)
    created_at  = DateTimeField()
    updated_at  = DateTimeField()
    deactivated_at = DateTimeField()

    # speed up fields
    guard_name  = StringField(max_length=255)
    guard_phone = StringField(max_length=255)
    school_name = StringField(max_length=255)
    location_name = StringField(max_length=255)
    parent_cache = CachedReferenceField('Shift', fields=['type', 'start', 'finish', 'start_sec', 'finish_sec', 'guard', 'weight'])

class Confirmation(Document):
    date        = DateTimeField(required=True)
    guard       = ReferenceField(Guard, required=True)
    department  = ReferenceField(Department, required=True)
    district    = ReferenceField(District, required=True)
    school      = ReferenceField(School, required=True)
    shift       = ReferenceField(Shift, required=True)
    location    = ReferenceField(Location, required=True)
    confirmed   = BooleanField() #confirmed status
    declined    = BooleanField() #declined status
    checkin     = BooleanField() #checked in status
    checkout    = BooleanField() #checkout status

    # checkin/out distance will be will be caculated by using distance function above with pre-save signal defined in __init__.py
    checkin_coordinates  = GeoPointField() # NamedTuple (lon, lat), Point = namedtuple('Point', 'lon lat'), or see lib.geo GeoPoint class
    checkout_coordinates = GeoPointField() # NamedTuple (lon, lat), Point = namedtuple('Point', 'lon lat'), or see lib.geo GeoPoint class
    checkin_distance     = DecimalField()  # in feet, calculates on save automatically, see self._on_save_update_distance() method
    checkout_distance    = DecimalField()  # in feet, calculates on save automatically, see self._on_save_update_distance() method

    # why declined
    declined_reason     = StringField(max_length=500) # explanation of the decline by guard
    declined_reassigned = ReferenceField(PooledGuard)  # if we have re-assigned the decline, link to another PooledGuard ID

    # time stamps
    confirmed_at = DateTimeField()
    declined_at  = DateTimeField()
    checkin_at   = DateTimeField()
    checkout_at  = DateTimeField()
    created_at   = DateTimeField()
    updated_at   = DateTimeField()

    # speed up fields
    supervisor       = ReferenceField(Supervisor, required=True)
    supervisor_name  = StringField(max_length=255, required=True)
    supervisor_phone = StringField(max_length=255, required=True)
    guard_name       = StringField(max_length=255, required=True)
    guard_phone      = StringField(max_length=255, required=True)
    guard_device_token = StringField(max_length=255)
    department_name  = StringField(max_length=255, required=True)
    department_code  = StringField(max_length=255, required=True)
    district_name    = StringField(max_length=255, required=True)
    district_code    = StringField(max_length=255, required=True)
    school_name      = StringField(max_length=255, required=True)
    school_phone     = StringField(max_length=255, required=True)
    school_code      = StringField(max_length=255, required=True)
    shift_launch     = DateTimeField(required=True)
    shift_start_sec  = IntField(required=True)
    shift_finish_sec = IntField(required=True)
    location_cache   = CachedReferenceField(Location, fields=['name', 'coordinates'])
    shift_cache      = CachedReferenceField(Shift,    fields=['type', 'date', 'weekday', 'name', 'start',
                                                              'finish', 'start_sec', 'finish_sec', 'guard', 'parent', 'weight'])


I have fixed this by 
query = Q(date=datetime.datetime.now()) & Q(shift=shift.id)         

but still want to report this bug.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with mongoengine/queryset/visitor.py and the CachedReferenceField implementation in mongoengine/fields.py, following the traceback from query combination through document serialization. Reproduce the Q(date=...) & Q(shift=shift) query and add a regression test showing that querying with the document object does not raise ValidationError; the existing shift.id workaround indicates the expected behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
mongodb, python
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.