Error when insert nested document, object is not subscriptable
One line description of the issue
Error when insert data to Mongodb with Djongo (install last version from github) TypeError: 'Note' object is not subscriptable
Python script
class Person(Subject):
gender = models.CharField(max_length=4)
name = models.CharField(max_length=64)
principal = models.BooleanField(default=False)
notes = ArrayField( model_container=Note, null=True, blank=True)
note.py
class Note(models.Model):
subject = models.CharField(max_length=128, null=True, blank=True)
text = models.TextField(max_length=1024, blank=True)
the issue probably located in file fields.py , line 139.
def _value_thru_fields(self,
func_name: str,
value: dict,
*other_args):
processed_value = {}
errors = {}
for field in self.model_container._meta.get_fields():
try:
try:
field_value = value[field.attname] #### line 139, value is a Note object instead of dict.
except KeyError:
raise ValidationError(f'Value for field "{field}" not supplied')
processed_value[field.attname] = getattr(field, func_name)(field_value, *other_args)
except ValidationError as e:
errors[field.name] = e.error_list
if errors:
e = ValidationError(errors)
raise ValidationError(str(e))
return processed_value
Traceback
TypeError: Got a TypeError when calling Person.objects.create(). This may be because you have a writable field on the serializer class that is not a valid argument to Person.objects.create(). You may need to make the field read-only, or override the PersonSerializer.create() method to handle this correctly.
Original exception was:
Traceback (most recent call last):
File "D:\dev\lan\landjango\landjango\rest_meets_djongo\serializers.py", line 192, in create
instance = model_class._default_manager.create(**data)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\query.py", line 422, in create
obj.save(force_insert=True, using=self.db)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\base.py", line 741, in save
force_update=force_update, update_fields=update_fields)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\base.py", line 779, in save_base
force_update, using, update_fields,
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\base.py", line 870, in _save_table
result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\base.py", line 908, in _do_insert
using=using, raw=raw)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\query.py", line 1186, in _insert
return query.get_compiler(using=using).execute_sql(return_id)
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\sql\compiler.py", line 1374, in execute_sql
for sql, params in self.as_sql():
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\sql\compiler.py", line 1318, in as_sql
for obj in self.query.objs
File "D:\dev\virtualenvs\apienv\lib\site-packages\django\db\models\sql\compiler.py", line 1318, in
As a workaround, I fall back the version to djongo 1.2.38 with rest_meets_djongo 0.0.11 with a patch for lookup to resolve filter issue
from django.db.models import Model
from django.db.models.lookups import PatternLookup, IExact
from djongo.models import fields
from djongo.models.fields import useful_field
# replace original lookup.py the method of PatternLookup.process_rhs
# def process_rhs(self, qn, connection):
# rhs, params = super().process_rhs(qn, connection)
# if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
# params[0] = self.param_pattern % connection.ops.prep_for_like_query(params[0])
# return rhs, params
def process_rhs_pattern(self, qn, connection):
rhs, params = super(PatternLookup, self).process_rhs(qn, connection)
if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
if isinstance(params[0], dict):
# Prevent Django's PatternLookup from casting our query dict {'field': 'to_match'}
# to a str "%{'field': 'to_match'}%"
field, to_match = next(iter(params[0].items()))
params[0] = {field: self.param_pattern % connection.ops.prep_for_like_query(to_match)}
connection.ops.prep_for_like_query(to_match)
else:
params[0] = self.param_pattern % connection.ops.prep_for_like_query(params[0])
return rhs, params
def process_rhs_iexact(self, qn, connection):
rhs, params = super(IExact, self).process_rhs(qn, connection)
if params:
if isinstance(params[0], dict):
# Prevent Django's PatternLookup from casting our query dict {'field': 'to_match'}
# to a str "%{'field': 'to_match'}%"
field, to_match = next(iter(params[0].items()))
params[0] = {field: connection.ops.prep_for_iexact_query(to_match)}
connection.ops.prep_for_like_query(to_match)
else:
params[0] = connection.ops.prep_for_iexact_query(params[0])
return rhs, params
class MyArrayModelField(fields.ArrayModelField):
def get_db_prep_value(self, value, connection, prepared=False):
if prepared:
return value
if value is None and self.blank:
return None
if not isinstance(value, list):
raise ValueError(
'Expected value to be type list,'
f'Got type {type(value)} instead'
)
ret = []
for a_mdl in value:
mdl_ob = {}
if not isinstance(a_mdl, Model):
raise ValueError('Array items must be Model instances')
for fld in a_mdl._meta.get_fields():
if not useful_field(fld):
continue
fld_value = getattr(a_mdl, fld.attname)
mdl_ob[fld.attname] = fld.get_db_prep_value(fld_value, connection, prepared)
ret.append(mdl_ob)
return ret
def get_lookup(self, lookup_name):
"""
Make django use our `get_lookup` method
when building the lookup class for an EmbeddedModelField instance.
"""
lookup = super(self.__class__, self).get_lookup(lookup_name)
if issubclass(lookup, PatternLookup):
lookup = type('Djongo' + lookup.__name__, (lookup,), {'process_rhs': process_rhs_pattern})
elif issubclass(lookup, IExact):
lookup = type('Djongo' + lookup.__name__, (lookup,), {'process_rhs': process_rhs_iexact})
return lookup
class MyEmbeddedModelField(fields.EmbeddedModelField):
def get_lookup(self, lookup_name):
"""
Make django use our `get_lookup` method
when building the lookup class for an EmbeddedModelField instance.
"""
lookup = super(self.__class__, self).get_lookup(lookup_name)
if issubclass(lookup, PatternLookup):
lookup = type('Djongo' + lookup.__name__, (lookup,), {'process_rhs': process_rhs_pattern})
elif issubclass(lookup, IExact):
lookup = type('Djongo' + lookup.__name__, (lookup,), {'process_rhs': process_rhs_iexact})
return lookup
Can you please give us the use-case that raises said error (is it an update, creation, or retrieval)? I suspect that whats happening is you're trying to update the value to null; I'm currently patching that into rest-meets-djongo, as its not yet supported (slight oversight, my apologies). If its something else, please let us know so we can make a more representative test cases.
Update; changes allowing null updates have been made, and are available in v0.0.13 of rest-meets-djongo. Hopefully that fixes the issue
the issue was triggered when insert data for nested model.
File "D:\dev\virtualenvs\apienv\lib\site-packages\djongo\models\fields.py", line 139, in _value_thru_fields
field_value = value[field.attname]
TypeError: 'Note' object is not subscriptable
Value is assigned as a Note object, the expression is not valid for it. I guess it should be a OrderedDict.
Are you passing in a Note without wrapping it in a list? Seems like the most likely culprit here. (I'll look into adding a more informative error message for that case)
EDIT: Doing some quick testing with 1.3.1, that doesn't seem to be the case either (and my error messages report it as such, regardless of initially submitted type)
@oraix I was also facing the same issue and you can solve this by wrapping the dictionary data in a list. Follow these steps in your case :
person_obj, person_created = Person.objects.get_or_create(name = <"any name">) if person_created: person_obj.gender = "Male" person_obj.principal = false data = {"subject":"Computer Science","text":"Computer science is AWESOME"} person_obj.notes=[data] person_obj.save()
I made a workaround, I had added the following code in my abstract model. I your case, the note model.
https://stackoverflow.com/questions/216972/what-does-it-mean-if-a-python-object-is-subscriptable-or-not
class Note(models.Model):
subject = models.CharField(max_length=128, null=True, blank=True)
text = models.TextField(max_length=1024, blank=True)
def __getitem__(self, name):
return getattr(self, name)
I made a workaround, I had added the following code in my abstract model. I your case, the note model.
https://stackoverflow.com/questions/216972/what-does-it-mean-if-a-python-object-is-subscriptable-or-not
class Note(models.Model): subject = models.CharField(max_length=128, null=True, blank=True) text = models.TextField(max_length=1024, blank=True) def __getitem__(self, name): return getattr(self, name)
Thanks you, It's a very helpful workaround.
@nesdis can you update this workaround in the code samples of djongomapper website