首页 > 解决方案 > 由于 from_db_value() 导致的 Django 3 对象评估错误

问题描述

我正在尝试将项目更新为 Django 3,但在评估对象时遇到了一个奇怪的错误,指出:

TypeError: from_db_value() missing 1 required positional argument: 'context'

我在这里阅读了 Django 文档:https ://docs.djangoproject.com/en/3.0/releases/3.0/#features-removed-in-3-0

删除了对 Field.from_db_value() 和 Expression.convert_value() 的上下文参数的支持。

但我不明白我需要做什么来解决这个问题,因为我的电话只是为了得到错误......

apps = Application.objects.filter(completed=False, canceled=False)
for app in apps:
    print(app)

有什么我没有得到的吗?

这是完整的追溯,它似乎没有给我任何工作

Traceback (most recent call last):
web_1       |   File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
web_1       |     response = get_response(request)
web_1       |   File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
web_1       |     response = self.process_exception_by_middleware(e, request)
web_1       |   File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
web_1       |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
web_1       |   File "/code/apps/users/decorators.py", line 23, in _wrapped_view
web_1       |     return view_func(request, *args, **kwargs)
web_1       |   File "/code/apps/users/decorators.py", line 23, in _wrapped_view
web_1       |     return view_func(request, *args, **kwargs)
web_1       |   File "/code/apps/reports/views/property_specific/availability_views.py", line 26, in availability_units_report
web_1       |     for app in apps:
web_1       |   File "/usr/local/lib/python3.7/site-packages/django/db/models/query.py", line 276, in __iter__
web_1       |     self._fetch_all()
web_1       |   File "/usr/local/lib/python3.7/site-packages/django/db/models/query.py", line 1261, in _fetch_all
web_1       |     self._result_cache = list(self._iterable_class(self))
web_1       |   File "/usr/local/lib/python3.7/site-packages/django/db/models/query.py", line 74, in __iter__
web_1       |     for row in compiler.results_iter(results):
web_1       |   File "/usr/local/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1095, in apply_converters
web_1       |     value = converter(value, expression, connection)
web_1       | TypeError: from_db_value() missing 1 required positional argument: 'context'

更新 如果需要,这是我的关联应用程序模型,尽管我在 3.0 文档中没有看到我错误地声明的任何内容:

class Application(SafeDeleteModel, TimestampModel, UUID):
    class Meta:
        permissions = (
            ('view_ssn', 'Can view ssn'),
            ('change_ssn', 'Can change ssn'),
            ('waive_app_fee', 'Can waive app fee'),
            ('change_processing_management', 'Can change processing for management'),
            ('change_processing_compliance', 'Can change processing for compliance'),
            ('change_preleasing', 'Can change preleasing'),
            ('cancel_application', 'Can cancel application'),
            ('occupy_unit', 'Can occupy a unit'),
        )

    id = models.AutoField(primary_key=True)

    ...
    # I have no __str__ method for this model

这是继承的 SafeDelete 模型,使用django-safedelete https://github.com/makinacorpus/django-safedelete构建

from safedelete.models import SafeDeleteModel as BaseModel

class SafeDeleteModel(BaseModel):
    _safedelete_policy = SOFT_DELETE_CASCADE

    class Meta:
        abstract = True
        default_permissions = ('add', 'change', 'delete', 'view', 'undelete')

    def exclude(self, value):
        Logger.info(self)

    def update(self, new_dict):
        """
        Update object with form field dictionary on submission.
        :param new_dict:
        """
        fields = [x.name for x in self._meta.get_fields()]
        for key, value in new_dict.items():
            if key in fields:
                setattr(self, key, value)

我的时间戳和 UUID 继承模型只是以更通用的方式将 UUID 和时间戳字段添加到我的模型中,无需显示它们。

这是 BaseModel,但它只是来自 SafeDeleteModel,因此我可以添加更多功能。

