Updating the file contents of a Django FileField during upload results in I/O error

I’m trying to encrypt the contents of a file that is being uploaded. This is the relevant code snippet:

class AppFile(models.Model):
    app_file = models.FileField(upload_to=upload_to, validators=[validate_file_size])
    encrypted_data_key = models.CharField(max_length=500, blank=True)

    def encrypt_file_with_data_key(self, data_key):
        cipher = Fernet(data_key)
        with self.app_file.open(mode='rb') as file:
            file_data = file.read()
            encrypted_data = cipher.encrypt(file_data)
        with self.app_file.open(mode='wb') as encrypted_file:
            encrypted_file.write(encrypted_data)

    def save(self, *args, **kwargs):
        if self._state.adding is True:

            # New image being uploaded
            encrypted_data_key, data_key = self.generate_data_key_from_vault()
            self.encrypted_data_key = encrypted_data_key

            # Encrypt the uploaded image file
            self.encrypt_file_with_data_key(data_key)

        super().save(args, kwargs)

I prefer this approach as this is agnostic of the StorageProvider being used. I also want to avoid detaching the file to a temporary folder, and re-attach it after encryption.

However, this results in the following error:

Traceback (most recent call last):
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/views/generic/base.py", line 84, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/views/generic/base.py", line 119, in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/views/generic/edit.py", line 184, in post
    return super().post(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/views/generic/edit.py", line 153, in post
    return self.form_valid(form)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/contrib/messages/views.py", line 12, in form_valid
    response = super().form_valid(form)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/views/generic/edit.py", line 135, in form_valid
    self.object = form.save()
                  ^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/forms/models.py", line 548, in save
    self.instance.save()
  File "/Users/jeroenjacobs/PycharmProjects/myapp/mainapp/models.py", line 90, in save
    self.encrypt_file_with_data_key(data_key)
  File "/Users/jeroenjacobs/PycharmProjects/myapp/mainapp/models.py", line 77, in encrypt_file_with_data_key
    with self.app_file.open(mode='wb') as encrypted_file:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/db/models/fields/files.py", line 80, in open
    self.file.open(mode)
  File "/Users/jeroenjacobs/.pyenv/versions/myapp/lib/python3.11/site-packages/django/core/files/uploadedfile.py", line 115, in open
    self.file.seek(0)
ValueError: I/O operation on closed file.

Reading the contents doesn’t seem to be a problem, but writing seems to generate an error, despite open being called.

I believe this is actually a bug in Django, but I have a hard time convincing the Django devs of this…

The issue is that the FieldFile proxy provided by the FileField in your view is not a file object and does not directly support all features of a file.

What I would suggest is one of: