Custom Form Field Render using UpdateView not Saving Data - django

I am trying to use custom HTML to render form field elements. However, now the data is not saving. The data is saving if I use the default form.as_p method.
Related template:
<!--DOES NOT SAVE DATA-->
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.description }}
<input type="submit" value="Update"/>
</form>
<!--SAVES DATA-->
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update"/>
</form>
Related UpdateView:
class ProfileEditView(UserPassesTestMixin, UpdateView):
model = Profile
form_class = ProfileCreateForm
template_name = 'update_profile_backup.html'
def get_success_url(self):
return reverse('profile', kwargs={'pk': self.object.pk})
def test_func(self):
obj = self.get_object()
print(obj.user == self.request.user)
return obj.user == self.request.user
Related ModelForm:
class ProfileCreateForm(forms.ModelForm):
class Meta:
model = Profile
fields = 'platform', 'content_niche', 'youtube_channel_id', 'description','profile_img', 'v_id_0', 'v_id_0_title', 'v_id_0_desc', \
'v_id_1', 'v_id_1_title', 'v_id_1_desc', 'v_id_2', 'v_id_2_title', 'v_id_2_desc'
Is it because the custom render method is not validating the form data? If so, how would I do that with an updateview?

Related

Django UpdateView not loading forms with styles

I created a form using Django's UpdateView class, however, when the form loads it seems like the text boxes and text areas are not styled (looks like form.as_p style). Here is an example of exactly what I did.
Views.py
class UpdatePostView(UpdateView):
template_name = 'Post/UpdatePost.html'
model = Post
fields = ['Title', 'Body']
success_url = reverse_lazy('BlogApp:main')
def form_valid(self, form):
form.instance.Title = form.cleaned_data['Title']
form.instance.Body = form.cleaned_data['Body']
form.instance.save()
return super().form_valid(form)
Here is how I loaded the form in UpdatePost.html:
<form id="UpdatePostForm" method="POST">
{% csrf_token %}
<div class="form-group">
<label for="PostTitle">{{form.Title.label}}</label>
{{form.Title}}
</div>
<div class="form-group">
<label for="PostBody">{{form.Body.label}}</label>
{{form.Body}}
</div>
<input class="btn btn-primary" type="submit" for="UpdatePostForm" value="Update">
</div>
</form>
If you use Bootstrap, you also can use django-crispy-forms (version for Bootstrap 4 https://github.com/django-crispy-forms/django-crispy-forms ,version for Bootstrap 5 - https://github.com/django-crispy-forms/crispy-bootstrap5). It is helpful to live DRY (Don't repeat yourself).
And then it will be something like(I use crispy-forms for Bootstrap 5):
pip install crispy-bootstrap5
INSTALLED_APPS = (
...
"crispy_forms",
"crispy_bootstrap5",
...
)
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
class UpdatePostView(UpdateView):
template_name = 'Post/UpdatePost.html'
model = Post
fields = ['Title', 'Body']
success_url = reverse_lazy('BlogApp:main')
def form_valid(self, form):
form.instance.Title = form.cleaned_data['Title']
form.instance.Body = form.cleaned_data['Body']
form.instance.save()
return super().form_valid(form)
template
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<h1 class="text-center">Update Post</h1>
<br />
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{form|crispy }}
<button class="btn btn-outline-primary">
Update
</button>
</form>
{% endblock content %}
Because by default the form.body and form.title render a html input, you can override the class attribut from your UpdateView like that :
def get_form(self, *args, **kwargs):
form = super(UpdatePostView, self).get_form(*args, **kwargs)
form.fields["Title"].widget.attrs["class"] = "form-group"
form.fields["Body"].widget.attrs["class"] = "form-group"
return form

2 Forms in a django template using cookiecutter-django

