Проблемы с настраиваемым полем, которое хранит pks и возвращает набор запросов - PullRequest
0 голосов
/ 12 июня 2019

Я попытался создать настраиваемое поле, в котором будет храниться строка, представляющая собой список 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'

...