Django: two forms in one view - python

How would I write a view with a single form to add a new person, and on that same form, be able to add 0 or 1 or 2 or ... 68757857 images of that same person?
Please refer to the code below.
In models.py:
class Person(models.Model):
name = models.CharField(max_length=50)
class Image(models.Model):
person = models.ForeignKey(Person)
image = models.ImageField(upload_to='images/')
In forms.py:
class PersonForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name']
class ImageForm(forms.ModelForm):
class Meta:
model = Image
fields = ['person', 'image']
From what I have seen, formsets are used. I have looked at many examples on SO, read the documentation and I am still confused.
I would appreciate it if someone can provide an example of a views.py that fulfils the specification at the beginning of this question.

You could use Inline formsets.
In view.py firt of all you have to create a new person instance and then use this instance with formset:
from django.forms import inlineformset_factory
ImageFormSet = inlineformset_factory(Person, Image, fields=('image',))
def my_view(request):
form = PersonForm(request.POST or None)
if form.is_valid():
new_person = form.save()
formset = ImageFormSet(request.POST or None, request.FILES or None, instance=new_person)
if formset.is_valid():
formset.save()
return redirect('persons:main')
else:
formset = ImageFormSet(request.POST or None, request.FILES or None)
return render(request, 'my_template.html', {'formset': formset, 'form': form})
In my_template.html:
<form action="" method="post" enctype="multipart/form-data">
{{ form.as_p }}
{{ formset.management_form }}
{% for frm in formset %}
{{ frm.as_p }}
{% endfor %}
<input type="submit" value="Create">
</form>

Related

How to Update ImageField in Django?

