I was reading the docs for the django class based view when I encountered a code that I couldn't really understand. If someone could explain the "get" part it would be really helpful.
here is the view code
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.generic import View
from .forms import MyForm
class MyFormView(View):
form_class = MyForm
initial = {'key': 'value'}
template_name = 'form_template.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
return render(request, self.template_name, {'form': form})
What's the "initial" for ? And how the get function is contributing to this code?
The get function renders the form, i.e. produces HTML of the form. When given a dictionary of initial values, then even on the first visit, the form will already be filled — with the initial values. Otherwise it would be empty.
The class variable initial is merely storing the initial values, so they can be used by the get function. You could put the variable also in the get method or omit it entirely:
def get(self, request, *args, **kwargs):
form = self.form_class(initial={'key': 'value'})
return render(request, self.template_name, {'form': form})
(Would not do it though, as the initial values are then slightly less visible, and they really are important.)
Also, have a look at the documentation regarding bound and unbound forms, to understand the difference between initial and default values.
initial = {'key': 'value'}
is a place to set some default values for the form.
self.form_class(initial=self.initial)
uses those defaults to fill the form when there is a GET request.
Related
I am experimenting with Django class based views and for some reason this simple post view does not seem to be working. When looking at my terminal it gets stuck on the GET method / validation of the csrf token. It is either not being validated or not saving to the database and therefore not being redirected to the thank-you page. I am not sure how to solve this issue. It definitely is not being saved to the DB, but as far as I am aware everything is correct here. I also am using a class based form which is why I am simply just calling form.save() The code is as follows:
class ReviewView(View):
def get(self, request):
form = ReviewForm()
return render(request, 'reviews/review.html', {
'form': form
})
def post(self, request):
form = ReviewForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thank-you')
return render(request, 'reviews/review.html', {
'form': form
})
Any help or ideas is greatly appreciated!
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.
DetailStory subclasses DetailView and ModelFormMixin thus presenting the DetailView of a certain story and a form at the end. However, on filling the form and submitting the data, the data is saved in the databases but it is still shown on the form (in addition to the one now displayed on the DetailView). How do I present an empty form after submitting it? (Here is the code sample)
class DetailStory(DetailView, ModelFormMixin):
model = Story
template_name = 'stories/detail_story.html'
context_object_name = 'detail'
form_class = CommentForm
def get(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
return DetailView.get(self, request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
if self.form.is_valid():
obj = self.form.save(commit=False)
obj.user = self.request.user
obj.memoir = self.get_object()
self.object = obj.save()
return self.get(request, *args, **kwargs)
def get_object(self):
item_id = crypt.decode(self.kwargs['story_id'])[0]
obj = get_object_or_404(Story, Q(privacy='public') | Q(user_id=self.request.user.id), pk=item_id)
return obj
get_form uses the request data to construct the form as per the docs
If the request is a POST or PUT, the request data (request.POST and request.FILES) will also be provided.
So simply don't make your post function go back through the get, just have it redirect to your required place or do anything differently to pointing it at the get function.
return redirect('mynamespace:story_detail', story_id=self.object.pk)
You may wish to read this answer for a list of technical details you should consider whilst making your application. In particular,
Redirect after a POST if that POST was successful, to prevent a refresh from submitting again.
I have tried following this suggestion to pass string parameters to a class based view but it does not seem to work.
the url:
url(r'^chart/(?P<chart_name>\w+)/$',
ChartView.as_view(chart_name='chart_name'), name="chart_url"),
the view:
class ChartView(View):
template_name = "chart.html"
chart_name = None
def post(self, request, *args, **kwargs):
form = DatesForm(request.POST)
context = {
'form': form
}
return render(request, self.template_name, context)
def get(self, request, *args, **kwargs):
print("test")
form = DatesForm()
# fetch plot data (default values used)
context = {
'form': form,
'chart_name': self.chart_name
}
return render(request, self.template_name, context)
the link that is supposed to be redirecting to the view:
Sometext
(namespace 'chartboard' given in the project's urlconf).
the error:
NoReverseMatch at /chart/lords/
Reverse for 'chart_url' with arguments '()' and keyword arguments '{}' not found. 1 pattern(s) tried: ['chart/(?P<chart_name>\\w+)/$']
For what its worth, "test" gets printed twice to the console output (why?)
Using django 1.8.11 and python 3.4.3 on Ubuntu 14.04.04
You should access the chart_name using kwargs:
# urls.py
url(r'^chart/(?P<chart_name>\w+)/$',
ChartView.as_view(), name="chart_url"),
# and then in the view
class ChartView(View):
template_name = "chart.html"
def get(self, request, *args, **kwargs):
form = DatesForm()
context = {
'form': form,
'chart_name': kwargs['chart_name'] # here you access the chart_name
}
return render(request, self.template_name, context)
The post you have considered for implementing this is for making sure that a variable is available in templates and that is taken care of by setting it up in context which is passed to the template render.
The problem you are facing here is to access a named group defined in the url pattern.
Here is more documentation on how django process a request when you try to access a URL.
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.