How to make the instance inside Django form take multiple values? - django

I'm making a Django blog and I want to make the user edit his post. I have 2 forms to render, Post Form that includes (title, post image, content, category) and another separated form for the post tags that includes (tag name). To edit the tags I have to get all the tags related to the post and set them to the instance attribute which takes only one object (and I have multiple tags for one post).
Here are my Models:
class PostTags(models.Model):
tag_name = models.CharField(max_length=100)
def __str__(self):
return self.tag_name
class Post(models.Model):
title = models.CharField(max_length=50)
picture = models.ImageField(null=True,upload_to='images/')
content = models.CharField(max_length=255)
likes = models.ManyToManyField(User,blank=True,related_name='likes')
dislikes = models.ManyToManyField(User,blank=True,related_name='dislikes')
date_of_publish = models.DateTimeField(auto_now_add=True,null=True,blank=True)
user = models.ForeignKey(User,on_delete=models.CASCADE)
category = models.ForeignKey(Category,on_delete=models.CASCADE)
tag = models.ManyToManyField(PostTags,blank=True)
def __str__(self):
return self.title
Here are my Forms:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title','picture','content','category']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'picture': forms.FileInput(attrs={'class': 'form-control'}),
'content':forms.TextInput(attrs={'class': 'form-control'}),
'category' : forms.Select(attrs={'class':'form-control'}),
}
class TagsForm(forms.ModelForm):
class Meta:
model = PostTags
fields = ['tag_name']
widgets = {
'tag_name': forms.TextInput(attrs={'class': 'form-control', 'data-role': 'tagsinput'})
}
and here is my try to get all tags in the tags form in views.py
def editPost(request,post_id):
post = Post.objects.get(id= post_id)
post_form = PostForm(instance=post)
# tagInstance = []
for tag in post.tag.all():
print(tag)
newTag = PostTags.objects.get(tag_name=tag)
tag_form = TagsForm(instance=newTag)
# tagInstance.append(newTag)
# print(tagInstance)
# if request.method == 'POST':
# form = PostForm(request.POST,instance=post)
# if form.is_valid():
# form.save()
# return redirect('post')
context = {"post_form":post_form,'tag_form':tag_form}
return render (request,"dj_admin/editpost.html",context)
The above try resulted in only the last tag rendered to the tags form which is expected

There is probably a cleaner solution but this should work:
def editPost(request,post_id):
post = Post.objects.get(id= post_id)
post_form = PostForm(instance=post)
context = {}
context['post_form'] = post_form
count = 0
for tag in post.tag.all():
print(tag)
newTag = PostTags.objects.get(tag_name=tag)
tag_form = TagsForm(instance=newTag)
count += 1
context['tag_form'+str(count)] = TagsForm(instance=newTag)
context['tag_form_counter'] = count
return render (request,"dj_admin/editpost.html",context)
So, in your template you must to check the tag_form_counter variable to know how many tag_forms you have. And make a forloop to show them.

If I understood you correctly, you want to render all tag_forms related to the post inside your template.
You can achieve what you want using django model inline_formsets
forms.py:
from django.forms import inlineformset_factory
from .models import Post, PostTags
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title','picture','content','category']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'picture': forms.FileInput(attrs={'class': 'form-control'}),
'content':forms.TextInput(attrs={'class': 'form-control'}),
'category' : forms.Select(attrs={'class':'form-control'}),
}
class TagsForm(forms.ModelForm):
class Meta:
model = PostTags
fields = ['tag_name']
widgets = {
'tag_name': forms.TextInput(attrs={'class': 'form-control', 'data-role': 'tagsinput'})
}
TagFormSet = inlineformset_factory(PostTags, Post, fields=('tag_name',))
views.py:
from .forms import PostForm, TagsForm, TagFormSet
def editPost(request,post_id):
post = Post.objects.get(id= post_id)
post_form = PostForm(instance=post)
tag_formset = TagFormSet(instance=post)
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
tag_formset = TagFormSet(request.POST, instance=post)
if form.is_valid() and tag_formset.is_valid():
form.save()
tag_formset.save()
return redirect('post')
context = {'post_form': post_form, 'tag_formset': tag_formset}
return render (request, 'dj_admin/editpost.html', context)
and then inside your template file you can simply add {{ tag_formset }} like this:
<form method="post">
{{ formset }}
</form>

Related

Django How to attach foreign key associated with the multiple models to submit one form

How can in create inline formset which share the same foreign key using function base views. I don't want to keep selecting product title(which is the FK to other forms) because am using two forms with linked to one Foreign key#
i want to implement this https://www.letscodemore.com/blog/django-inline-formset-factory-with-examples/ in function base views
I have these 3 models
#product model
class Product(models.Model):
title = models.CharField(max_length=150)
short_description = models.TextField(max_length=100)
def __str__(self):
return self.title
*Image model*
class Image(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True)
image = models.ImageField(blank=True, upload_to='images')
def __str__(self):
return self.product.title
*variant model*
class Variant(models.Model):
product = models.ForeignKey( Product, on_delete=models.CASCADE)
size = models.CharField(max_length=100)
quantity = models.PositiveIntegerField(default=1)
price = models.DecimalField(max_digits=12, decimal_places=2)
def __str__(self):
return self.product.title
Forms
**Forms**
from django import forms from
django.forms import inlineformset_factory
from .models import ( Product, Image, Variant)
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
widgets = {
'title': forms.TextInput(
attrs={ 'class': 'form-control'} ), 'short_description': forms.TextInput(
attrs={'class': 'form-control'}),
}
class ImageForm(forms.ModelForm):
class Meta:
model = Image
fields = '__all__'
class VariantForm(forms.ModelForm):
class Meta:
model = Variant
fields = '__all__'
widgets = {
'size': forms.TextInput(attrs={'class': 'form-control'} ),
'quantity': forms.NumberInput(attrs={'class': 'form-control'}),
'price': forms.NumberInput(attrs={ 'class': 'form-control'}),
}
VariantFormSet = inlineformset_factory( Product, Variant, form=VariantForm, extra=1, can_delete=True, can_delete_extra=True )
ImageFormSet = inlineformset_factory( Product, Image, form=ImageForm,extra=1, can_delete=True, can_delete_extra=True )
What i tried
**Views**
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import (ProductForm, VariantFormSet, ImageFormSet)
from .models import (Image, Product, Variant)
#create product
def create_product(request):
if method.request == 'POST':
form = ProductForm(request.POST)
if form.is valid():
form.save()
else: form = ProductForm()
return redirect('product:products')
What i tried
Attach Image and variant to the product
when submitting this form, the VariantForm should get FK(product title field) from the ImageForm FK which has already been selected in the django template
#Attach product image and variation to product
def add_image_and_variant(request):
if method.request == 'POST':
image_form = ImageForm(request.POST,request == 'FILES')
var_form = VariantForm(request.POST)
if image_form.is valid() and var_form():
image_instance =image_form.save()
var = var_form(commit = False)
var.title= image_instance
var.save()
else:
image_form = ImageForm()
var_form = VariantForm()
return redirect('product:products')
return render(request,'product_var.html',{'image_form':image_form,:var_form})
Form 1
{{ form.as_p}}
form 2
{{image.form.as_p}}
{{var_form.as_p}}

Django - How to make a current object "ImageField attribute" as the pre-defined value in a Update_Object view?

