Я попытался создать настраиваемое поле, в котором будет храниться строка, представляющая собой список pks, а затем возвращать ее как набор запросов. (да, возможно, я мог бы сделать это с помощью поля many2many, но я пытался (я) уменьшить количество запросов к базе данных для этого.
В любом случае, кажется, все работает нормально, пока я не реализовал метод def pre_save (self, model_instance, add) в моем настраиваемом поле.
Кажется, что происходит то, что, когда набор запросов возвращается из предварительного сохранения, он запускается через SQLInsertCompiler.prepare_value, который проверяет значение, чтобы увидеть, имеет ли он атрибут «resol_expression», который, как предполагается, является тогда выражением SQL и затем пытается проверить наличие «contains_column_references» ... Объект QuerySet имеет атрибут «resol_expression», но НЕ имеет других, которые находятся в объектах выражений SQL.
Я подозреваю, что это не подходит много.
Есть идеи, как этого избежать? Есть ли способ сохранить данные в виде строки дольше и просто преобразовать их в набор запросов прямо перед возвратом в запрашивающий код?
class PkListField(Field):
empty_strings_allowed = False
description = "PK List"
default_error_messages = {}
def __init__(self, *args, max_recs=100, max_pk_size=5, sep=',', ordered=None, as_pks=True, model=None, manager='objects', pre_save_func=None, **kwargs):
self.pk_list_model_manager = manager
self.max_recs = max_recs
self.max_pk_size = max_pk_size
self.sep = sep
self.as_pks = as_pks
self.ordered = ordered
self.pre_save_func = pre_save_func
self._pk_list_model = model
if not as_pks and model is None:
raise AttributeError('Model must be specified if not returning as_pks')
if as_pks and ordered is not None and ordered != 'pk':
raise AttributeError('Data can only be ordered by PKs when returning as pks')
kwargs['max_length'] = ((max_pk_size + 2) * max_recs) + 1
kwargs['blank'] = True
kwargs['default'] = sep + sep
super().__init__(*args, **kwargs)
@property
def pk_list_model(self):
if isinstance(self._pk_list_model, str):
self._pk_list_model = apps.get_model(self._pk_list_model)
return self._pk_list_model
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if self.pre_save_func is not None:
kwargs['pre_save_func'] = self.pre_save_func
if self.pk_list_model is not None:
kwargs['model'] = self.pk_list_model
if self.pk_list_model_manager is not 'objects':
kwargs['manager'] = self.pk_list_model_manager
if not self.as_pks:
kwargs['as_pks'] = self.as_pks
if self.max_recs != 100:
kwargs['max_recs'] = self.max_recs
if self.max_pk_size != 5:
kwargs['max_pk_size'] = self.max_pk_size
if self.sep != ',':
kwargs['sep'] = self.sep
if self.ordered is not None:
kwargs['ordered'] = self.ordered
del kwargs['max_length']
del kwargs['blank']
del kwargs['default']
return name, path, args, kwargs
def get_internal_type(self):
return "CharField"
def conv_str_to_python(self, value):
def conv_obj(obj_in):
if obj_in is None:
return []
elif isinstance(obj_in, int):
return [obj_in]
elif isinstance(obj_in, str):
if self.sep in obj_in:
return conv_obj(obj_in.strip().strip(self.sep).split(self.sep))
if obj_in:
return [int(obj_in.strip())]
else:
return []
elif issubclass(obj_in.__class__, models.Model):
return [obj_in.pk]
else:
try:
tmp_ret = []
for item in obj_in:
tmp_ret.extend(conv_obj(item))
return tmp_ret
except TypeError:
raise ValidationError('Invalid object type: %r' % obj_in)
if issubclass(value.__class__, models.QuerySet):
if self.as_pks:
return list(value.values_list('pk', flat=True))
else:
return value
if not value:
value = []
else:
value = conv_obj(value)
if self.as_pks:
if self.ordered == 'pk':
value.sort()
return value
tmp_mgr = getattr(self.pk_list_model, self.pk_list_model_manager)
print('in conv_to_python: value= %r' % value)
# if value:
tmp_ret = tmp_mgr.filter(pk__in=value)
if self.ordered:
tmp_ret = tmp_ret.order_by(*make_list(self.ordered))
print('in conv_to_python: returning filtered = %r' % tmp_ret)
return tmp_ret
# else:
# tmp_ret = tmp_mgr.none()
# print('in conv_to_python: returning none = %r' % tmp_ret)
# return tmp_ret
def to_python(self, value):
print(f'IN ({self.attname}) to_python({repr(value)})')
tmp_ret = self.conv_str_to_python(value)
print(f'OUT ({self.attname}) to_python({repr(tmp_ret)})')
return tmp_ret
def from_db_value(self, value, expression, connection):
print(f'IN ({self.attname}) from_db_value({repr(value)})')
tmp_ret = self.conv_str_to_python(value)
print(f'OUT ({self.attname}) from_db_value({repr(tmp_ret)})')
return tmp_ret
def conv_to_str(self, value, wrapped=True):
def conv_obj(obj_in):
if obj_in is None:
return []
elif isinstance(obj_in, int):
return [str(obj_in)]
elif isinstance(obj_in, str):
if self.sep in obj_in:
return conv_obj(obj_in.strip().strip(self.sep).split(self.sep))
if obj_in:
return [obj_in.strip()]
else:
return []
elif issubclass(obj_in.__class__, models.Model):
return [str(obj_in.pk)]
else:
try:
tmp_ret = []
for item in obj_in:
tmp_ret.extend(conv_obj(item))
return tmp_ret
except TypeError:
raise ValidationError('Invalid object type: %r' % obj_in)
if issubclass(value.__class__, models.QuerySet):
value = list(value.values_list('pk', flat=True))
value = self.sep.join(conv_obj(value))
if wrapped:
return self.sep + value + self.sep
else:
return value
def get_db_prep_value(self, value, connection, prepared=False):
print(f'IN ({self.attname}) get_db_prep_value({repr(value)})')
if not prepared:
value = self.get_prep_value(value)
value = super(PkListField, self).get_db_prep_value(value, connection, prepared=True)
print(f'OUT ({self.attname}) get_db_prep_value({repr(value)})')
return value
def get_prep_value(self, value):
print(f'IN ({self.attname}) get_prep_value({repr(value)})')
value = super().get_prep_value(value)
value = self.conv_to_str(value)
print(f'OUT ({self.attname}) get_prep_value({repr(value)})')
return value
def pre_save(self, model_instance, add):
if self.pre_save_func is not None:
value = super(PkListField, self).pre_save(model_instance, add)
value = self.pre_save_func(value=value, field=self.attname, model=model_instance, add=add)
setattr(model_instance, self.attname, value)
print(f'({self.attname}) OUT pre_Save({repr(value)})')
return value
else:
value = super().pre_save(model_instance, add)
print(f'OUT ({self.attname}) pre_save({repr(value)})')
return value
В настоящее время при тестировании (при попытке создать новый экземпляр модели с использованием этого, пока в модели еще нет данных), я вижу трассировку вроде:
Error
Traceback (most recent call last):
File "C:\Users\strohl\Documents\Project\Who\who_db\who_db_tests\test_model_methods.py", line 101, in setUp
self.M1 = MixinTest.objects.create(name='M1')
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\query.py", line 422, in create
obj.save(force_insert=True, using=self.db)
File "C:\Users\strohl\Documents\Project\Who\who_db\models.py", line 132, in save
super(MixinTest, self).save(*args, **kwargs)
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\base.py", line 741, in save
force_update=force_update, update_fields=update_fields)
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\base.py", line 779, in save_base
force_update, using, update_fields,
File "C:\Users\strohl\Documents\VirtualEnv\Who\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 "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\base.py", line 908, in _do_insert
using=using, raw=raw)
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\query.py", line 1186, in _insert
return query.get_compiler(using=using).execute_sql(return_id)
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\sql\compiler.py", line 1331, in execute_sql
for sql, params in self.as_sql():
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\sql\compiler.py", line 1275, in as_sql
for obj in self.query.objs
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\sql\compiler.py", line 1275, in <listcomp>
for obj in self.query.objs
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\sql\compiler.py", line 1274, in <listcomp>
[self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]
File "C:\Users\strohl\Documents\VirtualEnv\Who\lib\site-packages\django\db\models\sql\compiler.py", line 1205, in prepare_value
if value.contains_column_references:
AttributeError: 'Query' object has no attribute 'contains_column_references'