I have a table of animals. For each animal I have multiple images. I would like the user to be able to register a new animal and add multiple images to it in one form. As of now no records are created and I do not understand why. I've been struggelig this for hours. I know I could get there by using formsets but would prefer something like this.
I suspect the view is causing this, but do not understand why.
This is my not-working sollution:
from django.db import models
from django.core.exceptions import ValidationError
def validate_img_extension(value):
if not value.name.endswith(".jpg", ".jpeg", ".png"):
raise ValidationError("Only .jpg, .jpeg og .png allowed.")
class Animal(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True)
class AnimalImage(models.Model):
animal = models.ForeignKey(Animal, on_delete=models.CASCADE)
image = models.FileField(upload_to="products/", validators=[validate_img_extension])
from django.shortcuts import redirect, render
from .forms import AnimalImageForm, AnimalForm
from .models import Animal, AnimalImage
def createAnimal(request):
animalform = AnimalForm()
imageform = AnimalImageForm()
if request.method == "POST":
animalform = AnimalForm(request.POST)
imageform = AnimalImageForm(request.POST or None, request.FILES or None)
images = request.FILES.getlist("image")
if animalform.is_valid() and imageform.is_valid():
title = animalform.cleaned_data["title"]
describ = animalform.cleaned_data["description"]
animal_instance = Animal.objects.create(title=title, description=describ)
animal_instance.save()
for i in images:
AnimalImage.objects.create(animal=animal_instance, image=i)
return redirect("uplhome")
context = {"animalform": animalform, "imageform": imageform}
return render(request, "upl/animal_form.html", context)
def uplhome(request):
return render(request, "upl/uplhome.html")
from django import forms
from django.forms import ClearableFileInput
from .models import Animal, AnimalImage
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class AnimalForm(forms.ModelForm):
class Meta:
model = Animal
fields = ["title", "description"]
class AnimalImageForm(forms.ModelForm):
class Meta:
model = AnimalImage
fields = ["image"]
widgets = {"image": MultipleFileInput()}
from django.urls import path, include
from upl import views
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("uplhome", views.uplhome, name="uplhome"),
path("createanimal", views.createAnimal, name="createanimal"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
<h1>Animal Form </h1>
<form class="form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{animalform.title.label_tag}}
{{animalform.title}}
<br><br>
{{animalform.description.label_tag}}
{{animalform.description}}
<br><br>
{{imageform.image.label_tag}}
{{imageform.image}}
<br><br>
<button type="submit">Submit</button>
</form>
<a href="{% url 'uplhome' %}">Animal Home</a>
As Django documentation says,
If you want to upload multiple files using one form field, create a subclass of the field’s widget and set its allow_multiple_selected class attribute to True.
In order for such files to be all validated by your form (and have the value of the field include them all), you will also have to subclass FileField.
You already have the code for widget, so only FileField is left to be subclassed - again, example from documentation:
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = [single_file_clean(data, initial)]
return result
Then you can use this class in your form like this:
class AnimalImageForm(forms.ModelForm):
image = MultipleFileField()
class Meta:
model = AnimalImage
fields = ["image"]
And you'll have to adjust your validator to allow multiple values to be validated:
def validate_img_extension(files_list):
for file in files_list:
if not file.name.endswith((".jpg", ".jpeg", ".png")):
raise ValidationError("Only .jpg, .jpeg og .png allowed.")