无法在 Django Admin 中将 InMemoryUploadedFile 保存到 S3

发布于 2025-01-09 03:02:24 字数 1402 浏览 1 评论 0 原文

我正在使用 django-storage 包,并尝试一次上传多个图像。因此,我覆盖了 ModelAdmin 中的 add_view 和 save_model 方法,以便删除原始图像字段并使用模板 HTML 中给出的自定义图像字段(在输入标记中带有 multiple 标志):

MODELS.PY

class Media(AbstractCreatedUpdatedDateMixin):
    uuid = models.UUIDField(unique=True, default=uuid4, editable=False, db_index=True)
    user = models.ForeignKey(User, related_name="uploaded_media", on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    image = models.ImageField(upload_to=uuid_directory_path)


ADMIN.PY

class MediaModelAdmin(admin.ModelAdmin):
    def add_view(self, request, form_url='', extra_context=None):
        self.exclude = ('image', "is_approved")
        extra_context = extra_context or {}
        extra_context['show_save_and_add_another'] = False
        extra_context['show_save_and_continue'] = False
        return super().add_view(request, form_url, extra_context)

    def save_model(self, request, obj, form, change):
        for file in request.FILES.values():
            obj.user = User.objects.filter(id=request.POST.get("user")).first()
            obj.title = request.POST.get("title")
            obj.image.save(file.name, file.file)
            obj.save()

它正确上传到S3,但它不会保存实例并抛出此错误:

TypeError at /admin/media/media/add/
expected string or bytes-like object

我不确定这里出了什么问题,也许上传尚未完成,因此数据库事务已回滚,但我无法弄清楚出去做什么。

I'm using the django-storage package, and trying to upload multiple images at once. So I overwritten the add_view and save_model methods in ModelAdmin, in order to remove the original image field and use a custom one (with a multiple flag in the input tag) given in the template HTML:

MODELS.PY

class Media(AbstractCreatedUpdatedDateMixin):
    uuid = models.UUIDField(unique=True, default=uuid4, editable=False, db_index=True)
    user = models.ForeignKey(User, related_name="uploaded_media", on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    image = models.ImageField(upload_to=uuid_directory_path)


ADMIN.PY

class MediaModelAdmin(admin.ModelAdmin):
    def add_view(self, request, form_url='', extra_context=None):
        self.exclude = ('image', "is_approved")
        extra_context = extra_context or {}
        extra_context['show_save_and_add_another'] = False
        extra_context['show_save_and_continue'] = False
        return super().add_view(request, form_url, extra_context)

    def save_model(self, request, obj, form, change):
        for file in request.FILES.values():
            obj.user = User.objects.filter(id=request.POST.get("user")).first()
            obj.title = request.POST.get("title")
            obj.image.save(file.name, file.file)
            obj.save()

It uploads correctly to S3, but it doesn't save the instance and throws this error:

TypeError at /admin/media/media/add/
expected string or bytes-like object

I'm not sure what is wrong here, maybe the fact that the upload is not done yet so the DB transaction is rolled back, but I can't figure out what do to.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

掀纱窥君容 2025-01-16 03:02:24

尝试改变这一点

obj.image.save(文件名, 文件名)

obj.image.save(file.name, file)

Try to change this

obj.image.save(file.name, file.file)

to

obj.image.save(file.name, file)
手长情犹 2025-01-16 03:02:24

一段时间后我发现了这个问题。我没有在 request.FILES.values() 中使用 for file 甚至在 save_model 中设置它,而是创建了一个单独的表单(代码如下)并覆盖了 add_view 方法中的 >self.form 属性。

--------------
admin.py
--------------
...

class MediaModelAdmin(admin.ModelAdmin):
...

    def add_view(self, request, form_url='', extra_context=None):
        self.form = BulkUploadMediaAdminValidationForm
        return super().add_view(request, form_url, extra_context)
...

--------------
forms.py
--------------
...

class BulkUploadMediaAdminValidationForm(forms.ModelForm):
    user = forms.ModelChoiceField(User.objects.filter(is_active=True))
    title = forms.CharField(max_length=255, required=True)
    category = forms.ChoiceField(choices=ContentType.choices(), required=True)
    location = forms.PointField(widget=LocationWidget(), label=u"Location (lng, lat)", required=True)
    image = forms.ImageField(
        widget=PreviewMultipleFileInput(attrs={"multiple": True}), # this is forms.ClearableFileInput with customized CSS and JS to show file thumbnails
        label=_(u"Image - select multiple with Ctrl or Shift"),
    )

    class Meta:
        model = Media
        fields = [
            "user",
            "title",
            "category",
            "location",
            "image",
            "taken_at",
        ]

    def save(self, *args, **kwargs):
        data_dict = self.cleaned_data.copy()
        data_dict["is_approved"] = timezone.now()
        del data_dict["image"]
        instance = None
        for f in self.files.getlist("image"):
            instance = Media(**data_dict)
            instance.image.save(f.name, f.file)
            instance.save()
        return instance

    def save_m2m(self):
        pass

这里的主要问题是尝试访问 request.FILES.values()。我不知道为什么,但如果您尝试访问文件列表,它只会返回一个文件。
您需要使用 self.files.getlist("image") 才能正确检索文件数组。

这发生在 Django 3.2 中,所以我不确定它是否在新版本中得到修复。

After some time I found out the issue. Instead of using for file in request.FILES.values() or even setting this in the save_model I've created a separate Form (code below) and overwrited the self.form attribute in the add_view method of the ModelAdmin class.

--------------
admin.py
--------------
...

class MediaModelAdmin(admin.ModelAdmin):
...

    def add_view(self, request, form_url='', extra_context=None):
        self.form = BulkUploadMediaAdminValidationForm
        return super().add_view(request, form_url, extra_context)
...

--------------
forms.py
--------------
...

class BulkUploadMediaAdminValidationForm(forms.ModelForm):
    user = forms.ModelChoiceField(User.objects.filter(is_active=True))
    title = forms.CharField(max_length=255, required=True)
    category = forms.ChoiceField(choices=ContentType.choices(), required=True)
    location = forms.PointField(widget=LocationWidget(), label=u"Location (lng, lat)", required=True)
    image = forms.ImageField(
        widget=PreviewMultipleFileInput(attrs={"multiple": True}), # this is forms.ClearableFileInput with customized CSS and JS to show file thumbnails
        label=_(u"Image - select multiple with Ctrl or Shift"),
    )

    class Meta:
        model = Media
        fields = [
            "user",
            "title",
            "category",
            "location",
            "image",
            "taken_at",
        ]

    def save(self, *args, **kwargs):
        data_dict = self.cleaned_data.copy()
        data_dict["is_approved"] = timezone.now()
        del data_dict["image"]
        instance = None
        for f in self.files.getlist("image"):
            instance = Media(**data_dict)
            instance.image.save(f.name, f.file)
            instance.save()
        return instance

    def save_m2m(self):
        pass

The main issue here was trying to access request.FILES.values(). I'm not sure why but if you try to access a list of files it'll return just a single file.
You need to use self.files.getlist("image") in order to correctly retrieve an array of the files.

This happened in Django 3.2, so I'm not sure if it's fixed in newer versions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文