class SafeDeleteModel(models.Model):
    """Abstract safedelete-ready model.

    .. note::
        To create your safedelete-ready models, you have to make them inherit from this model.

    :attribute deleted:
        DateTimeField set to the moment the object was deleted. Is set to
        ``None`` if the object has not been deleted.

    :attribute _safedelete_policy: define what happens when you delete an object.
        It can be one of ``HARD_DELETE``, ``SOFT_DELETE``, ``SOFT_DELETE_CASCADE``, ``NO_DELETE`` and ``HARD_DELETE_NOCASCADE``.
        Defaults to ``SOFT_DELETE``.

        >>> class MyModel(SafeDeleteModel):
        ...     _safedelete_policy = SOFT_DELETE
        ...     my_field = models.TextField()
        ...
        >>> # Now you have your model (with its ``deleted`` field, and custom manager and delete method)

    :attribute objects:
        The :class:`safedelete.managers.SafeDeleteManager` that returns the non-deleted models.

    :attribute all_objects:
        The :class:`safedelete.managers.SafeDeleteAllManager` that returns the all models (non-deleted and soft-deleted).

    :attribute deleted_objects:
        The :class:`safedelete.managers.SafeDeleteDeletedManager` that returns the soft-deleted models.
    """

    _safedelete_policy = SOFT_DELETE

    deleted = models.DateTimeField(editable=False, null=True)

    objects = SafeDeleteManager()
    all_objects = SafeDeleteAllManager()
    deleted_objects = SafeDeleteDeletedManager()

    class Meta:
        abstract = True

    def save(self, keep_deleted=False, **kwargs):
        """Save an object, un-deleting it if it was deleted.

        Args:
            keep_deleted: Do not undelete the model if soft-deleted. (default: {False})
            kwargs: Passed onto :func:`save`.

        .. note::
            Undeletes soft-deleted models by default.
        """

        # undelete signal has to happen here (and not in undelete)
        # in order to catch the case where a deleted model becomes
        # implicitly undeleted on-save.  If someone manually nulls out
        # deleted, it'll bypass this logic, which I think is fine, because
        # otherwise we'd have to shadow field changes to handle that case.

        was_undeleted = False
        if not keep_deleted:
            if self.deleted and self.pk:
                was_undeleted = True
            self.deleted = None

        super(SafeDeleteModel, self).save(**kwargs)

        if was_undeleted:
            # send undelete signal
            using = kwargs.get('using') or router.db_for_write(self.__class__, instance=self)
            post_undelete.send(sender=self.__class__, instance=self, using=using)

    def undelete(self, force_policy=None, **kwargs):
        """Undelete a soft-deleted model.

        Args:
            force_policy: Force a specific undelete policy. (default: {None})
            kwargs: Passed onto :func:`save`.

        .. note::
            Will raise a :class:`AssertionError` if the model was not soft-deleted.
        """
        current_policy = force_policy or self._safedelete_policy

        assert self.deleted
        self.save(keep_deleted=False, **kwargs)

        if current_policy == SOFT_DELETE_CASCADE:
            for related in related_objects(self):
                if is_safedelete_cls(related.__class__) and related.deleted:
                    related.undelete()

    def delete(self, force_policy=None, **kwargs):
        """Overrides Django's delete behaviour based on the model's delete policy.

        Args:
            force_policy: Force a specific delete policy. (default: {None})
            kwargs: Passed onto :func:`save` if soft deleted.
        """
        current_policy = self._safedelete_policy if (force_policy is None) else force_policy

        if current_policy == NO_DELETE:

            # Don't do anything.
            return

        elif current_policy == SOFT_DELETE:

            # Only soft-delete the object, marking it as deleted.
            self.deleted = timezone.now()
            using = kwargs.get('using') or router.db_for_write(self.__class__, instance=self)
            # send pre_softdelete signal
            pre_softdelete.send(sender=self.__class__, instance=self, using=using)
            super(SafeDeleteModel, self).save(**kwargs)
            # send softdelete signal
            post_softdelete.send(sender=self.__class__, instance=self, using=using)

        elif current_policy == HARD_DELETE:

            # Normally hard-delete the object.
            super(SafeDeleteModel, self).delete()

        elif current_policy == HARD_DELETE_NOCASCADE:

            # Hard-delete the object only if nothing would be deleted with it

            if not can_hard_delete(self):
                self.delete(force_policy=SOFT_DELETE, **kwargs)
            else:
                self.delete(force_policy=HARD_DELETE, **kwargs)

        elif current_policy == SOFT_DELETE_CASCADE:
            # Soft-delete on related objects before
            for related in related_objects(self):
                if is_safedelete_cls(related.__class__) and not related.deleted:
                    related.delete(force_policy=SOFT_DELETE, **kwargs)
            # soft-delete the object
            self.delete(force_policy=SOFT_DELETE, **kwargs)

    @classmethod
    def has_unique_fields(cls):
        """Checks if one of the fields of this model has a unique constraint set (unique=True)

        Args:
            model: Model instance to check
        """
        for field in cls._meta.fields:
            if field._unique:
                return True
        return False

    # We need to overwrite this check to ensure uniqueness is also checked
    # against "deleted" (but still in db) objects.
    # FIXME: Better/cleaner way ?
    def _perform_unique_checks(self, unique_checks):
        errors = {}

        for model_class, unique_check in unique_checks:
            lookup_kwargs = {}
            for field_name in unique_check:
                f = self._meta.get_field(field_name)
                lookup_value = getattr(self, f.attname)
                if lookup_value is None:
                    continue
                if f.primary_key and not self._state.adding:
                    continue
                lookup_kwargs[str(field_name)] = lookup_value
            if len(unique_check) != len(lookup_kwargs):
                continue

            # This is the changed line
            if hasattr(model_class, 'all_objects'):
                qs = model_class.all_objects.filter(**lookup_kwargs)
            else:
                qs = model_class._default_manager.filter(**lookup_kwargs)

            model_class_pk = self._get_pk_val(model_class._meta)
            if not self._state.adding and model_class_pk is not None:
                qs = qs.exclude(pk=model_class_pk)
            if qs.exists():
                if len(unique_check) == 1:
                    key = unique_check[0]
                else:
                    key = models.base.NON_FIELD_ERRORS
                errors.setdefault(key, []).append(
                    self.unique_error_message(model_class, unique_check)
                )
        return errors

