Django encrypted field + unique

I want to use django_cryptography.fields.encrypt in my model to have a field:

 sin = encrypt(models.IntegerField(
        validators=[MinValueValidator(10000000), MaxValueValidator(99999999)],
        default="",
        unique=True,
    ))

However, using unique with encrypt throws:

django.core.exceptions.FieldError: Unsupported lookup 'exact' for EncryptedBigIntegerField or join on the field not permitted, perhaps you meant exact or iexact?

This is on django 5.1. Any ideas or suggestions to fix this, maybe another way to encrypt the field?

The django_cryptography project only shows as being tested up through Django 4.2 and python 3.11. It doesn’t show as being compatible with Django 5.1.

Also, according to Django 5.x support? · Issue #115 · georgemarshall/django-cryptography · GitHub, this appears like it may be an abandoned project. (The last public release was version 1.1 from 2022.)

You could fork the project and bring it up to date.

Check out Django Packages : Encryption for some other possible options.

thanks for the response. I have also tried GitHub - chrisclark/django-cryptography: Easily encrypt data in Django and django-cryptography-5 which are both forks of the original django-cryptography though with not too many commits, however, the behaviour is the same.

Interestingly, the error occurs only when using the CreateView. Creating an instance by extracting the data from an uploaded file or via the shell, the constraint “unique” is completely ignored.

I will have a look at the django packages and we will see what I will come up with. I will keep this thread updated.

Please provide the full error message with the traceback that you are receiving.

voila:

Internal Server Error: /person/add
Traceback (most recent call last):
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/views/generic/base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/views/generic/edit.py", line 182, in post
    return super().post(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/views/generic/edit.py", line 150, in post
    if form.is_valid():
       ^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/forms/forms.py", line 197, in is_valid
    return self.is_bound and not self.errors
                                 ^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/forms/forms.py", line 192, in errors
    self.full_clean()
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/forms/forms.py", line 327, in full_clean
    self._post_clean()
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/forms/models.py", line 504, in _post_clean
    self.validate_unique()
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/forms/models.py", line 513, in validate_unique
    self.instance.validate_unique(exclude=exclude)
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/base.py", line 1378, in validate_unique
    errors = self._perform_unique_checks(unique_checks)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/base.py", line 1477, in _perform_unique_checks
    qs = model_class._default_manager.filter(**lookup_kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/query.py", line 1476, in filter
    return self._filter_or_exclude(False, args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/query.py", line 1494, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/query.py", line 1501, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1609, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1641, in _add_q
    child_clause, needed_inner = self.build_filter(
                                 ^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1555, in build_filter
    condition = self.build_lookup(lookups, col, value)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1379, in build_lookup
    lhs = self.try_transform(lhs, lookup_name)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/zpool/work/dev/django/django-tinker/py311-dj51-pdf/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1423, in try_transform
    raise FieldError(
django.core.exceptions.FieldError: Unsupported lookup 'exact' for EncryptedIntegerField or join on the field not permitted, perhaps you meant exact or iexact?
[19/Feb/2025 14:43:25] "POST /person/add HTTP/1.1" 500 161198

Thanks. (I wanted to acknowledge your post.)

This one looks interesting. Unfortunately, it’s going to take me a little bit of time to dig in to.

Before I dive into this in more detail, is there any other potential relevant information?

For example, are you using a custom Manager class for this model?

Are you using any mixins in your model class definition?

Are you using any mixins in your CreateView? Any method decorators?

Are you overriding any of the view methods?

(It may be helpful if you posted the complete model and view.)

it’s a very basic model and view, no manager/mixins/decorated functions:

class Person(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    sin = encrypt(models.IntegerField(
            validators=[MinValueValidator(1000000000), MaxValueValidator(9999999999)],
            unique=True,
            default="",
        ))

    pnr = encrypt(models.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(1000)],
        default="",
        unique=True,
    ))
    titel = models.CharField(max_length=16, blank=True, default="")
    firstname = models.CharField(max_length=18, default="")
    lastname = models.CharField(max_length=24, default="")
    birthdate = encrypt(models.DateField(blank=True, default=""))
    email = models.EmailField(blank=True, default="")
    phone = encrypt(models.CharField(max_length=18, blank=True, default=""))
    street = encrypt(models.CharField(max_length=28, blank=True, default=""))
    zip = encrypt(models.IntegerField(
            validators=[MinValueValidator(1000), MaxValueValidator(99999)],
            blank=True,
            default="",
    ))
    city = encrypt(models.CharField(max_length=28, blank=True, default=""))
    country = models.CharField(max_length=16, blank=True, default="")
    docpassword = encrypt(
        models.CharField(
            max_length=20,
            validators=[MinLengthValidator(12)],
            default=secrets.token_urlsafe(32)[:20],
        )
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "Personen"
        ordering = ["lastname"]

    def __str__(self):
        return " ".join([str(self.firstname), str(self.lastname)])

    def get_absolute_url(self):
        return reverse("person_detail", kwargs={"pk": self.pk})
class PersonAddView(CreateView):
    model = Person
    template_name = "person_add.html"
    fields = (
       "sin",
       "pnr",
       "titel",
       "firstname",
       "lastname",
       "birthdate",
       "email",
       "phone",
       "street",
       "zip",
       "city",
       "country",
       "docpassword"
    )
1 Like

One thing I see right off-hand is that you’re trying to assign a null string as the default value of an integer field. I don’t know if it’s at all related to this issue, but it’s something that should be fixed.

What database engine are you using?

I use PostgreSQL, and while I’m also getting errors, it’s not the same errors as you’re getting. Since we’re both seeing errors in the ORM itself, I’m thinking that the difference may be related to the database being used.