Django : Call a view from a template - python

How to call a view in my layout (template) ?
For example : I've a form on every pages of my website. I need to generate CSRF token for each pages but I don't want to put the generation code on every view.
Thank you.

In Django, once you reach the template, I don't believe you can call something to the effect of Zend's Action Helper.
Obviously, you could do an AJAX call to an exposed url in Django and retrieve the necessary data. In that case you can provide the csrf token to the ajax calls as follows..
$.ajaxSetup({data: {csrfmiddlewaretoken: '{{ csrf_token }}' },});
I'm not a hundred percent sure but you can implement something like Zend's Action Helper in a decorator (which could be applied to multiple views of your choice before processing the request) or in a context processor (which is applied to all views' processed request).

If your form is just HTML, then simply have a template containing the HTML and include that from other templates (or have it in your base template). To generate a CSRF token, you simply use {% csrf_token %} in the template, as explained at https://docs.djangoproject.com/en/dev/ref/contrib/csrf/
If you want to generate the HTML of a Django form, then you could add a context processor - explained at https://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext - that generates the form and then that will be available to all templates.
def form_processor(request):
form = Form()
return { 'form': form.as_p() }
Template:
<form>{% csrf_token %}{{ {{ form }}</form>

Related

How to submit data from a Search Form within the base.html of a Flask app?

I'd like to have a search form in my navbar which is 'included' in my base.html template. This means the search bar can be filled out and submitted from any of my templates that extend from base.html. The results will be displayed on my index page, so once the form is submitted, the form data should be redirected to my index route and eventually passed to the index.html template.
I think this might be possible with context processing, by creating a #app.context_processor function, but I haven't found a lot of documentation on it- particularly using flask. Can context processing be used for this kind of thing or is it more for returning global variables back into the template?
If it can be done, what do I need to do to set it up? Any help appreciated
The action attribute in the form tag can define which view to post the data to in the search form. The view itself can then handle the data and redirect.
# base.html
<form action="{{ url_for('index') }}" method="post">
...
</form>
# view.py
#route('/', methods=['GET', 'POST']
def index():
if request.method == 'POST':
# handle form data here
return render_template('index.html', form_data=data)

View function is not being called after submitting form in django

I have made a simple form inside a html file whose path is www.site.com/posts/5. Whenever the form is submitted, it redirects back to the same page i.e www.site.com/posts/5 displaying a message given by user in the form.
However, whenever the form is submitted it doesn't call the foobar view.
The urls.py, views.py and html files are as follows:-
urls.py
urlpatterns = [
path('posts/<int:foo>',user_views.display, name="display",
path('posts/<int:foo>',user_views.foobar, name="makefoo"),
]
views.py
def foobar(request, foo):
#do something
html file
<form name="fooform" action= "{% url 'makefoo' 5 %}" method = "post">
{% csrf_token %}
<input type="text" name="FOO_BODY" maxlength="300" required>
<input type="submit" value="comment">
<input type="reset" value="clear">
</form>
Edit : user_views is just from user import views as user_views
You can not attach two views to the same URL. The {% url ... %} template tag, only generates a URL for that path. But if there is a "url clash", then it is possible that the requests ends up in the other view.
You thus should define another URL, or encode the post logic in the display view. In case of a POST request, you can thus first take the necessary steps, and then for example return a redirect to the page, such that we can again render the page:
def display(request, foo):
if request.method == 'POST':
# do something
return redirect(display, foo=foo)
#do something else (original code)
return HttpResponse(..)
This is the famous Post/Redirect/Get web development design pattern [wiki]. This is usually better than returning a HTTP response directly in the POST, since if the user performs a refresh, the POST will be performed a second time.
As mentioned in the comment by #williem, you have two path() defined in the urls.py.
Always First matching route will be picked up from the url route table. So whenever r^'posts/' is requested it will call the display() from the user_views, so it will never go to foobar(). Either remove the route with display() or change the sequence. Also, I assume you imported the user_views.
Reference:
https://docs.djangoproject.com/en/2.1/topics/http/urls/

Django not passing data to views on POST request

I've created a drop-down menu that is supposed to pass data to a view that'll help filter a queryset. However, it doesn't seem like the data is actually being passed to the view. Below is the relevant code I've written.
template.html
<!-- Query based content for dropdown menu -->
<form method="POST" action="{% url 'property-selected' %}" id="property-select">
{% csrf_token %}
<select class="dropdown-content" onchange="this.form.submit()" name="property-select">
{% if current_user_meters %}
<option disabled selected> -- select an option -- </option>
{% for meter in current_user_meters %}
<option class="dropdown-menu-option" value="{{meter.id}}">{{meter.name}}</option>
{% endfor %}
{% else %}
<option>You don't have any meters</option>
{% endif %}
</select>
</form>
views.py
def property_selected(request):
if request.method == 'POST':
selection = request.POST.get('property-select')
current_user_groups = Group.objects.filter(
id__in=request.user.groups.all()
)
current_user_properties = Property.objects.filter(
groups__in=current_user_groups
)
current_user_meters = Meter.objects.filter(
meter_id__in=current_user_properties
)
selected_meters = Meter.objects.filter(name=selection)
selected_meter_data = MeterData.objects.filter(
name=selection
).order_by('date')
return render(request, 'properties/property-selected.html', {
'current_user_meters': current_user_meters,
'selection': selection,
'selectected_meters': selected_meters,
'selected_meter_data': selected_meter_data,
})
For the querysets in the views file, the selection variable doesn't seem to be getting anything, which is where I want the data from the POST request to go. I want the data from the POST request to go there so my selected_meters and selected_meter_data queries will work as intended.
The values in the property-select options are IDs, but you are trying to filter MeterData by name with those values. Either filter by id, or use the name attribute as the option values.
You need to either use a form to ingest your data (thereby providing cleaning and validation) or access request.body instead of request.post, and then parse it for yourself.
From the documentation, emphasis mine (https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.POST):
HttpRequest.POST¶ A dictionary-like object containing all given HTTP
POST parameters, providing that the request contains form data. See
the QueryDict documentation below. If you need to access raw or
non-form data posted in the request, access this through the
HttpRequest.body attribute instead.
It’s possible that a request can come in via POST with an empty POST
dictionary – if, say, a form is requested via the POST HTTP method but
does not include form data. Therefore, you shouldn’t use if
request.POST to check for use of the POST method; instead, use if
request.method == "POST" (see HttpRequest.method).
I'd recommend passing the request into a form. It gives you some nice functionality down the line, and lets you avoid parsing request.body for yourself, even if it's an extra step.

Django: provide global method to all (class-based) views?

I need a button, included in the header of every page in my Django application, to trigger some global method, to toggle a "mode" setting in the session. There are two modes: 'preview' and 'publish'.
One solution I've come up with: duplicate a post() method in every (class-based) view, to handle the mode change. This hardly seems DRY.
Another would be to inherit all of my CBVs from a single superclass, or use a mixin. I suppose this is a possibility.
A better solution perhaps: I've setup a context_processor to handle publishing the mode globally to my templates. This works fine. I've also setup a middleware class with process_request which could, theoretically, handle POST requests globally. But how do I call this process_request method from my templates?
My current stab at it follows. How do I toggle the "preview" and "publish" buttons in my template, and call the middleware?
template.html:
<html>
<head></head>
<body>
<header>
<form method="post">
{% csrf_token %}
<!-- button toggle -->
{% if mode == 'preview' %}
<button name="mode" value="publish">Publish</button>
{% else %}
<button name="mode" value="preview">Preview</button>
{% endif %}
</form>
</header>
</body>
</html>
middleware.py:
class MyMiddleware(object):
def process_request(self, request):
update_mode(request)
def update_mode(request, new_mode=None): # how do I call this from template?
modes = [
'preview',
'publish'
]
# ensure default
if not request.session.get('mode', None):
request.session['mode'] = 'preview'
# set new mode
if new_mode and new_mode in modes:
request.session['mode'] = new_mode
context_processor.py:
def template_mode(request):
context = {
'mode': request.session['mode']
}
return context
You don't "call" middleware: that's not at all how it works. Middleware is invoked on every request, so in your case the update_mode function would always run.
A better solution would be to get the form containing the button to post to a new URL, which invokes a view to update the mode. You could add a hidden field containing the current URL - which you can get from request.path - and the update mode view can redirect back to that URL after doing its work.
I wouldn't do it that way - how about making a template tag for the form?
In your templatetags.py:
def set_session_mode_form():
return {'session_form': SessionForm()}
register.inclusion)tag("<path-to-your-template>",set_session_mode_form)
Then your session form sends to a view that updates the session variable you want.
To use it, just load the tags on your page and use {% include %}. This way, its very easy to add to any page, and keeps it DRY.

The "next" parameter, redirect, django.contrib.auth.login

I'm trying to redirect users to custom url "/gallery/(username)/" after successfully logging in. It currently redirects to the default "/account/profile/" url While I know what I can override the redirect url in my settings.py, my url is dynamic thus it will not work.
Documentation states that I need to use the "next" parameter and context processors. I have the {{next}} in my template, but I'm confused on how to actually pass the "/gallery/(username)". Any help would be greatly appreciated.
p.s: I'm trying to steer away from writing my own login view.
Django's login view django.contrib.auth.views.login accepts a dictionary named extra_context. The values in the dictionary are directly passed to the template. So you can use that to set the next parameter. Once that is done, you can set a hidden field with name next and value {{ next }} so that it gets rendered in the template.
I confess I usually use 2 redirects in order to get something like this to work.
First, Make your own registration/login.html page. You can copy-and-paste the html example in this section of the authentication docs to make the process a little easier. Instead of using the dynamic '{{ next }} variable from the context, however, hardwire the value of next to go to a generic landing view of logged-in users
<input type="submit" value="login" />
<input type="hidden" name="next" value="/gallery/" />
Then, in the view that you map to the /gallery/ URL, extract the User object from the request (since the user will now be logged in, especially if the gallery view is wrapped in a #permission_required or #login_required decorator. Use that view to redirect to the appropriate user-specific gallery page:
#login_required
def gallery(request):
url = '/gallery/%s/' % request.user.username
return HttpResponseRedirect(url)
If you already have the custom template for login form you need to add the following inside your <form> tag:
<input type="hidden" name="next" value="{{next}}" />
BTW, you don't have to create your own login view. django.contrib.auth.views.login works fine. You only need to create a template for it (registration/login.html)
being an newbie to django and stumbling over this somewhat older thread i found a differing solution for the problem of dynamically (=override a custom default only if needed) setting the next-param that i'd like to share (working fine with django 1.5, earlier versions untested):
just as django-d i wanted avoid repetition and a custom login-view, so i used the stock django.contrib.auth.views.login-view by adding the line of
url(r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'myapp/login.html',}, name='login'),
to my urls.py and within the login.html-templates form-element:
{% if not next or not next.strip %}
{# avoid django.contrib.auth.views.login s default of /account/profile/ #}
{% url 'afterlogindefaultview' as next %}
{% endif %}
<input type="hidden" name="next" value="{{ next }}" />
which to my understanding follows the decoupling-practice of the url-configurations from the views.
so for views that should redirect to my apps login and afterwards head to a non-default view
i use
return HttpResponseRedirect('%s?next=%s' % (reverse('login'), reverse('mycustomnext')) )
from the view where i want to have the user to log in. i use this to get back to the view where i left off for logging the user in.
You can use a static redirect to /loggedin/ and then associate the url to a view that makes the correct redirect.
Login takes an extra step but if you want to use django's view it does the job.
create your own view for logging in, with it's own url, don't use the admin's one.
you can store the next page in the session, or pass it as a GET parameter to the login view
(i.e. /login?next=gallery) just don't forget to sanitize and validate that value before redirecting to it.

Categories