i am new in Django. i am having issue in updating ImageField.i have following code
in models.py
class ImageModel(models.Model):
image_name = models.CharField(max_length=50)
image_color = models.CharField(max_length=50)
image_document = models.ImageField(upload_to='product/')
-This is My forms.py
class ImageForm(forms.ModelForm):
class Meta:
model = ImageModel
fields = ['image_name', 'image_color' , 'image_document']
in Html file (editproduct.html)
<form method="POST" action="/myapp/updateimage/{{ singleimagedata.id }}">
{% csrf_token %}
<input class="form-control" type="text" name="image_name" value="{{ singleimagedata.image_name}}">
<input class="form-control" type="file" name="image_document">
<button type="submit" class="btn btn-primary">UPDATE PRODUCT</button>
</form>
-myapp is my application name. {{singleimagedata}} is a Variable Containing all fetched Data
-urls.py
urlpatterns = [
path('productlist', views.productlist, name='productlist'),
path('addproduct', views.addproduct, name='addproduct'),
path('editimage/<int:id>', views.editimage, name='editimage'),
path('updateimage/<int:id>', views.updateimage, name='updateimage'),
]
and Here is My views.py
def productlist(request):
if request.method == 'GET':
imagedata = ImageModel.objects.all()
return render(request,"product/productlist.html",{'imagedata':imagedata})
def addproduct(request):
if request.method == 'POST':
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.add_message(request, messages.SUCCESS, 'Image Uploaded')
return redirect('/myapp/productlist')
else:
imageform = ImageForm()
return render(request, "product/addproduct.html", {'imageform': imageform})
def editimage(request, id):
singleimagedata = ImageModel.objects.get(id=id)
return render(request, 'product/editproduct.html', {'singleimagedata': singleimagedata})
def updateimage(request, id): #this function is called when update data
data = ImageModel.objects.get(id=id)
form = ImageForm(request.POST,request.FILES,instance = data)
if form.is_valid():
form.save()
return redirect("/myapp/productlist")
else:
return render(request, 'demo/editproduct.html', {'singleimagedata': data})
My image Upload is working fine.i can not Update image while updating data.rest of the data are updated.i don't know how to update image and how to remove old image and put new image into directory.
I think you missed the enctype="multipart/form-data", try to change:
<form method="POST" action="/myapp/updateimage/{{ singleimagedata.id }}">
into;
<form method="POST" enctype="multipart/form-data" action="{% url 'updateimage' id=singleimagedata.id %}">
Don't miss also to add the image_color field to your html input.
Because, in your case the image_color field model is designed as required field.
To remove & update the old image file from directory;
import os
from django.conf import settings
# your imported module...
def updateimage(request, id): #this function is called when update data
old_image = ImageModel.objects.get(id=id)
form = ImageForm(request.POST, request.FILES, instance=old_image)
if form.is_valid():
# deleting old uploaded image.
image_path = old_image.image_document.path
if os.path.exists(image_path):
os.remove(image_path)
# the `form.save` will also update your newest image & path.
form.save()
return redirect("/myapp/productlist")
else:
context = {'singleimagedata': old_image, 'form': form}
return render(request, 'demo/editproduct.html', context)
I had a similar issue while updating the profile_pic of user. I solved this with the following code I think this might help:
Models.py
class Profile(models.Model):
# setting o2o field of user with User model
user_name = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True)
first_name = models.CharField(max_length=70, null=True, blank=True)
last_name = models.CharField(max_length=70, null=True, blank=True)
profile_pic = models.ImageField(upload_to="images", blank=True, null=True,)
def __str__(self):
return str(self.user_name)
forms.py
class ProfileEditForm(ModelForm):
class Meta:
model = Profile
fields = '__all__'
# excluding user_name as it is a one_to_one relationship with User model
exclude = ['user_name']
views.py
#login_required(login_url='login')
def edit_profile(request, id):
username = get_object_or_404(Profile, id=id)
extended_pro_edit_form = ProfileEditForm(instance=username)
if request.method == "POST":
extended_pro_edit_form = ProfileEditForm(request.POST, request.FILES, instance=username)
if extended_pro_edit_form.is_valid():
extended_pro_edit_form.save()
next_ = request.POST.get('next', '/')
return HttpResponseRedirect(next_)
context = {'extended_pro_edit_form': extended_pro_edit_form}
return render(request, 'edit_profile.html', context)
edit-profile.html
<form action="" method="post"
enctype="multipart/form-data">
{% csrf_token %}
{{ extended_pro_edit_form.as_p }}
{{ extended_pro_edit_form.errors }}
<!--To redirect user to prvious page after post req-->
<input type="hidden" name="next" value="{{ request.GET.next }}">
<button type="submit">UPDATE</button>
</form>
Answer from #binpy should solve your problem. In addition to your second answer, you could do:
def updateimage(request, id): #this function is called when update data
data = ImageModel.objects.get(id=id)
form = ImageForm(request.POST,request.FILES,instance = data)
if form.is_valid():
data.image_document.delete() # This will delete your old image
form.save()
return redirect("/myapp/productlist")
else:
return render(request, 'demo/editproduct.html', {'singleimagedata': data})
Check delete() method on django docs.
some times something like cached old image is not replaced in the front-end so you might just need to forces refresh by pressing CTRL + F5 or clear your browsing history.
the answer given by #binpy is a needed update so that the files are passed to the back-end.

Django image form isn't saving the image

