Валидация Django max_length для BinaryField вызывает KeyError в переводе __init__.py

У меня есть простая модель, что-то вроде

class Notenbild(models.Model):
    bild_data = models.BinaryField(max_length=500000, editable=True)

В admin.py

class BinaryFieldWithUpload(forms.FileField):
    def __init__(self, *, max_length=None, allow_empty_file=False, **kwargs):
        super().__init__(max_length=max_length, allow_empty_file=allow_empty_file, **kwargs)

    def to_python(self, data):
        data = super().to_python(data)
        if data:
            image = Image.open(data)
            # some more processing with the image which I omitted here
            byte_array = io.BytesIO()
            image.save(byte_array, format='PNG')
            return byte_array.getvalue()
        return None

    def widget_attrs(self, widget):
        attrs = super().widget_attrs(widget)
        if isinstance(widget, FileInput) and "accept" not in widget.attrs:
            attrs.setdefault("accept", "image/*")
        return attrs


@admin.register(Notenbild)
class NotenbildAdmin(admin.ModelAdmin):
    fields = [
        'bild_data',
        'vorschau',
    ]
    readonly_fields = ['vorschau']

    formfield_overrides = {
        models.BinaryField: {'form_class': BinaryFieldWithUpload},
    }

    @admin.display(description='Bild (Vorschau)')
    def vorschau(self, notenbild: Notenbild):
        encoded_image = base64.b64encode(notenbild.bild_data).decode('utf-8')
        return format_html(
            f'<p>{len(notenbild.bild_data)} bytes</p>'
            f'<img src="data:image/png;base64,{encoded_image}" style="max-width:40rem; max-height:16rem" />'
        )

, который отлично работает для сохранения изображений через интерфейс администратора, которые соответствуют ограничениям по размеру. Однако при попытке сохранить файл, размер которого превышает установленный лимит, я получаю очень странную ошибку:

KeyError at /admin/library/notenbild/368/change/
"Your dictionary lacks key 'max'. Please provide it, because it is required to determine whether string is singular or plural."

Django Version: 5.1.1
Exception Location: /Users/alex/Repositories/ekd-cms/venv/lib/python3.12/site-packages/django/utils/translation/__init__.py, line 130, in _get_number_value

, при этом полный трассировщик стека будет


Traceback (most recent call last):
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/utils/translation/__init__.py", line 128, in _get_number_value
    return values[number]
           ^^^^^^^^^^^^^^

During handling of the above exception ('max'), another exception occurred:
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/debug_toolbar/middleware.py", line 92, in __call__
    panel.generate_stats(request, response)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/debug_toolbar/panels/templates/panel.py", line 201, in generate_stats
    template_data["context_list"] = self.process_context_list(
                                    
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/debug_toolbar/panels/templates/panel.py", line 134, in process_context_list
    if key_values == context_layer:
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/forms/utils.py", line 192, in __eq__
    return list(self) == other
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/forms/utils.py", line 192, in __eq__
    return list(self) == other
           ^^^^^^^^^^
  File "<frozen _collections_abc>", line 1026, in __iter__
    <source code not available>
                        ^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/forms/utils.py", line 197, in __getitem__
    return next(iter(error))
           ^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/core/exceptions.py", line 210, in __iter__
    message %= error.params
    ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/utils/functional.py", line 167, in __mod__
    return self.__cast() % other
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/utils/translation/__init__.py", line 148, in __mod__
    number_value = self._get_number_value(rhs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alex/Repositories/my-project/venv/lib/python3.12/site-packages/django/utils/translation/__init__.py", line 130, in _get_number_value
    raise KeyError(
    ^

, что не имеет для меня никакого смысла. Валидация правильно отловила недопустимые данные, но, похоже, это может быть ошибкой Django? Или я неправильно использую эту штуку?

Я пробовал добавить свой собственный валидатор к модели:

from django.core.validators import BaseValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError

@deconstructible
class MaxFileSizeValidator(BaseValidator):
    message = _("Ensure the file is less than or equal to %(limit_value)s.")
    code = "limit_value"

    def __init__(self, limit_value, message=None):
        super().__init__(limit_value, message)
        self.limit_value = limit_value
        if message:
            self.message = message

    def __call__(self, value):
        cleaned = self.clean(value)
        limit_value = (
            self.limit_value() if callable(self.limit_value) else self.limit_value
        )
        params = {"limit_value": limit_value, "show_value": cleaned, "value": value}
        if self.compare(cleaned, limit_value):
            raise ValidationError(self.message, code=self.code, params=params)

    def compare(self, a, b):
        return len(a) > b

и

class Notenbild(models.Model):
    bild_data = models.BinaryField(max_length=500000, editable=True, validators=[MaxFileSizeValidator(500000)])

но как бы я ни пытался, метод перевода __init__.py продолжает выдавать эту ошибку, которая не имеет для меня большого смысла. Есть идеи?

Вернуться на верх