reusing views inside other views in Django - django

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/

Related

django - Count objects and sum values in a field

I want to include some basic statistics about a model in a stats.html file. The variables don't show in the html. What am I doing wrong?
from django.shortcuts import render, get_object_or_404, redirect
from django.db.models import Avg, Sum, Count
from .models import Production
def statistics(request):
nr_of_plays = Production.objects.count()
nr_of_actors = Production.objects.aggregate(num_actors=Sum('nr_actors'))
nr_of_audience = Production.objects.aggregate(num_audience=Sum('est_audience'))
context = {
'nr_of_plays': nr_of_plays,
'nr_of_actors': nr_of_actors['num_actors'],
'nr_of_audience': nr_of_audience['num_audience'],
'test':'abc'
}
return render(request, 'stats.html', context)
The model:
class Production(models.Model):
title = models.CharField(max_length=200)
nr_actors = models.IntegerField(default=0)
est_audience = models.IntegerField(default=0)
...
urls.py:
path('stats/', views.statistics, name='stats'),
the relevant section of base.html:
<copyright class="text-muted">
<div class="container text-center">
<p>© One World Theatre - {% now "Y" %} {% include 'stats.html' with test=test %} </p>
</div>
</copyright>
And the stats.html template:
{% load static %}
{{ test }} - Stats: {{ nr_of_plays }} plays produced, involving {{ nr_of_actors }} actors, seen by {{ nr_of_audience }} people.
the output:
© One World Theatre - 2020 - Stats: plays produced, involving actors, seen by people.
EDIT:
I didn't mention that I'm using my template stats.html in my base.html template like this {% include 'stats.html' %}. When I add with test=test to the include tag, the test text shows. But when adding with nr_of_plays=nr_of_plays nothing happens :-/.
I ended up forgetting about trying to {% include 'stats.html' %} in my base template and just added those variables where I need them, works great. Not DRY, but what to do... .
EDIT 2:
I was too quick to cry victory. Edited the question with the latest code. Passing the variables in the view that handles the main content block works, but that means I would have to add them in every single view (not DRY). Still not getting what doesn't work with my setup. example.com/stats.html renders exactly what I want, but doesn't show the variables when I include it in my base.html. with test=test doesn't do anything. Clueless (and thankful for the help sofar).
Aggregate returns a dictionary.
You need to access its value via the key
context = {
'nr_of_plays': nr_of_plays,
'nr_of_actors': nr_of_actors['nr_actors_sum'],
'nr_of_audience': nr_of_audience['est_audience_sum']
}
Alternatively you can specify a custom key name instead of the default composite one:
nr_of_actors = Production.objects.aggregate(num_actors=Sum('nr_actors'))
nr_of_audience = Production.objects.aggregate(num_audience=Sum('est_audience'))
Note: .all() is redundant and can be removed
Base on your latest confession and symptoms, you don't seem to be going to your statistics view.
Looks like the url is rendering another view, which also extends base.html confuses you that you are in the right view.
One way to test it is to put a print statement in your statistics view and see if it prints anything in the console:
def statistics(request):
print(111111111111111111111111111111)
...
return render(request, 'stats.html', context)
Second thing is, if your base.html includes stats.html, you shouldn't be rendering the stats.html directly, you should pass the context to a template that extends base.html.
Third thing is, refer to Pynchia's answer to properly get the count of aggregated queryset.

What is the optimal way to write function-based views in Django?

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' %}

Check and clear filters with django-filter

