I have this view with two forms.
def anunciocreateview(request):
anuncio_form = AnuncioForm(request.POST or None)
producto_form = ProductoForm(request.POST or None)
if request.method == "POST":
if all([anuncio_form.is_valid(), producto_form.is_valid(), imagen_form.is_valid()]):
anuncio = anuncio_form.save(commit=False)
anuncio.anunciante = request.user
anuncio.save()
producto = producto_form.save(commit=False)
producto.anuncio = anuncio
producto.save()
return HttpResponse(status=204, headers={'HX-Trigger' : 'eventsListChanged'})
else:
anuncio_form = AnuncioForm()
producto_form = ProductoForm()
context = {
'anuncio_form' : anuncio_form,
'producto_form' : producto_form,
}
return render(request, 'buyandsell/formulario.html', context)
This view works OKAY; it allows the user to create instances of both models with the correct relation. I'm trying to add another form for the image of the product. I tried adding this:
def anunciocreateview(request):
anuncio_form = AnuncioForm(request.POST or None)
producto_form = ProductoForm(request.POST or None)
imagen_form = ImagenForm(request.POST, request.FILES)
if request.method == "POST":
if all([anuncio_form.is_valid(), producto_form.is_valid(), imagen_form.is_valid()]):
anuncio = anuncio_form.save(commit=False)
anuncio.anunciante = request.user
anuncio.save()
producto = producto_form.save(commit=False)
producto.anuncio = anuncio
producto.save()
imagen = imagen_form.request.FILES.get('imagen')
if imagen:
Imagen.objects.create(producto=producto, imagen=imagen)
return HttpResponse(status=204, headers={'HX-Trigger' : 'eventsListChanged'})
else:
print(request.FILES)
else:
anuncio_form = AnuncioForm()
producto_form = ProductoForm()
imagen_form = ImagenForm()
context = {
'anuncio_form' : anuncio_form,
'producto_form' : producto_form,
'imagen_form' : imagen_form
}
return render(request, 'buyandsell/formulario.html', context)
But this happens:
1- The 'Image upload' form field shows error 'This field is required' at the moment of rendering the form.
2- If uploading an image and clicking submit, the redirect doesn't happen as the imagen_form
is not marked as valid. No error pops in the console whatsoever (I'm handling requests with HTMX).
If I omit the 'request.FILES'
when instantiating the form, the error when the form renders disappear, but it anyway doesn't upload the image nor the form.
What am I missing?
For reference, here is the HTML file:
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div >
<div >
<h5 >Create new listing</h5>
<button type="button" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div >
<div >
<label for="{{ anuncio_form.titulo.auto_id }}">{{ anuncio_form.titulo.label }}</label>
{% render_field anuncio_form.titulo|add_error_class:"is-invalid" %}
</div>
<div >
<label for="{{ anuncio_form.envio.auto_id }}">{{ anuncio_form.envio.label }}</label>
{% translate "Select available delivery options" as input_place_holder %}
{% render_field anuncio_form.envio|add_error_class:"is-invalid" placeholder=input_place_holder %}
</div>
<h5>Product:</h5>
<div >
<label for="{{ producto_form.nombre.auto_id }}">{{ producto_form.nombre.label }}</label>
{% translate "Name of your product" as input_place_holder %}
{% render_field producto_form.nombre|add_error_class:"is-invalid" placeholder=input_place_holder %}
</div>
<div >
<label for="{{ producto_form.descripcion.auto_id }}">{{ producto_form.descripcion.label }}</label>
{% translate "Give a detailed description of your product" as input_place_holder %}
{% render_field producto_form.descripcion|add_error_class:"is-invalid" placeholder=input_place_holder %}
</div>
<div >
<div >
<div >
<label for="{{ producto_form.estado.auto_id }}">{{ producto_form.estado.label }}</label>
{% render_field producto_form.estado|add_error_class:"is-invalid" %}
</div>
</div>
<div >
<div >
<label for="{{ producto_form.cantidad.auto_id }}">{{ producto_form.cantidad.label }}</label>
{% render_field producto_form.cantidad|add_error_class:"is-invalid" %}
</div>
</div>
</div>
<div >
<div >
<div >
<label for="{{ producto_form.precio_unitario.auto_id }}">{{ producto_form.precio_unitario.label }}</label>
{% render_field producto_form.precio_unitario|add_error_class:"is-invalid" %}
</div>
</div>
<div >
<div >
<label for="{{ producto_form.disponibilidad.auto_id }}">{{ producto_form.disponibilidad.label }}</label>
{% render_field producto_form.disponibilidad|add_error_class:"is-invalid" %}
</div>
</div>
</div>
<h5>Images:</h5>
<div >
<label for="{{ imagen_form.imagen.auto_id }}">{{ imagen_form.imagen.label }}</label>
{% render_field imagen_form.imagen|add_error_class:"is-invalid" %}
</div>
<div id="productforms"></div>
</div>
<div >
<button type="button" hx-get="{% url 'buyandsell:create-product' %}" hx-target="#productforms" hx-swap="beforeend">Add new product</button>
<button type="button" data-bs-dismiss="modal">Cancel</button>
<button type="submit" hx-post="{% url 'buyandsell:createview' %}">Submit</button>
</div>
</div>
</form>
Edit:
forms.py for ImagenForm:
class ImagenForm(ModelForm):
class Meta:
model = Imagen
fields = ['imagen']
models.py for the Imagen model:
class Imagen(models.Model):
producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name='imagen', blank=True, null=True)
imagen = models.ImageField(upload_to='marketplace/')
def __str__(self):
return f"Imagen de {self.producto}"
def save(self, *args, **kwargs):
self.image = resize_image(self.image, size=(350, 350))
super().save(*args, **kwargs)
CodePudding user response:
Don't initialize forms with request.POST
if it is not a POST request.
Two changes to fix your issue:
- move first form init block under
if request.method == "POST"
- move
else
block one tab to the left so it becomeselse
toif request.method == "POST"
instead of current version where it iselse
toif .is_valid()
if request.method == "POST":
anuncio_form = AnuncioForm(request.POST or None)
producto_form = ProductoForm(request.POST or None)
imagen_form = ImagenForm(request.POST, request.FILES)
if all([anuncio_form.is_valid(), producto_form.is_valid(), imagen_form.is_valid()]):
...
else:
anuncio_form = AnuncioForm()
producto_form = ProductoForm()
imagen_form = ImagenForm()
Now you use request.POST
only on POST requests, instead you show empty forms. If a form is invalid on POST then it will be rendered with errors (if template is done cottectly) instead of showing empty forms back again.
For handling multiple forms with one view/template take a look at these answers: one two three
upd
For HTMX-requests it is also required to set hx-encoding="multipart/form-data"
in form
element attributes. Similar question HTMX docs