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.
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.