diff --git a/s3_file_field/fields.py b/s3_file_field/fields.py index 84241e3..76344cc 100644 --- a/s3_file_field/fields.py +++ b/s3_file_field/fields.py @@ -55,13 +55,11 @@ def id(self) -> str: raise RuntimeError("contribute_to_class has not been called yet on this field.") return str(self) - def contribute_to_class( - self, cls: type[models.Model], name: str, private_only: bool = False - ) -> None: + def contribute_to_class(self, cls, name, **kwargs): # This is executed when the Field is formally added to its containing class. # As a side effect, self.name is set and self.__str__ becomes usable as a unique # identifier for the Field. - super().contribute_to_class(cls, name, private_only=private_only) + super().contribute_to_class(cls, name, **kwargs) if cls.__module__ != "__fake__": # Django's makemigrations iteratively creates fake model instances. # To avoid registration collisions, don't register these. @@ -76,7 +74,7 @@ def formfield( form_class: type[forms.Field] | None = None, choices_form_class: type[forms.ChoiceField] | None = None, **kwargs: Any, - ) -> forms.Field | None: + ) -> forms.Field: """ Return a forms.Field instance for this model field. @@ -100,8 +98,11 @@ def save_form_data(self, instance: models.Model, data) -> None: # However, we don't want the S3FileInput or S3FormFileField to emit a string value, # since that will break most of the default validation. if isinstance(data, S3PlaceholderFile): - data = data.name - super().save_form_data(instance, data) + # For S3PlaceholderFile, directly set the field value to avoid Django 5.1's + # new file validation that expects file objects to have proper attributes + setattr(instance, self.attname, data.name) + else: + super().save_form_data(instance, data) def check(self, **kwargs: Any) -> list[CheckMessage]: return [ diff --git a/tests/test_fields.py b/tests/test_fields.py index 0d3e2bd..3f0ab16 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -5,6 +5,7 @@ import pytest from test_app.models import Resource +from s3_file_field.widgets import S3PlaceholderFile @pytest.mark.django_db() @@ -62,3 +63,22 @@ def test_fields_clean_empty() -> None: def test_fields_check_success(resource: Resource) -> None: assert resource._meta.get_field("blob").check() == [] + + +def test_s3_placeholder_file_save_form_data() -> None: + resource = Resource() + blob_field = resource._meta.get_field("blob") + placeholder_file = S3PlaceholderFile(name="test-file.txt") + blob_field.save_form_data(resource, placeholder_file) + assert getattr(resource, blob_field.attname) == "test-file.txt" + + +@pytest.mark.django_db() +def test_s3_placeholder_file_save_form_data_with_save() -> None: + resource = Resource() + blob_field = resource._meta.get_field("blob") + placeholder_file = S3PlaceholderFile(name="test-file-save.txt") + blob_field.save_form_data(resource, placeholder_file) + resource.save() + resource.refresh_from_db() + assert resource.blob.name == "test-file-save.txt"