I am using django-filter to filter a ListView and would like to display a "Clear all filters" link if any filters are applied.
Due to the generic nature of the filtering system I haven't yet found a straightforward way to achieve this.
The only thing I came up with so far is to return the regular queryset in the get_queryset method of the view if a "clear" flag is present in the request, however this doesn't actually clear the filters - it just returns all the data.
Does anyone have a solution/idea for this?
Update: Solution
After Jerin's comment I decided to solve this problem in 2 separate parts:
has filter:
I check if any of the fields I defined in my filter class are in the request. My solution looks a bit different as I'm using class based views so I abstracted it away in a mixin but if you're using simple views like here, you could just do:
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
has_filter = any(field in request.GET for field in
set(f.get_fields()))
return render(request, 'my_app/template.html', {
'filter': f,
'has_filter': has_filter
})
clear all filters:
A simple redirect to your list view:
{% if has_filter %}
{% trans 'Clear all filters' %}
{% endif %}
Here is the mixup version of the answer (combination of mine and Chris)
You could place a Clear all filters button and that will redirect to your default ListView (/host/end/point/).
But some non-filter parameters (such as pagination or something else) may occur in URL. So the better option is, check for any filter fields in URL and if so, display the filter clearing link
The opted solution is,
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
has_filter = any(field in request.GET for field in set(f.get_fields()))
return render(request, 'my_app/template.html', {
'filter': f,
'has_filter': has_filter
})
and in template,
{% if has_filter %}
{% trans 'Clear all filters' %}
{% endif %}
Just make a button and point to the base search field.
<a class="btn btn-warning" href="{% url 'App:FilterView' %}">Reset</a>
If your FilterSet instance is available on the template you can check for filter.is_bound like this:
{% if filter.is_bound %}
Clear filters
{% endif %}
If you are using the FilterMixin or the FilterView, your FilterSet instance will be available as filter to the template as above.
I like this simple solution, however when I attempt to use it the current filter parameters are some how getting appended to the url even though it's the base url in the anchor.
So hovering over the button my link (determined using {% url 'app:view' %} shows
localhost/app/view correctly
However when clicking the button the url in browser has the parameters appended
localhost/app/view/?filter1=val1&filter2=val2 etc.
Is django caching something? Is browser (Chrome) caching? Can I force something in the anchor to not use them?
Answered my own, but for anyone else passing by:
I had the anchor on a button within a the filter form, although it was not a submit button moving it outside the form gave the desired result.

Include template displaying a form

What I want to do is include a form from a separate template at the bottom of a given page, lets say; "example.com/listdataandform/".
The form-template "form.html" displays the form as it should when the view is included in the URLConf. So I can view with "example.com/form/"
What I have so far goes something like this:
{% extends "base/base.html" %}
{% block title %} page title {% endblock %}
{% block content %}
<h2>some "scene" data</h2>
<ul>
{% for scene in scenes %}
<li>{{ scene.scene }} - {{ scene.date }}</li>
{% endfor %}
</ul>
{% include "tasks/form.html"%}
{% endblock %}
The code inside "block content" works as it should, since it is defined with it's corresponding view for the url "example.com/listdataandform/".
{% include "tasks/form.html"%}: This only displays the submit button from form.html, as expected. I realize by only doing this: {% include "tasks/form.html"%}, the corresponding view method is never executed to provide the "form"-template with data.
Is there any way to this without having to define the view to a specific pattern in urls.py, so that the form can be used without going to the that specified URL..?
So I guess the more general question is; how to include templates and provide them with data generated from a view?
Thanks.
For occasions like this, where I have something that needs to be included on every (or almost every) page, I use a custom context processor, which I then add to the TEMPLATE_CONTEXT_PROCESSORS in settings.py. You can add your form to the context by using this method.
Example:
common.py (this goes in the same folder as settings.py)
from myapp.forms import MyForm
def context(request):
c = {}
c['myform'] = MyForm()
return c
You can also do any processing required for the form here.
Then add it in your settings.py file:
settings.py
.
.
TEMPLATE_CONTEXT_PROCESSORS = (
'''
All the processors that are already there
'''
"myproject.common.context",
)
.
.
I realize by only doing this: {% include "tasks/form.html"%}, the corresponding view method is never executed to provide the "form"-template with data.
Indeed. You included a template, and it really means "included" - ie: "execute in the current context". The template knows nothing about your views, not even what a "view" is.
How does this help me executing the view for the included template to provide it with form data?
It doesn't. A Django "view" is not "a fraction of a template", it's really a request handler, iow a piece of code that takes an HTTP request and returns an HTTP response.
Your have to provide the form to the context one way or another. The possible places are:
in the view
in a context processor (if using a RequestContext)
in a middleware if using a TemplateResponse AND the TemplateResponse has not been rendered yet
in a custom template tag
In all cases this will just insert the form in your template's context - you'll still have to take care of the form processing when it's posted. There are different ways to address this problem but from what I guess of your use case (adding the same form and processing to a couple differents views of your own app), using a custom TemplateResponse subclass taking care of the form's initialisation and processing might just be the ticket.

loop in Django template: how to control the loop iterator?

I'm using Django to show a list of posts. Each post has a 'is_public' field, so if one post's 'is_public' equals to False, it should not be shown to the user. Also, I want to show a fixed number of posts in one page, but this number can be changing depending on views.
I decided to crop the queryset in template as a few views are using the same template, generating it in the view means a lot of repeated codes.
If written in python, it should look like this:
i=number_of_posts_to_show_in_one_page
while i:
if qs[i].is_public == True:
#show qs[i] to the page
i--
As the django template does not support while loop and for loop seems hard to control, is there a way of achieving this? Or should I do it in another way?(One idea is to crop the qs before looping)Thanks!
Update:
I've written this template tag to pre-process the queryset:
#register.simple_tag(takes_context=True)
def pre_process_list(context,list,numbers):
#if not user.has_perm('admin'):
context['result_list']=list.filter(is_public=True, is_removed=False)[0:numbers]
#else:
#context['result_list']=list[0:numbers]
return ''
Before using for loop in the template, I'll pass the queryset to this templage tag, and use a simple for loop to show its result.
If in the future I want to show non-public posts to admins(which is not decided yet), I can write in some logic like the commented ones, and have them styled differently in the template.
{% for post in posts %}
{% if post.is_public %}
{{ post }}
{% endif %}
{% endfor %}
Though this would be a perfect use case for a manager.
You could write a simple manager that filters public posts.
class PublicPostManager(models.Manager):
def get_query_set(self):
return super(PublicPostManager, self).get_query_set().filter(is_public=True)
Then you would add it to your Post Class:
class Post(models.Model):
...
public = PublicPostManager()
Then you could pass post.public.all() as public_posts to your template and simplify your loop:
{% for post in public_posts %}
{{ post }}
{% endfor %}
#arie has a good approach with the manager, but you can easily do the same without writing a manager:
# View
posts = Post.objects.filter(is_public=True) # or use the manager
# Now, you can either limit the number of posts you send
# posts = posts[:5] (only show five in the view)
return render_to_response('foo.html',{'posts':posts})
# Template
# Or you can do the limits in your template itself:
{% for post in posts|slice:":5" %}
{{ post }}
{% endfor %}
See the slice filter on more information.
However, since this is a common operation, with django 1.3 you can use class based views to automate most of this.