这是模型管理器的 SafeDeleteManager:

class SafeDeleteManager(models.Manager):
    """Default manager for the SafeDeleteModel.

    If _safedelete_visibility == DELETED_VISIBLE_BY_PK, the manager can returns deleted
    objects if they are accessed by primary key.

    :attribute _safedelete_visibility: define what happens when you query masked objects.
        It can be one of ``DELETED_INVISIBLE`` and ``DELETED_VISIBLE_BY_PK``.
        Defaults to ``DELETED_INVISIBLE``.

        >>> from safedelete.models import SafeDeleteModel
        >>> from safedelete.managers import SafeDeleteManager
        >>> class MyModelManager(SafeDeleteManager):
        ...     _safedelete_visibility = DELETED_VISIBLE_BY_PK
        ...
        >>> class MyModel(SafeDeleteModel):
        ...     _safedelete_policy = SOFT_DELETE
        ...     my_field = models.TextField()
        ...     objects = MyModelManager()
        ...
        >>>

    :attribute _queryset_class: define which class for queryset should be used
        This attribute allows to add custom filters for both deleted and not
        deleted objects. It is ``SafeDeleteQueryset`` by default.
        Custom queryset classes should be inherited from ``SafeDeleteQueryset``.
    """

    _safedelete_visibility = DELETED_INVISIBLE
    _safedelete_visibility_field = 'pk'
    _queryset_class = SafeDeleteQueryset

    def __init__(self, queryset_class=None, *args, **kwargs):
        """Hook for setting custom ``_queryset_class``.

        Example:

            class CustomQueryset(models.QuerySet):
                pass

            class MyModel(models.Model):
                my_field = models.TextField()

                objects = SafeDeleteManager(CustomQuerySet)
        """
        super(SafeDeleteManager, self).__init__(*args, **kwargs)
        if queryset_class:
            self._queryset_class = queryset_class

    def get_queryset(self):
        # Backwards compatibility, no need to move options to QuerySet.
        queryset = self._queryset_class(self.model, using=self._db)
        queryset._safedelete_visibility = self._safedelete_visibility
        queryset._safedelete_visibility_field = self._safedelete_visibility_field
        return queryset

    def all_with_deleted(self):
        """Show all models including the soft deleted models.

        .. note::
            This is useful for related managers as those don't have access to
            ``all_objects``.
        """
        return self.all(
            force_visibility=DELETED_VISIBLE
        )

    def deleted_only(self):
        """Only show the soft deleted models.

        .. note::
            This is useful for related managers as those don't have access to
            ``deleted_objects``.
        """
        return self.all(
            force_visibility=DELETED_ONLY_VISIBLE
        )

    def all(self, **kwargs):
        """Pass kwargs to ``SafeDeleteQuerySet.all()``.

        Args:
            force_visibility: Show deleted models. (default: {None})

        .. note::
            The ``force_visibility`` argument is meant for related managers when no
            other managers like ``all_objects`` or ``deleted_objects`` are available.
        """
        force_visibility = kwargs.pop('force_visibility', None)

        # We don't call all() on the queryset, see https://github.com/makinacorpus/django-safedelete/issues/81
        qs = self.get_queryset()
        if force_visibility is not None:
            qs._safedelete_force_visibility = force_visibility
        return qs

    def update_or_create(self, defaults=None, **kwargs):
        """See :func:`~django.db.models.Query.update_or_create.`.

        Change to regular djangoesk function:
        Regular update_or_create() fails on soft-deleted, existing record with unique constraint on non-id field
        If object is soft-deleted we don't update-or-create it but reset the deleted field to None.
        So the object is visible again like a create in any other case.

        Attention: If the object is "revived" from a soft-deleted state the created return value will
        still be false because the object is technically not created unless you set
        SAFE_DELETE_INTERPRET_UNDELETED_OBJECTS_AS_CREATED = True in the django settings.

        Args:
            defaults: Dict with defaults to update/create model instance with
            kwargs: Attributes to lookup model instance with
        """

        # Check if one of the model fields contains a unique constraint
        revived_soft_deleted_object = False
        if self.model.has_unique_fields():
            # Check if object is already soft-deleted
            deleted_object = self.all_with_deleted().filter(**kwargs).exclude(deleted=None).first()

            # If object is soft-deleted, reset delete-state...
            if deleted_object and deleted_object._safedelete_policy in self.get_soft_delete_policies():
                deleted_object.deleted = None
                deleted_object.save()
                revived_soft_deleted_object = True

        # Do the standard logic
        obj, created = super(SafeDeleteManager, self).update_or_create(defaults, **kwargs)

        # If object was soft-deleted and is "revived" and settings flag is True, show object as created
        if revived_soft_deleted_object and \
                getattr(settings, 'SAFE_DELETE_INTERPRET_UNDELETED_OBJECTS_AS_CREATED', False):
            created = True

        return obj, created

    @staticmethod
    def get_soft_delete_policies():
        """Returns all stati which stand for some kind of soft-delete"""
        return [SOFT_DELETE, SOFT_DELETE_CASCADE]

