Using Django Context Processors with forms - python

I have multiple forms to be shown everywhere in my project and hence I read that having a context_processor was the best way to do it. So, I created one inside my app and it looks something like this:
def forms_processor(request):
name_form = NewNameForm()
work_form = NewWorkForm()
address_form = NewAddressForm()
context = {'name_form': name_form,
'work_form': work_form,
'address_form': work_form,
}
return context
This works great, I can just use {{name_form}} anywhere in my templates and that renders the form.
Now my question is, where do I validate the form? In my views.py or the context_processors.py? Right now my views for name_form looks something like this:
def user_profile(request):
if request.method == 'POST':
name_form = NewNameForm(request.POST)
if name_form.is_valid():
form.save()
else:
ctx = {'title': 'Profile', 'active_tab': 'Profile'}
return render (request, 'user_profile.html', ctx)
This isn't working actually, if I submit an invalid form, it just comes back to the same page and won't show a populated form.
If someone could guide me or redirect me to some docs on this topic, that'd be awesome! Thanks!

The problem is that your processor instantiates the form on each render. Each time you call render, your processor is called, which instantiates a new form and displays THAT form, not the form instance that you created in the view. Therefore, the form being rendered is a blank instance but the form that contains the input and errors was destroyed by garbage collection after finishing your view.
A way I would do this, is passing the form you create in the view back to context before rendering. Pass it in to a context key such as "name_form_filled". Then if that variable is present in the context, don't render "name_form", instead render "name_form_filled".
views.py
def user_profile(request):
ctx = {}
if request.method == 'POST':
name_form = NewNameForm(request.POST)
if name_form.is_valid():
name_form.save() # you named this named_form, not form.
# If you want to redirect to another view when the form is saved successfuly, do it here.
else:
ctx["name_form_filled"] = form
else:
ctx.update({'title': 'Profile', 'active_tab': 'Profile'})
return render (request, 'user_profile.html', ctx)
user_profile.html
<div id="form_container">
{% if name_form_filled %}
<!-- Render form that has input and errors from previous POST. -->
{{ name_form_filled }}
{% else %}
<!-- render empty initial form. User has not attempted to submit yet. -->
{{ name_form }}
{% endif %}
</div>
===========================================================================
Another way you could do this is turn this view into a class based view and inherit a base class based view. This base class will override the get_context_data method and add your three forms. Note that you won't be using the context processor with this methodology so you could get rid of it if wanted in this case.
All views that use your form will extend the base view class. Then, after evaluating your form, if it is invalid, overwrite your name_form context key with the invalid form instance, which will be in your context.
views.py
class BaseView(View):
def get_context_data(self, *args, **kwargs):
context = {
"name_form": NewNameForm(),
"work_form": NewWorkForm(),
"address_form": NewAddressForm()
}
return context
class UserProfileView(BaseView):
def get(self, request, *args, **kwargs):
# Do GET logic here.
ctx = self.get_context_data(*args, **kwargs) # BaseView.get_context_data will be called here unless you override it in this class.
ctx.update({'title': 'Profile', 'active_tab': 'Profile'})
return render (request, 'user_profile.html', ctx)
def post(self, request, *args, **kwargs):
# Do POST logic here.
ctx = self.get_context_data(*args, **kwargs) # BaseView.get_context_data will be called here unless you override it in this class.
name_form = NewNameForm(request.POST)
if name_form.is_valid():
name_form.save()
else:
ctx["name_form"] = name_form # will replace the empty form in context with the form instance created in name_form that has input and errors.
return render (request, 'user_profile.html', ctx)
user_profile.html
<div id="form_container">
<!-- Will render whatever is in name_form. If this is after the
user has submitted an invalid form, this form will be populated with input and errors because we overwrote it in the view. -->
{{ name_form }}
</div>
===========================================================================
I personally think that the first solution is the best but when you start getting more complex, you should probably switch over to the second solution as class based views make complex views way easier.

Direct answer: you validate the form in views.py with is_valid() method. What you need is to populate context with bound form if the form is invalid:
def user_profile(request):
ctx = {'title': 'Profile', 'active_tab': 'Profile'}
if request.method == 'POST':
name_form = NewNameForm(request.POST)
if name_form.is_valid():
form.save()
return redirect(YOUR_REDIRECT_URL) # Always redirect after successful POST
ctx['form'] = form # if form is invalid return it with context
return render (request, 'user_profile.html', ctx)
Read more in documentation.

Related

Problems with Django URL routing

I'm building a website, to be used in dental practices, however I'm having trouble with the URL routing. I'm wanting af URL pattern like: Denthelp/kartotek/#nameofclinic#/opretpatient.
My suggestion looks like this:
urls.py:
path('kartotek/<str:kl_id>/', views.kartotek, name="kartotek"),
path('kartotek/<str:kl_id>/opretpatient/', views.opretpatient, name="opret_patient"),
Views. py:
def kartotek(request, kl_id):
kliniknavn = Klinik.objects.get(navn=kl_id)
E_patient = kliniknavn.patient_set.all()
context = { 'kliniknavn':kliniknavn, 'E_patient':E_patient}
return render(request,'DentHelp/kartotek.html', context )
def opretpatient(request, kl_id):
kliniknavn = Klinik.objects.get(navn=kl_id)
form = PatientForm()
if request.method == 'POST':
form = PatientForm(request.POST)
if form.is_valid():
form.save()
return redirect('kartotek/<str:kl_id>/')
context = {'form':form, 'kliniknavn':kliniknavn}
return render(request,'DentHelp/kartotek/<str:kl_id>/opretpatient.html', context)
When running code I get an OSError for the last line of code shown here.
Have you guys have any advise for this to work?
You are mixing up render with redirect. render renders a template dynamically with attributes from context, where redirect redirects the user to a different view. To call render, you need to provide template name and context. For redirect, you need to provide url name and parameters (if required). Here is how you should do in your code:
def opretpatient(request, kl_id):
kliniknavn = Klinik.objects.get(navn=kl_id)
form = PatientForm()
if request.method == 'POST':
form = PatientForm(request.POST)
if form.is_valid():
form.save()
return redirect('kartotek', kl_id) # url name and parameter
context = {'form':form, 'kliniknavn':kliniknavn}
return render(request, 'DentHelp/kartotek/opretpatient.html', context) # template name and context

Django form errors appearing without error

I made a research here in Stack and my problem is the opposite of the majority, I saw some ways to make it appear, but my problem is that it's appearing when the user hits the "Register" button / Refresh the register page. So it's an annoying thing that appears wherever the user enter/refresh the page because the form is empty.
View.py
#unauthenticated_user
def register(request):
form_u = CreateUser(request.POST)
form_c = CreateClient(request.POST)
if request.method == 'POST':
form_u = CreateUser(request.POST)
form_c = CreateClient(request.POST)
if form_u.is_valid() and form_c.is_valid():
user = form_u.save()
group = Group.objects.get(name='func')
user.groups.add(group)
client = form_c.save(commit=False)
client.user = user
client.save()
return redirect('login')
else:
form_u = CreateUser()
form_c = CreateClient()
context = {'form_u': form_u, 'form_c': form_c}
return render(request, 'register.html', context)
HTML
<form method="POST" action="" id="ativa">
{% csrf_token %}
...
</form>
{{form_u.errors}}
{{form_c.errors}}
<div class="mt-4">
<div class="d-flex justify-content-center links">
Have an account ? Login
</div>
</div>
Print
P.S: The site is in portuguese, but I can share the form link in heroku
Your logic is opposite of what you want:
Initialize the forms with POST data regardless of whether the request is a POST or a GET request, which will result in the errors if there is no POST data.
Then you initialize empty forms when the form data is invalid.
Instead you'll want to pass POST data only if the request is a POST request, and you should initialize empty forms only if the request is not a POST request:
#unauthenticated_user
def register(request):
# If request is POST, validate forms and add objects.
if request.method == 'POST':
form_u = CreateUser(request.POST)
form_c = CreateClient(request.POST)
if form_u.is_valid() and form_c.is_valid():
user = form_u.save()
group = Group.objects.get(name='func')
user.groups.add(group)
client = form_c.save(commit=False)
client.user = user
client.save()
return redirect('login')
# We can remove the else statement here,
# because the function either redirects or resumes
# normal flow and renders the template
# with the form errors.
else:
# Only initialize empty forms when no POST request was made.
form_u = CreateUser()
form_c = CreateClient()
context = {'form_u': form_u, 'form_c': form_c}
return render(request, 'register.html', context)