I'm creating an update view using django-form for updating one of my objects that have the following fields:
class Object(models.Model):
name = models.CharField(max_length=40)
logo = models.ImageField(upload_to='object_logo/')
text_1 = models.TextField()
text_2 = models.TextField()
So, i have created the following form in forms.py:
class ObjectForm(forms.ModelForm):
class Meta:
model = Object
fields = [
'name',
'logo',
'text_1',
'text_2',
]
labels = {
'name': 'Name',
'logo': 'Logo',
'text_1': 'Text 1',
'text_2': 'Text 2',
}
and defined the following view called update_object:
def update_object(request, value):
object = get_object_or_404(Object, pk=value)
if request.method == "POST":
form = ObjectForm(request.POST, request.FILES)
if form.is_valid():
object.name = form.cleaned_data['name']
object.logo = form.cleaned_data['logo']
object.text_1 = form.cleaned_data['text_1']
object.text_2 = form.cleaned_data['text_2']
object.save()
return HttpResponseRedirect(reverse('myApp:detail_object', args=(value, )))
else:
form = ObjectForm(
initial={
'name': object.name,
'logo': object.logo,
'text_1': object.text_1,
'text_2': object.text_2,
}
)
context = {'object': object, 'form': form}
return render(request, 'myApp/update_object.html', context)
My problem is: even with an "initial" value stetted up for logo, i have to select an image every time i will update my object (otherwise i receive the update_object page with the message "This field is required").
Is there a way to make the current object.logo as the pre-defined value of the input in my ObjectForm in the update_object view?
I've already tried to set blank = True in the logo model field (which was a bad idea). I also thought in make an alternative conditional code for form.is_valid() but i dont know how to do it.
Update your forms.py like so:
class ObjectForm(forms.ModelForm):
class Meta:
model = Object
fields = '__all__'
...and views.py:
def update_object(request, value):
object = get_object_or_404(Object, pk=value)
if request.method == "POST":
form = ObjectForm(request.POST, request.FILES, instance=object)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('myApp:detail_object', args=(value, )))
else:
form = ObjectForm(instance=object)
context = {'object': object, 'form': form}
return render(request, 'myApp/update_object.html', context)
It can be done like this (more clean)
In case share your template code

POST request not working for Django form and Django formset

I have a form, ApplyJobForm and a Formset, ApplyJobFormset. GET method works when I pass the form and the formset to a view, but for the post request the form and the formset is_valid() isn't working, after clicking submit it returns me to a view without saving. I am unable to save the form with the formset, I don't know what I'm doing wrong here.
Here are my codes.
models.py
class Applicants(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='applicants')
experience = models.IntegerField(blank=True, null=True)
cv = models.FileField(upload_to=user_directory_path)
degree = models.CharField(choices=DEGREE_TYPE, blank=True, max_length=10)
created_at = models.DateTimeField(default=timezone.now)
def __str__(self):
return f'{self.user.get_full_name()} Applied'
class Certification(models.Model):
applicant = models.ForeignKey(Applicants, on_delete=models.CASCADE, related_name='applicant_certifications')
name = models.CharField(max_length=50)
certification = models.FileField(upload_to=user_directory_path, blank=True)
def __str__(self):
return f'{self.user.get_full_name()} certificate'
forms.py
class ApplyJobForm(forms.ModelForm):
class Meta:
model = Applicants
fields = ('job', 'degree', 'experience', 'cv')
exclude = ('job',)
labels = {
'degree': 'Degree',
'experience': 'Experience',
'cv': 'CV',
}
widgets = {
'degree': forms.Select(attrs={
'class': 'form-control',
}
),
'experience': forms.NumberInput(
attrs={
'class': 'form-control',
}
),
'cv': forms.FileInput(
attrs={
'class': 'form-control',
}
),
}
ApplyFormset = modelformset_factory(
Certification,
fields=('name', 'certification'),
extra=1,
widgets={
'name': forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': 'Certification name'
}
),
'certification': forms.FileInput(
attrs={
'class': 'form-control',
'placeholder': 'Upload certification'
}
)
}
)
views.py
def job_apply(request, job_id=None):
template_name = 'apply_form.html'
applyform = ApplyJobForm(request.GET or None)
job = get_object_or_404(Job, id=job_id)
formset = ApplyFormset(queryset=Certification.objects.none())
if request.method == 'GET':
context = {'applyform': applyform, 'formset': formset}
return render(request, template_name, context)
elif request.method == 'POST':
applyform = ApplyJobForm(request.POST)
formset = ApplyFormset(request.POST)
if applyform.is_valid() and formset.is_valid():
apply = applyform.save(commit=False)
applyform.job = job
apply.save()
for form in formset:
# so that `apply` instance can be attached.
certification = form.save(commit=False)
certification.apply = apply
certification.save()
return redirect('successful-apply')
else:
return redirect('job-detail', id=job.id)
return render(request, template_name, {'applyform': applyform, 'formset': formset})
Here an applicant can add as many certification field when applying for a job, although the certification field is not a required field. Certification model is bound to the Applicants model.
.html
<form class="form" method="POST" action="" enctype="multipart/form-data" role="form" autocomplete="off">
.................
</form>
First of all, never redirect if your forms are not valid. You want to render your template with the invalid form so that you can display the errors to the user. This also helps debugging since you'll see the errors.
So in your view, remove these two lines:
else:
return redirect('job-detail', id=job.id)
so that the invalid case renders the forms in your template.
Next, since you have files to upload, you need to initialise forms that require files with request.FILES:
formset = ApplyFormset(request.POST, request.FILES)
(and the same for applyform).
Finally make sure that in your template you are also displaying all the errors, either on each field ({{ form.<field>.errors }}) or globally ({{ form.errors }}).