I'm creating 2 forms on one template in cookiecutter-django. I have both forms working on a normal django project but when I migrated them to cookiecutter-django, the forms did not work on the user_detail template.
This is the forms.py
class NarrateForm(ModelForm):
class Meta:
model = Narrate
fields = [
'title',
'body',
]
exclude = ('status',)
class TranslateForm(ModelForm):
class Meta:
model = Translate
fields = [
'title',
'body',
]
exclude = ('status',)
These are the views.py of forms that I have:
class TranslateFormView(FormView):
form_class = TranslateForm
template_name = 'user_detail.html'
def post(self, request, *args, **kwargs):
add_translation = self.form_class(request.POST)
add_narration = NarrateForm()
if add_translation.is_valid():
add_translation.save()
return self.render_to_response(
self.get_context_data(
success=True
)
)
else:
return self.render_to_response(
self.get_context_data(
add_translation=add_translation,
)
)
class NarrateFormView(FormView):
form_class = NarrateForm
template_name = 'users/user_detail.html'
def post(self, request, *args, **kwargs):
add_narration = self.form_class(request.POST)
add_translation = TranslateForm()
if add_narration.is_valid():
add_narration.save()
return self.render_to_response(
self.get_context_data(
success=True
)
)
else:
return self.render_to_response(
self.get_context_data(
add_narration=add_narration,
)
)
Now this is the view of the user_details from cookiecutter-django
class UserDetailView(LoginRequiredMixin, DetailView):
model = User
slug_field = "username"
slug_url_kwarg = "username"
user_detail_view = UserDetailView.as_view()
This is the code on the template which works on the old django project
<form method="POST" action="#">
{% csrf_token %}
{{ add_narration }}
<button type="submit" name="submit" value="Send Narration">Submit Narration</button>
</form>
<form method="POST" action="#">
{% csrf_token %}
{{ add_translation }}
<button type="submit" name="submit" value="Send Narration">Submit Narration</button>
</form>
I've been trying to make this work for more than 2 hours already and had no luck.
A possible solution could be creating a basic form (extend django.forms.Form) in the forms.py file, manually creating the fields and the modela later on in the views.py file.
give the submit inputs different names as shown here
<form method="POST" action="#">
{% csrf_token %}
{{ add_narration }}
<button type="submit" name="narrationsubmit" value="Send Narration">Submit Narration</button>
</form>
<form method="POST" action="#">
{% csrf_token %}
{{ add_translation }}
<button type="submit" name="transaltionsubmit" value="Send Narration">Submit Narration</button>
</form>
Then handle them as below in your view.
def handleforms(request):
if request.method =="POST" and "narrationsubmit" in request.post:
//process
elif request.method =="POST" and "transaltionsubmit" in request.post:
//process
NB: this is for a function based view. format it for a CBV

Django two forms interconnected in single template

I have another model which is like below
class Status(models.Model):
name = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.BooleanField(default=False)
I just want to create a form which will render all users from django User model with upper model connected. When click single Status button it will just save that field. I'm using CreateView. How to do that?
<form method="post" action="">
User1 <input type="checkbox" name="status" />
<input type="submit" name="submit" value="Status"/>
</form>
<form method="post" action="">
User2 <input type="checkbox" name="status" />
<input type="submit" name="submit" value="Status"/>
</form>
<form method="post" action="">
User3 <input type="checkbox" name="status" />
<input type="submit" name="submit" value="Status"/>
</form>
You could use Formsets from Django. Through this way, you can set 2 forms in one, get fields from both forms and save them with a single button.
For example, you have two models bounded by a ForeignKey in your models.py file :
class MyModelA(models.Model):
field1 = ...
field2 = ...
class MyModelB(models.Model):
field1 = ...
field2 = models.ForeignKey(MyModelA, ...)
Then, in your forms.py file, you have to bound these both forms thanks to formsets :
from django.forms.models import inlineformset_factory
from .models import MyModelA, MyModelB
MyFormSet = inlineformset_factory(MyModelA, MyModelB, form=MyModelBForm, extra=1, max_num=1)
With this line, your models will be set into the same django form in your template.
Now, in your views.py file, you have to call your formset :
class MyClassCreateView(CreateView):
model = MyModelA
template_name = 'path/to/your/template'
def get_context_data(self, **kwargs):
context = super(MyClassCreateView, self).get_context_data(**kwargs)
context['my_form'] = MyFormSet(self.request.POST or None)
return context
def form_valid(self, form):
context = self.get_context_data()
document = context['my_form']
if document.is_valid():
self.object = form.save()
document.instance = self.object
document.save()
return super(MyClassCreateView, self).form_valid(form)
And finally, in your template.html file, you can call your formset :
<form method="post" action="" novalidate>
{% csrf_token %}
{{ form }}
{{ my_form }}
<input type="submit" class="btn btn-default" value="{% trans 'Save' %}" />
</form>
Hopfully it could help you to set your Django formsets

