MongoEngine / mongoengine

A Python Object-Document-Mapper for working with MongoDB
http://mongoengine.org
MIT License
4.2k stars 1.23k forks source link

Error querying CachedReferenceField - Only accept a document object #880

Open dmitry-saritasa opened 9 years ago

dmitry-saritasa commented 9 years ago
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.
MRigal commented 9 years ago

@saritasa thanks for the great report! You could also submit a PR if you wish