What is the recommended way to write views (as functions) in Django?
I am asking in terms of readability, etc.
For example: define the template first, then do the translations, then define models and lastly define context.
Here is some example code:
def index(request): # Landing page, translated to the browser's language (default is english)
template = loader.get_template("koncerti/index.html")
# Translators: This is the text on the homepage buttons
concerts = gettext("Koncerti")
band = gettext("Band")
# Translators: This is the option in the language-switch box
foreignLanguage = gettext("eng")
koncertiUrl = '/koncerti/international' # The URL slug leading to 'koncerti' page
bandUrl = '/band/international' # The URL slug leading to 'band' page
translationMode = '' # The URL slug that is leading to slovenian translation mode
context = {
'Concerts' : concerts,
'Band' : band,
'foreignLanguage' : foreignLanguage,
'koncertiUrl' : koncertiUrl,
'bandUrl' : bandUrl,
'translationMode' : translationMode
}
return HttpResponse(template.render(context, request))
I think you are doing too much in the view. A view is supposed to make the proper querysets, and make changes to the database. It's main task is thus to decide what to render, and what to update. Not how to update it.
Deciding about translations, url resolution, etc. is normally a task of the template. The template has template tags for that such as {% trans … %} [Django-doc] and {% url … %} [Django-doc].
Using the {% url … %} over raw URLs you construct yourself is strongly advisable. It makes the odds of making a mistake smaller, and furthermore it is clear to what view you are referring.
You furthermore might want to use render(..) [Django-doc]. This function combines looking for a template, rendering that template with the template render engine, and returning a HTTP response.
from django.shortcuts import render
def index(request):
return render(request, 'koncerti/index.html', {})
In the template, you can then render this with:
{% load i18n %}
{% trans 'Koncerti' %}
Related
I'm trying to send an object obtained with a ModelChoiceField into a view via urls and views parameters by I don't think that's the right method. I don't know if I have to use POST or GET method.
When I use the GET method, the object is displayed in the url but the view stay the same.
With the POST method, django send errors messages about parameters.
EDIT : I don't remeber the exact messages, I can't recover them for now but as I remember they said something like Reverse for argument not found
My form
class IndividuForm(forms.Form):
individu = forms.ModelChoiceField(queryset=Individu.objects.all()
Here's the view with the form
def index(request):
individu_form = IndividuForm(request.GET or None)
if individu_form.is_valid():
individu_id = individu_form.cleaned_data['individu'].id
HttpResponseRedirect('%i/raison/' % individu_id)
return render(request, 'derog_bv/index.html', {'individu_form':individu_form})
The template where the form is displayed
<form action="{% url 'index' <!-- Errors when I put parameters here --> %} method="<!-- GET or POST -->">
{% csrf_token %}
{{ form }}
<input type="submit">Suivant</input>
</form>
The view where I want to get my object
def raison(request, """ error with this parameter : individu_id"""):
individu = get_object_or_404(Individu, pk=individu_id)
URLs
urlpatterns = [
path('', views.index, name='index'),
path('<int:individu_id>/raison/', views.raison, name='raison'),
]
Ok, so:
1/ you definitly want to use the GET method for your form (you're not submitting data for processing / server state change)
2/ I don't know why you'd want to pass parameters (nor which parameters FWIW) to the {% url %} tag in your index template - you're submitting the form to the index view, which doesn't expect any additional param. Actually you could just remove the action attribute of the HTML form tag since the default (submitting to the current url) is what you want.
3/ your views.raison prototype must match the url definition, so it has to be:
def raison(request, individu_id):
# code here
4/ in your index view:
HttpResponseRedirect('%i/raison/' % individu_id)
you want to build the url using django.core.urlresolvers.reverse instead :
from django.core.urlresolvers import reverse
def index(request):
# code here
if ...:
next = reverse("raison", kwargs={"individu_id": individu_id})
return HttpResponseRedirect(next)
or - even easier - just use the redirect shortcut:
from django.shortcuts import redirect
def index(request):
# code here
if ...:
return redirect("raison", individu_id=individu_id)
There might be other issues with your code but from the infos you posted, those hints should at least put you back on tracks.
With the following renderer...
from django.shortcuts import render
...
return render(request, 'template.html', {'context':context,})
Is it possible to override the render classe's methods so that I can in certain circumstances interpret template tags myself for example if I find a tag consisting of a certain format such as...
{% url 'website' page.slug %}
I could point it to...
/theme/theme-1/page.html
or
/theme/theme-2/page.html
depending on extranious settings.
The render method is just a shortcut for:
template = loader.get_template(''template.html')
context = {
...,
}
return HttpResponse(template.render(context, request))
Therefore, it is not the correct place to try to change the behaviour of the url tag.
For the example you have given, it looks like the website should be a variable that holds theme-1 or theme-2. You can then pass the variable to the url tag instead of the string 'website'.
{% url website page.slug %}
If that is not possible, you could create a custom template tag my_url that returns the correct url depending on your settings.
I have a few components on my website that appear on many pages. DRY in mind, I would like to factor them out in separate snippets that I can include in the views that need them.
If it were only static items, then an {% include "snippet.html" %} would be the perfect solution. How can achieve a similar thing for snippets that include forms (and hence logic in the view) or require calculations before being displayed? Also, I would like to be able to nest the snippets several levels deep.
I know I can put simple logic in the template, using {% if ... %} ... {% endif %} blocks, but this turns into horrible spagetthi very soon and I want to keep the business logic separated from the presentation logic.
I am imagining a pattern as follows (here with oversimplified business logic):
def view1(request):
"Display some data"
total = get_total_vote_count()
return render(request, 'snippet1.html', {'total': total})
def view2(request, pk):
"Display some data about the article with primary key pk."
votes = get_votes_for_article(pk)
render1 = view1(request)
return render(request, 'snippet2.html', {'votes': votes, 'render1': render1})
def view3(request, pk):
"Display article pk and some additional data from view1 and view2":
article = get_object_or_404(Article, pk=pk)
render2 = view2(request, pk)
return render(request,
'article.html',
{'article': article, 'render2': render2},
)
with the templates something like:
# in snippet1.html:
{{ total }}
# in snippet2.html:
<p>Votes for this article: {{ votes }} out of {{ render1 }} total votes.</p>
# in page.html:
{% extends "base.html" %}
{% block "content" %}
<h1>article.title</h1>
<p>article.text</p>
<small>{{ render2 }}</small>
{% end block "content" %}
Note that there will be more views that will use view1 and view2 (e.g. an overview of the votes for all articles); that is why I have factored them out in separate functions.
How can I make this work?
Or is there a better trick in the Django toolbox to make this work without repeating view1 and view2 every time I want to use the same snippets in other views?
This is what custom template tags - specifically, inclusion tags - are for: rendering a template fragment with its own context.
what about django middleware, you can use middleware for this case. view1 and view2 repeating every time right ?. then attach the render1 and render2 to your request.
Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.
https://docs.djangoproject.com/en/dev/topics/http/middleware/
Django says there's 3 ways to turn off autoescape:
Use |safe after the variable
Use {% autoescape on %} and {% endautoescape %} within blocks
Use a Context like context = Context({'message': message}, autoescape=False)
(1) and (2) work fine. But I have the situation where I have templates to generate plain-text push notifications, and I have LOADS of templates to build and maintain. I could go through and put the {% autoescape on %} and {% endautoescape %} tags in all of them, but (3) should allow me to do it in one line in the view.
The template:
{% block ios_message %}{{message}}{% endblock %}
The view:
message = u"'&<>"
context = Context({'message': message}, autoescape=False)
render_block_to_string(template_name, 'ios_message', context)
The output:
u''&<>
The code for block_render.py is from here: https://github.com/uniphil/Django-Block-Render/blob/master/block_render.py. I'm using it as is from there.
Anyone know what gives?
Take a closer look to function render_block_to_string():
def render_block_to_string(template_name, block, dictionary=None,
context_instance=None):
"""Return a string
Loads the given template_name and renders the given block with the
given dictionary as context.
"""
dictionary = dictionary or {}
t = _get_template(template_name)
if context_instance:
context_instance.update(dictionary)
else:
context_instance = Context(dictionary)
return render_template_block(t, block, context_instance)
The 3rd arg should be a dict, not context. Otherwise it would use the normal context instance.
So I believe it should be:
render_block_to_string(template_name, 'ios_message', {}, context)
Hope it helps.
I could solve it my doing it like that:
from django.template.context import make_context
from django.template.loader import get_template
# Getting the template either by a path or creating the Template object yourself
template = get_template('your/path/to/the/template.html')
# Note here the 'template.template' and the 'autoescape=False' parameter
subject = template.template.render(make_context(context, autoescape=False))
Found it by doing that myself. Because by default the autoescape setting will be used from the engine
https://github.com/django/django/blob/4b6dfe16226a81fea464ac5f77942f4d6ba266e8/django/template/backends/django.py#L58-L63
Django Version: 2.2.1
Wanted to comment but looks like I don't have enough reputation since this is a newish account
You can turn off the autoescape found here: https://github.com/django/django/blob/529c3f264d99fff0129cb6afbe4be2eb11d8a501/django/template/context.py#L137
i.e.
Context(data_dict, autoescape=False)
{% include 'django.contrib.auth.views.login' %}
I don't want to write everything by hand.. I hate this really, django full of automatic stuff.
Goal is to include registration/login.html into base.html, so that I could have this form in every page
If I include only template itself (registration/login.html), problem appears that "form.login", I mean "form" var is not defined because this one comes from VIEW which called when you going to login url. So how can I call that view MANUALLY with include or at least to grab django.contrib.auth.views.login variables by my self in my own view and pass then to base.html?
P.s. It's not just about login form, I think there will be more situations like this
I have found better solution in #django irc.
They called inclusion tags
I'll give you my code, because I got lot's of problem learning new stuff in django =)
file: templatetags/form_login.py
from django import template
register = template.Library()
from django.contrib.auth.forms import AuthenticationForm
#register.inclusion_tag('registration/login.html')
def form_login():
return { 'form': AuthenticationForm() }
Now you can have your form anywhere, this will prerender template and THAT'S IT! no stupid context processors which requires to modify whole project settings.py, which is really sux if you writing stand alone little application..
If you need login-form on every page
Create a context processor:
def login_form_processor(request):
return {
'login_form': LoginForm(request.POST or None)
}
Add it to settings.CONTEXT_PROCESSORS.
Include the template for login form:
{% with login_form as form %}
{% include "registration/login.html" %}
{% endwith %}
You can also make you form lazy-loading, so form will not be created until it is used for the first time.
from django.utils improt functional
def login_form_processor(request):
create_login_form = lambda: LoginForm(request.POST or None)
return {
'login_form': functional.lazy(create_login_form, LoginForm)
}
But I guess you won't want the lazy-loading feature, because login-form is cheap to initialize.
Reusing views
Concerning the "grabbing variables" part from your question: you cannot grab variable from view. Django view is method which returns response object. You can not get variables from response. However some of views accept extra_context and other attributes. Those attributes allow you to configure those views in urls, or to wrap them with your own view, for example:
def my_login_view(request):
some_extra_data = get_some_data()
extra_context = {
'some_extra_var': some_extra_data
}
return login_view(request, extra_context=extra_context, template="my_template.html")
This is not exactly grabbing the variables from views, more like augmentation of existing views.
If you expect to have more situations like this, do less data-porcessing in views. Call some methods which checks for permissions. Collect some data from context-processors. Return rendered response. Now you can reuse the data in other views.
You can specify the action on the form html to point to the URL that accesses the corresponding view.
If you want a form, say called as login_form always populated in all templates, then put it in the context_processors.
Browsing the code for django.contrib.auth.views, you will see that the variables form, site and *site_name* are passed to the template.
Either you (1) provide your custom registration form or (2) you can just import django.contrib.auth.forms.AuthenticationForm in your view if you want to use it.