Django Model form is showing invalid with initialized values and file - django

I have a model like this:
class Cohortpdfs(models.Model):
id = models.AutoField(primary_key=True,editable=True)
title= models.CharField(max_length=200,validators=[MaxLengthValidator(100)])
content=models.FileField()
cohort_id=models.ForeignKey(Cohort,on_delete=models.CASCADE,default=0,db_constraint=False)
I want to initialize and save data from views.
views.py:
if requests.method == 'POST':
initialvalue={
'cohort_id':id,
'title':requests.POST.get('title'),
'content':requests.FILES['content'],
}
if contentform.is_valid():
print("form valid")
else:
print("FORM ERROR")
Forms.py
class CohortpdfsForm(forms.ModelForm):
class Meta:
model = Cohortpdfs
fields = [
"title","content","cohort_id",
]
widgets={
'title' : forms.TextInput(attrs={'class': 'form-control','max_length':20}),
'content': forms.FileInput(attrs={'class': 'form-control'}),
'cohort_id': forms.TextInput(attrs={'type': 'hidden'}),
}
This is showing my form is not valid. How can I initialize the form with the uploaded file(PDF) and save it into the model database?

Related

Django - Keep specific fields on form after submit

I have a view that has a simple "save and add another" functionality, that redirects the user to the same page after submit the form.
View:
def new_planning(request):
form = PlanningForm(request.POST)
if form.is_valid():
form.save()
if 'another' in request.POST:
messages.success(request, ('Success!'))
return redirect('new_planning')
else:
return redirect('list_planning')
return render(request, 'pages/planning/new_planning.html', context={
'form': form,
})
Form:
class PlanningForm(forms.ModelForm):
accountplan = ModelChoiceField(
queryset=AccountsPlan.objects.filter(active=True).order_by('code'),
)
month = forms.DateField(
required=True,
error_messages={'required': '', },
)
amount = forms.DecimalField(
max_digits=9,
decimal_places=2,
required=True,
validators=[
error_messages={'required': '', },
)
class Meta:
model = Planning
fields = '__all__'
The function works as expected and after the submit, the same page is rendered with a blank form. What I want is to keep just the "amount" field blank and keep the data typed in the "accountplan" and "month" fields. Is there a way to do this?
I read about instance in the docs, but it doesn't seem to be what I looking for, since I don't want to get the data from the database (if that's possible), but simply keep the last inputs typed in both fields.
If you rewrite the "ModelForm" to a "Model" class, you can get the values of the posted datas, and can be rendered to the page.
For example:
# views.py
def ContactPageView(request):
if request.method == "POST":
email = request.POST.get('email')
message = request.POST.get('message')
message_element = ContactFormObject(email=email, message=message)
message_element.save()
else:
name, message = '', ''
form_data = name, message
return render(request, 'contact.html', {'form_data': form_data})
# models.py
class ContactFormObject(models.Model):
email = models.CharField(max_length=100) #....
ModelForm is more comfortable, but I don't recommend it if you have extra 10 minutes to code some HTML in order to the possibilities of more customization.

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

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

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>

how to save custom ModelForm fields in django

i have custom fields in ModelForm and there is no any values on save. im just confuse what to use in view.py to save with data
form.py
class AddCityForm(forms.ModelForm):
duration = forms.ChoiceField(widget=forms.RadioSelect(attrs={
'style': 'background-color: #FAF9F9', 'class': 'mb-3, form-check-inline'}), choices=DURATION_CHOICES)
country = forms.ChoiceField(widget=forms.RadioSelect(attrs={
'style': 'background-color: #FAF9F9', 'class': 'mb-3, form-check-inline'}), choices=CITY_CHOICE)
something = forms.CharField(widget=forms.TextInput(attrs={
'style': 'background-color: #FAF9F9', 'class': 'mb-3'}))
class Meta:
model = Cities
exclude = ['city', 'duration', 'something']
view.py
def add_city(request):
data = dict()
if request.method == 'POST':
form = AddCityForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
form.city = request.POST.get('country')
form.duration = request.POST.get('dur')
form.something = request.POST.get('something')
form = form.save()
messages.success(request, f'Test for Added Successfully')
data['form_is_valid'] = True
else:
data['form_is_valid'] = False
else:
form = AddCityForm()
context = dict(form=form)
data['html_form'] = render_to_string('cites/modal_1.html', context, request=request)
return JsonResponse(data)
can any one help with this ?
Looks like the code is working, i have no idea why did not before i asked this question but i will keep this if any one look for similar question

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