Django redirect URL into another URL with form

I wouldlike to redirect in another URL when I submit my form.
After submit, I wouldike to have another page with something like "Hello (SummonerName) and Welcome" with URL like this : interface/main/SummonerName=(SummonerName)
Here my code :
forms.py
from django import forms
class SummonerForm(forms.Form):
summoner_name = form.CharField(label='SummonerName:', max_length=100)
views.py
from django.http import HttpresponseRedirect
from django.shortcuts import render
from .forms import NameForm
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/interface/name.html')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'interface/main.html', {'form': form})
and my main.html
<form action="/SummonerName={{ current_name }}" method="post">
<label for="summoner_name">SummonerName: </label>
<input id="summoner_name" type="text" name="summoner_name" value="{{ current_name }}">
<input type="submit" value="Rechercher">
</form>
EDIT :
urls.py
from django.urls import path
from . import views
urlpatterns = {
path('main', view.get_summoner_name)
}
Thank you !
That is what return HttpResponseRedirect does your views.py. You only need to pass your desired url as the first parameter instead of `'/interface/name.html'.
See:
https://docs.djangoproject.com/en/2.2/ref/request-response/#httpresponse-subclasses
For passing on the name, you have many options depending on what you want to do with the inputted data.
A simple solution:
Your example is rather simple and so if you only want to redirect to some url and show the name the user has inputed the easiest would be to pass the name (and any other parameters) via the url as a querystring:
import urllib.parse
# ... omitted code
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# get parameters from the form
params = {
"summoner_name" : form.validated_data["summoner_name"],
# multiple parameters can be passed in this dict e.g.
# "some_other param" : param_value
}
# encode the dictionary into a querystring
q_string = urllib.parse.urlencode(params)
# construct the new URL:
redirect_url = "/interface/success/?summoner_name={}".format(q_string)
return HttpResponseRedirect(redirect_url)
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'interface/main.html', {'form': form})
You can then access these parameters in your views/templates by the request object.
Then, in your urls.py you should match this url pattern by adding to your patterns something like:
path('interface/success/', views.success_view),
and you should of course define that view:
# views.py
def success_view(request):
return render(request, "success.html")
Finally you should add a template success.html. There you could do <h1> Hello {{ request.GET.summoner_name }}! </h1>
A more common usecase
The method above only shows data the user has inputted which is usually not very usefull... More commonly in a form we ask data that we need to save to the db, and thats why I proposed a ModelForm in my comment. It is off the scope of this answer to explain exactly how that would work but in a nutshell: you should define a model in your models.py (i.e. a db table) where you can store data (summoner_name would be a field in such a model).
class SimpleModel(models.Model):
summoner_name=models.CharField(max_length=256)
# other fields you may need
Then you can use a django's generic class based views e.g.
class SimpleFormView(CreateView):
model = SimpleModel
success_url = "interface/success"
urls.py will also need some editing and your tempaltes should comply with django naming conentions too. Do read this if you want to go down that way:
https://docs.djangoproject.com/en/2.2/topics/class-based-views/generic-editing/#form-handling-with-class-based-views

Django Page not found (404) Request Method: No Sales matches the given query [duplicate]

This question already has answers here:
Django: get() returned more than one items -- it returned 3
(4 answers)
Closed 5 years ago.
My python code keeps returning the following error:
Page not found (404) Request Method: GET Request URL:
http://127.0.0.1:8000/geo_gas/edit_sale/ Raised by:
geo_gas.views.edit_sale No Sales matches the given query.
class Sales(models.Model):
gas_qty = models.CharField(max_length=20)
amount = models.CharField(max_length=20)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return a string representation of the model."""
return self.gas_qty
class Meta:
verbose_name_plural = 'Sales'
View.py
def edit_sale(request):
"""Edit an existing sales record."""
entry = get_object_or_404(Sales, pk=1)
if request.method != 'POST':
form = SalesForm(instance=entry)
else:
form = SalesForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('geo_gas:sales'))
context = {'entry': entry, 'form': form}
return render(request, 'ht/edit_sale.html', context)
Urls.py
.......
# Page for editing a sale entry
path('edit_sale/', views.edit_sale, name='edit_sale'),
.......
ht/templates/ht/edit_sale.html
Edit entry:
<form action="{% url 'geo_gas:edit_entry' %}" method='post'>
{% csrf_token %}
{{ form.as_p }}
<button name="submit">save changes</button>
</form>
I have not been able to identify what part of the code is causing the error to occur.
The error is saying that no Sales instance with pk=1 exists, as the error is likely thrown by get_object_or_404(Sales, pk=1). So you might want to check if that's really the case.
You can try checking the pk of your instance by doing Sales.objects.first().pk to see.
Update: How to make it dynamic
Before going into that, it might be useful to understand what RESTful API endpoints are like. But briefly in this context you might want to have something like this
# urls.py
path('sale/', views.list_sale, name='list_sale'),
path('sale/<int:pk>/', views.retrieve_update_delete_sale, name='retrieve_update_delete_sale') # names of the view can be whatever you want
What happens here is that the pk argument is passed from the URL (<int:pk>) to the view as an argument in the function-based view.
Accessing the arguments passed from URLs in your view
def retrieve_update_delete_sale(request, pk): # <-- you will be able to access it here
"""Edit an existing sales record."""
entry = get_object_or_404(Sales, pk=pk) # this is now dynamic!
if request.method != 'POST':
form = SalesForm(instance=entry)
else:
form = SalesForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('geo_gas:sales'))
context = {'entry': entry, 'form': form}
return render(request, 'ht/edit_sale.html', context)
So now if you want to access the Sale instance with pk=1, all you need to do is visit the url with /sale/1/, and so on.

Django - being able to access page only after HttpResponseRedirect

I have a class based view in which I process the form and redirect the user on successful submission like so:
views.py
def get(self,request):
form = self.form_class()
return render(request, template_name, { 'form' : form })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
...
return HttpResponseRedirect(reverse('success'))
return render(request, template_name, { 'form' : form })
urls.py
...
url(r'^submit/success', SubmitView.as_view(), name='success'),
...
It is possible to access url directly by typing success/submit. I don't use any authentication on the site and want the user only be able to access the submit/success page after redirection, so that they are not able to access it directly. How do I do it?
If you are using sessions, you can accomplish it like so:
# in the view where form is submitted
if form.is_valid():
request.session['form-submitted'] = True
return HttpResponseRedirect(reverse('success'))
# in the success view
def get(self, request):
if not request.session.get('form-submitted', False):
# handle case where form was not submitted
else:
# render the template
Instead of redirecting, you could POST to the 'success' page.
Then use if request.method == 'POST':
But beware, this is NOT secure, as headers can be spoofed.
Better to just call the success view from within the POST method, I think.
Have you tried something like this:
if form.is_valid():
...
return HttpResponseRedirect(SubmitView.as_view())
Not sure if this works out of the box, but with a few more tricks you might get what you want.
To add to the answer #miki725 posted I would also make sure you change
request.session['form-submitted'] = False
after you have entered the
if not request.session.get('form-submitted', False):
In order to prevent accessing the page directly or using the back and forward on the browser.

Categories

Resources