Django Model form is not shown

I am probably missing something very simple, because my model form is not shown at the template. The code is very simple:
models.py:
class Story(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=200)
content = models.TextField()
picture = models.ImageField(upload_to = 'images/post_images')
date = models.DateTimeField(auto_now_add=True)
forms.py:
class StoryForm(forms.ModelForm):
class Meta:
model = Story
views.py:
from sfv.forms import StoryForm
#login_required(redirect_field_name=None)
def restricted(request):
user = request.user
form = StoryForm()
#graph = get_persistent_graph(request)
return render(request, "restricted.html", {user : 'user', form : 'form',})
template:
<form method = 'POST' action = ''>
<table>
{{ form }}
<table>
</form>
I have also tried form.as_p, that didnt help.
Change your render method from
return render(request, "restricted.html", {user : 'user', form : 'form',})
to
return render(request, "restricted.html", { 'user' : user, 'form' : form})
The key and value in the context dictionary were interchanged. Hence the issue.

django formset initial data showing id (primary key)

For some reason, the exclude in my forms isn't working and the primary key of my Item models is showing up on my formset. How can I get rid of it?
Form:
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ('name',
'description',
'quantity',
'start',
'end',
'cost_price',
'selling_price',)
widgets = {
'cost_price': forms.TextInput(attrs={'onChange':'updateSellingPrice()'}),
'description': forms.Textarea,
'start': SelectDateWidget,
'end': SelectDateWidget}
exclude = ('id')
ItemFormSet = modelformset_factory(Item, form=ItemForm, max_num=5, extra=3, exclude=('id'))
View:
def item_details(request, event_slug, role_id, module_slug):
event = get_object_or_404(Event, slug=event_slug)
payment_details = EventPaymentDetail.objects.get_or_create(event=event)[0]
try:
item_details = Item.objects.filter(event=event)
except:
item_details = Item.objects.get_or_create(event=event)[0]
if request.method == 'POST':
item_formset = ItemFormSet(request.POST)
#display_error(request, item_formset)
if item_formset.is_valid():
instances = item_formset.save(commit=False)
for instance in instances:
instance.event = event
instance.save()
messages.success(request, 'Item details successfully saved!')
url = reverse('event_admin_dashboard', args=[event_slug, role_id])
return redirect(url)
else:
item_formset = ItemFormSet()
currency_type = payment_details.currency
template = 'registration/item_details.html'
return render(request, template, locals())
I don't believe it is possible to exclude the id field. I'm afraid I can't give you an explanation or a link to the docs.
Aside:
It's not the issue here, but you're missing a comma on your exclude tuple. That's not the problem here, but it means that django interprets it as
exclude = ('i', 'd')
It should be:
exclude = ('id',)