I have a form that involves uploading a profile picture. I have it working so that I can upload images in the /admin/ interface and display them correctly, but I cannot get my Modelform to save the image.
Here is what I have:
models.py
class Candidate(models.Model):
UserID = models.ForeignKey(User, on_delete=models.CASCADE)
ElectionID = models.ForeignKey(Election, on_delete=models.CASCADE)
Bio = models.CharField(max_length=500, blank=True)
ProfilePicture = models.ImageField(upload_to="profilepics/", null=True, blank=True)
forms.py
class AddCandidateForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['ElectionID', 'Bio', 'ProfilePicture']
cand_reg.html (Template)
{% block content %}
<h1>Register as a candidate</h1>
<form method="POST" class="post-form">
{% csrf_token %}
<h2>Select an election:</h2><br>
{{form.ElectionID}}<br>
<h2>Enter your bio:</h2><br>
{{form.Bio}}<br>
<h2>Upload a profile picture:</h2><br>
{{form.ProfilePicture}}<br>
<button type="submit">Register</button>
</form>
{% endblock %}
When I try the view function like so I get the error:
MultiValueDictKeyError at /register/
"'ProfilePicture'"
views.py
def add_candidate(request):
if request.method == 'POST':
form = AddCandidateForm(request.POST, request.FILES)
if form.is_valid():
candidate = form.save(commit=False)
candidate = request.FILES['ProfilePicture']
candidate.UserID = request.user
candidate.save()
return redirect('/home/')
else:
form = AddCandidateForm()
return render(request, 'cand_reg.html', {
"form": form
})
views.py
When I remove the offending line, the error goes away.
def add_candidate(request):
if request.method == 'POST':
form = AddCandidateForm(request.POST, request.FILES)
if form.is_valid():
candidate = form.save(commit=False)
# candidate = request.FILES['ProfilePicture']
candidate.UserID = request.user
candidate.save()
return redirect('/home/')
else:
form = AddCandidateForm()
return render(request, 'cand_reg.html', {
"form": form
})
However, this doesn't actually save the image, so when I try to render it in a separate template, I get an error then.
Can anyone help me understand why the image isn't uploading?
Thanks in advance :)
You must set the ProfilePicture attribute of the model and not the instance itself (candidate = request.FILES['ProfilePicture']).
Change to:
candidate = form.save(commit=False)
candidate.ProfilePicture = request.FILES['ProfilePicture']
candidate.UserID = request.user
candidate.save()
Change your HTML form to accept files as well. Change to: <form method="POST" enctype="multipart/form-data" class="post-form">. When a form includes file inputs (<input type="file" />), then it must be encoded differently than it used when it includes only text. More here. If you right-click and inspect the {{form.ProfilePicture}} you'll see that this is actually a file input.
Extra one:
Please, do not name your class attributes (ProfilePicture, UserID etc) in PascalCase. Use snake_case instead (profile_picture, user_id etc).

Pre-populating a child models django create form with a parent's ID

I have followed the guidelines from This answer in order to pass Parent pk to the child creation page. At the moment though it is not working and I am seeing the following log.
[14/Jul/2017 13:15:37] "POST /catalog/productstatus/2/create/ HTTP/1.1" 200 4001
I'm not sure what I'm doing wrong, here is the code I currently have.
Models
Models.py
class Product(models.Model):
serial_number = models.CharField(unique=True, max_length=15)
class ProductStatus(models.Model):
serial_number = models.ForeignKey('Product', on_delete=models.CASCADE, null=True)
status = models.CharField(max_length=20, blank=True, default='Stock', help_text='Products status')
date = models.DateTimeField(auto_now_add=True)
View
class ProductStatusCreate(CreateView):
model = ProductStatus
template_name = 'catalog/productstatus_create.html'
form_class = ProductStatusModelForm
def form_valid(self, form):
productstatus = form.save(commit=False)
product_id = form.data['product_id']
product = get_object_or_404(Product, id=product_id)
productstatus.product = product
return super(ProductStatusCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(ProductStatusCreate, self).get_context_data(**kwargs)
context['s_id'] = self.kwargs['product_id']
return context
def get_success_url(self):
if 'product_id' in self.kwargs:
product = self.kwargs['product_id']
else:
product = self.object.product.pk
return reverse_lazy('product_detail', kwargs={'pk': product})
Forms
class ProductStatusModelForm(forms.ModelForm):
class Meta:
model = ProductStatus
fields = ['status',]
def __init__(self, *args, **kwargs):
self.fields["product"] = forms.CharField(widget=forms.HiddenInput())
super(ProductStatusModelForm, self).__init__( *args, **kwargs)
templates/myapp/product_detail.html
New
urls.py
urlpatterns += [
url(r'^productstatus/(?P<product_id>\d+)/create/$', views.ProductStatusCreate.as_view(), name='productstatus_create'),
]
productstatus_create.html
{% extends "base_generic.html" %}
{% block content %}
<h2>New Product Status</h2>
</br>
<form action="" method="post">
{% csrf_token %}
<table>
<input type=hidden id="id_product" name="product" value="{{ s_id }}">
{{ form }}
</table>
<input type="submit" value="Submit" />
</form>
</br>
{% endblock %}
When looking at the page's source the value does get populated but when I submit the form nothing happens.
Why do you have views.ProductInstanceCreate.as_view() in your urls.py but the view you show is called ProductStatusCreate? Are you sure you are using the right view?
You are creating a 'product' hidden field in your form, but not providing a value for it anywhere. Your template output then has two product fields, and the latter (blank) is taken, so returns an error saying it is required.
None of this outputting the product ID to the template in order to read it back in is necessary - you always have the ID available to you in the URL kwargs.
You can get rid of your get_context_data, and the extra field code in the Form and template. Your form_valid can be something like:
def form_valid(self, form):
product = get_object_or_404(Product, id=self.kwargs['product_id'])
form.instance.product = product
return super().form_valid(form)
And product_id will always be in self.kwargs, so your get_success_url can be shorter too:
def get_success_url(self):
product = self.kwargs['product_id']
return reverse('product_detail', kwargs={'pk': product})

Getting a 'This field is required` error when updating a model instance

I have an edit view for when a user wants to edit a Post:
def edit(request, id):
post = get_object_or_404(Post, id=id)
edit_form = PostForm(request.POST or None, instance=post)
if edit_form.is_valid():
instance = edit_form.save(commit=False)
instance.save(update_fields=['content'])
return HttpResponseRedirect('/')
else:
print(edit_form.errors)
edit_form = PostForm(instance=post)
context = {
'edit_form': edit_form,
'form_post': post
}
return render(request, 'edit.html', context)
When a user edits a Post, I only want them to be able to edit 1 field (content), so i've only rendered that form field in my template (pre-populated with the previous post.content. The other fields are just fields of the object (not a form/can't be edited).
...
<form method="post" action="" enctype="multipart/form-data">{% csrf_token %}
<h1>{{ form_post.title }}</h1>
<p>{{ edit_form.content}}</p>
<p>{{ form_post.category }}</p>
</form>
...
and here is my Post model:
class Post(models.Model):
...
title = models.TextField(max_length=76)
content = models.TextField(null=False, default='')
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default='1')
When the edit form is submitted, form_errors returns this:
<ul class="errorlist">
<li>title<ul class="errorlist"><li>This field is required.</li></ul></li>
<li>category<ul class="errorlist"><li>This field is required.</li></ul</li>
</ul>
Why is this happening? Doesn't:
instance = edit_form.save(commit=False)
instance.save(update_fields=['content'])
keep the fields from the orignal Post and just change the content field?
If you only want some of the fields to be editable, you should set fields in your model form. If you use PostForm in another view and cannot edit fields, then create a new form.
class EditPostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['content']
You can subclass PostForm if you prefer:
class EditPostForm(PostForm):
class Meta(PostForm.Meta):
fields = ['content']
Then update your edit view to use EditPostForm instead of PostForm.

Django ImageField not rendering ClearableFileInput

Starting to beat my head against the wall...perhaps I am missing something simple.
models.py
class GFImage(models.Model):
image = models.ImageField(upload_to = 'uploads', null=True, blank=True)
views.py
def addImage(request):
errors = []
if request.method == 'POST':
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
form.save()
urlRedirect = "/home"
return redirect(urlRedirect)
else:
form = ImageForm()
return render(request, "/add_image.html", {'form': form})
forms.py
class ImageForm(ModelForm):
class Meta:
model = GFImage
add_image.html
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type = "submit" value = "Submit">
</form>
Whatever I do, my form will not use the ClearableFileInput widget. It should default automatically, but even assigning it in the form's META will not work. What else could be blocking Django from using the clearable widget?
The ClearableFileInput will only display the clear checkbox when there's an initial file selected. Looking at your form, it looks like a a new form without initial data, so the checkbox won't be displayed.
def render(self, name, value, attrs=None):
.. snip ..
if value and hasattr(value, "url"):
template = self.template_with_initial
substitutions['initial'] = format_html(self.url_markup_template,
https://github.com/django/django/blob/5fda9c9810dfdf36b557e10d0d76775a72b0e0c6/django/forms/widgets.py#L372

Categories