Using modelformset_factory in a Django Class Based View

I am building a view that will let me update multiple fields on multiple objects at the same time. I'm doing this using ModelFormSet & modelformset_factory.
The template will be a table of forms with the object name to the left of the fields (see image below).
I found this example, but I am stuck on how to implement the class based view & template.
My Formset
class BaseFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseFormSet, self).__init__(*args, **kwargs)
self.queryset = Reference.objects.filter(
start__isnull=True)
ReferenceFormSet = modelformset_factory(
Reference,
fields=('start', 'end'),
formset=BaseFormSet,
extra=0)
My View
class ReferenceFormSetView(LoginRequiredMixin, SuperuserRequiredMixin, FormView):
model = Reference
form_class = ReferenceFormSet
template_name = "references/references_form.html"
def form_valid(self, form):
for sub_form in form:
if sub_form.has_changed():
sub_form.save()
return super(ReferenceFormSetView, self).form_valid(form)
My Template
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
<h1>{{ headline }}</h1>
<div class="row">
<form action="" method="post">
{% crispy form %}
<div class="">
<input type="submit" value="Submit" />
</div>
</form>
</div>
</div>
{% endblock content %}
Questions
The view seems odd with the Formset in the form_class. Is there a better way to handle this?
How can I access the instance name to display in the form?
I found a solution using a package called django-extra-views.
There is a class called ModelFormSetView which does exactly what I wanted. Here is my implementation (simplified) for others to use -
My View
class ReferenceFormSetView(ModelFormSetView):
model = Reference
template_name = "references/references_form.html"
fields = ['start', 'end']
extra = 0
def get_queryset(self):
return self.model.objects.all()
def get_success_url(self):
return reverse('references:formset')
def formset_valid(self, formset):
"""
If the formset is valid redirect to the supplied URL
"""
messages.success(self.request, "Updated")
return HttpResponseRedirect(self.get_success_url())
def formset_invalid(self, formset):
"""
If the formset is invalid, re-render the context data with the
data-filled formset and errors.
"""
messages.error(self.request, "Error dummy")
return self.render_to_response(self.get_context_data(formset=formset))
My Template
<form class="" method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div class="">
{% for field in form %}
{{ field }}
{% endfor %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Save</button>
</form>

Django form.is_valid() is always False while uploading images

views.py
I am trying to upload an image but form.is_valid always return false
def upload(request):
if request.method=="POST":
form=uploadform(request.POST,request.FILES)
if form.is_valid():
cd=cleaned_data
form.save(commit=True)
return render(request,"success.html")
else:
form=uploadform()
return render(request,'pico.html',{'form':form})
here is the form
<form action="" method="post">
{{form.as_p}}
{% csrf_token %}
<input type="submit" value="Submit" />
</form>
forms.py
class uploadform(ModelForm):
class Meta:
model=examplemodel
fields=['pic']
models.py
class examplemodel(models.Model):
pic=models.ImageField()
Did you set enctype="multipart/form-data" in the html form tag?