标签: pythonpython-3.xdjango-modelsdjango-3.0

解决方案


这最终成为使用django-encrypted-model-fields不兼容的问题:https://pypi.org/project/django-encrypted-model-fields/

总的来说,代码中方法中的contextargfrom_db_value()是问题所在。我只是添加了一个默认值,None我就能够将系统更新到 Django 3。

from __future__ import unicode_literals
import django.db
import django.db.models
from django.utils.functional import cached_property
from django.core import validators
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
import cryptography.fernet
# from django.utils.six import PY2, string_types # no longer supported, use six
from six import string_types, PY2, text_type

class EncryptedMixin(object):
    def to_python(self, value):
        if value is None:
            return value

        if isinstance(value, (bytes, string_types[0])):
            if isinstance(value, bytes):
                value = value.decode('utf-8')
            try:
                value = decrypt_str(value)
            except cryptography.fernet.InvalidToken:
                pass

        return super(EncryptedMixin, self).to_python(value)

    # ---- ISSUE WAS IN THIS SIGNATURE
    def from_db_value(self, value, expression, connection, context=None):
        return self.to_python(value)

    def get_db_prep_save(self, value, connection):
        value = super(EncryptedMixin, self).get_db_prep_save(value, connection)

        if value is None:
            return value
        if PY2:
            return encrypt_str(text_type(value))
        # decode the encrypted value to a unicode string, else this breaks in pgsql
        return (encrypt_str(str(value))).decode('utf-8')

    def get_internal_type(self):
        return "TextField"

    def deconstruct(self):
        name, path, args, kwargs = super(EncryptedMixin, self).deconstruct()

        if 'max_length' in kwargs:
            del kwargs['max_length']

        return name, path, args, kwargs

我使用此代码查明问题并遍历所有模型以查看哪些模型引发了错误,对我而言,只有使用该包加密的字段。

    from django.apps import apps
    for model in apps.get_models():
        app_label = ContentType.objects.get_for_model(model).app_label
        model_name = model.__name__
        model = apps.get_model(app_label, model_name)
        print(model)
        # if model_name not in ['Resident', 'Application', 'Children', 'CoApplicant']:
        print(model.objects.first())


推荐阅读