Issue
I am attempting to update a project to Django 3 and I am running into a strange errors evaluating objects stating that:
TypeError: from_db_value() missing 1 required positional argument: 'context'
I read in the Django docs here: https://docs.djangoproject.com/en/3.0/releases/3.0/#features-removed-in-3-0
Support for the context argument of Field.from_db_value() and Expression.convert_value() is removed.
But I am not understanding what I need to do to fix this, as my call is simply this to get the error...
apps = Application.objects.filter(completed=False, canceled=False)
for app in apps:
print(app)
Is there something I am not getting?
Here is the full traceback, it seems to give me nothing to work with
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'
Update Here is my associated Application Model if need be, although I see nothing in the 3.0 docs that I am declaring wrongly:
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
Here is the inherited SafeDelete Model, built using 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)
My timestamp and UUID inherited models simply add UUID and Timestamp fields to my models in a more universal manner, there is no need to show them.
Here is BaseModel, but it just comes from the SafeDeleteModel so I could add a tiny bit more functionality.
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
And here is SafeDeleteManager for the model manager:
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]
Solution
This ended up being an issue with incompatibilities using django-encrypted-model-fields from: https://pypi.org/project/django-encrypted-model-fields/
Overall the context
arg from in the from_db_value()
method in the code was the issue. I simply added a default value of None
and I was able to update the system to 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
I used this code to pinpoint the issue and loop over all models to see which ones threw the error, for me it was only the fields which were encrypted using that package.
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())
Answered By - ViaTech
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.