Say we have a Django page that shows a list of items and allows the user to fill in a form to add to the items (let's call the items posts).
What I want:
The URL for this page refers to a view. The view calls two other views (called "sub-view" hereon), then each sub-view renders its section and returns the results. The main view then joins the results of the sub-views and returns that.
Ideally, I would have a quick javascript check on the page - if javascript is enabled, the submit button for the form will be "Ajax'd" to the sub-view that deals with form adding, and the page will be updated that way. I suppose I could trigger a request to refresh the list of posts afterwards too or something.
So how do I concatenate the two sub-views in the main view? Is this possible?
UPDATE: "sub-view" is a term I made up. What I want is a view that can be called either by Ajax directly to return something meaningful, or from another view (which I'll call the "main view"). If called by this "main view", how does the main view handle returning the data from multiple "sub-views"?
Is there a simple way to do this? Is this an appropriate way to think about multiple views in a page? Should I care about separation of responsibilities?
A view in django is just any callable that ultimately returns a Response object. Within that view, you could split the work up into whatever organization suits you. Maybe your view 100% delegates out to other methods.
In your case, your main view would call 2 other functions for data. These could also be views if they also accept a Request object as well and make use of it. They also would need to return Response objects to be considered django views since that is how you would point URLs at them. But it doesn't really do you any good to have two other views returning you Response objects. What you probably want is just other methods that do specific tasks and return some data structure, or maybe even a rendered snippet of a template. You would then use these data, or merge the template strings together and return that in your main Response.
If you are really set on making use of other views that return Response objects, then you can do something like grabbing the body out of them, and merging them into your own response:
https://docs.djangoproject.com/en/1.4/ref/request-response/
Really, nothing is much different from the tutorials. You are just calling other methods for data. If you want to make it organized you should separate the data processing logic from the view function. Your main view would call these data processing functions for values. And your "sub views" would just be simple views that also call these individual data functions and wrap them into a Response.
Pseudo:
def mainView(request):
val = data1()
val2 = data2()
response = # val + va2 + other stuff
return response
def subView1(request):
val = data1()
response = # val + stuff
return response
def subView2(request):
val2 = data2()
response = # val2 + stuff
return response
def data1():
val = # get data
return val
def data2():
val2 = # get data
return val2
The views should contain only view-related logic:
Views are for processing requests and serving the requested data
That includes checking for user authorization/permission and dealing with given parameters
If the requested data is not trivial to fetch, outsource the code to a more appropriate location (your model or form definitions or another custom place)
Outsource calculations to make them reusable and call these methods from your views to keep them small.
Nevertheless, maybe you want something else, namely templates with extends and include.
With extends you are able to create a base layout for your HTML code and define specific blocks which can be rendered elsewhere. Example? Ok.
base.html:
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{% block title %}My Site{% endblock %}</title>
</head>
<body>
<div id="header">
<h1>My Site</h1>
</div>
{% block content %}{% endblock %}
</body>
</html>
Then, in any other template, you can overwrite the blocks title and content which we defined in the base template:
{% extends "base.html" %}
{% block title %}My Page{% endblock %}
{% block content %}
<h2>My Page</h2>
<div>lorem ipsum</div>
{% endblock %}
Also, you can create sub-templates like the following one, let's name it _item.html:
<li class="item">
<span>{{ something.foo }}</span>
<strong>{{ something.bar }}</span>
</li>
You can include that snippet in any other template and pass an arbitrary number of parameters:
{% for something in mymodel.mym2mrelation.all %}
{% include "_item.html" with something=something only %}
{% endfor %}
Naturally, you can combine both concepts. Like so:
{% extends "base.html" %}
{% block title %}My Page{% endblock %}
{% block content %}
<h2>My Page</h2>
<div>lorem ipsum</div>
<ul>
{% for something in mymodel.mym2mrelation.all %}
{% include "_item.html" with something=something only %}
{% endfor %}
</ul>
{% endblock %}
I hope that helps.
This is the perfect time to introduce class based views.
class methods are essentially "sub views" that split logic into reusable snippets.
If you want the readability of split functions - it only gets better by using django's class based views (via all of the functionality provided by default and access to request, kwargs, args, etc., via the class instance).
The docs even contain a good example for returning a JSON response or HTML response based on request parameters (this exact situation).
The best part? You can reuse your class based views as mixins in future views. Look at the docs example to see how to convert any class based view to handle a JSON response of the template context via a simple subclass.
Related
I am writing a custom form field which has a custom widget. The widget needs some static assets. Its template looks like this:
{% load sekizai_tags static %}
<div id="myWidget"> ... </div>
{% addtoblock "css" %}
<link rel="stylesheet" href="{% static 'my_app/css/widget.css' %}">{% endaddtoblock %}
{% addtoblock "js" %}
<script src="{% static 'my_app/js/widget.js' %}"></script>{% endaddtoblock %}
However, this doesn't work. I get: You must enable the 'sekizai.context_processors.sekizai' template context processor or use 'sekizai.context.SekizaiContext' to render your templates.
I guess this is because the form (and widget) has no access to the request object?! Is there a way around this?
I found in the sekizai source a mention of SekizaiContext as...
An alternative context to be used instead of RequestContext in places where
no request is available.
... but I can't figure out how exactly I would do that.
Note that I do not want to start passing the request around. This would not be an acceptable solution, as my custom field is meant to be reusable; it should just work in any form without further modifications.
Update
Actually, after having posted an answer to my own question and... a bit of more thinking, I realize that answer - using Media class - does NOT really address the issue. After all, shortcomings of Media class is why sekizai is needed in the first place.
Using Media class would mean:
# widgets.py
class MyWidget(widgets.Widget):
template_name = "my_widget.html"
# other stuff
class Media:
css = {"all": "my_app/css/widget.css"}
js = ("my_app/js/widget.js")
And then, adding {{ form.media }} to the template containing the form. Thus, the original intention of posting this question would not have been addressed. i.e. using addtoblock in the widget template so as the field can be used as is, without further modifications to other templates.
In addition, if {{ form.media }} is used the html tags would render immediately after the form, unless if I'd wrap it like so: {% addtoblock "css" %}{{ form.media }}{% endaddtoblock %}. And then both css and js would both be added in the same place (whereas typically one would want to add the css link tag in the head and the js script tag after body).
If anyone has a solution for using sekizai's addtoblock in a template that has no access to the request object, still welcome!
Update 2
I am able to use addtoblock and avoid the context error overriding the get_context method in my widgets.Widget subclass like so:
# widget.py
class MyWidget(widgets.Widget):
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
sezikai_ctx_var = get_varname()
sekizai_ctx = SekizaiContext().flatten()
context.update({sezikai_ctx_var: sekizai_ctx[sezikai_ctx_var]})
return context
However, the assets are still not rendered...
After a bit of thinking, and reading the docs, I realize that static assets for forms and widgets is done by django out-of-the-box:
https://docs.djangoproject.com/en/3.1/topics/forms/media/
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.
I have a model called Project in an app called projects that I registered with the admin site so the instances can be added/edited/etc. This works as expected. Now I want to add a button for each project in the change list view on the admin site, that links to a custom form that requires a Project instance to do things. I followed a bunch of different tutorials to customize the admin site and managed to add another field to the table of the change list view. However the entries show up outside the table (see image).
I added the custom field by overwriting the admin/change_list.html template and calling a custom template tag custom_result_list within it. This tag adds a table field to the change list and then calls the admin/change_list_results.html template to render it. I have confirmed with a debugger that the item is added to the entries of the change list before the template is rendered (see image).
I cannot explain why the table is not rendered correctly even though the additional field has the same structure as the auto-generated ones. I have to admit I have resorted to Cargo Cult Programming, because I do not understand how this is supposed to work, despite spending too many hours trying to solve this simple problem.
Here's the relevant code.
In file /projects/templatetags/custom_admin_tags.py:
from django import template
from django.contrib.admin.templatetags.admin_list import result_list as admin_result_list
def custom_result_list(chl):
extended_cl = {}
extended_cl.update(admin_result_list(chl))
extended_cl["result_headers"].append({
'class_attrib': r' class="column-__str__"',
'sortable': False,
'text': 'Configure Project'
})
idx = 0
snippet = '<td class="action-button">{}</td>'
for project in chl.result_list:
extended_cl["results"][idx].append(snippet.format(project.id, project.unmod_name))
idx += 1
return extended_cl
register = template.Library()
register.inclusion_tag('admin/change_list_results.html')(custom_result_list)
In file templates/admin/projects/project/change_list.html:
{% extends "admin/change_list.html" %}
{% load i18n admin_urls static admin_list %}
{% load custom_admin_tags %}
{% block result_list %}
{% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
{% custom_result_list cl %}
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
{% endblock %}
To fix your issue:
from django.utils.html import format_html
replace your snippet.format(...) with format_html(snippet,...)
Explanation:
in django, all strings you pass from python are automatically HTML escaped. which here means, all your tags will not be considered as HTML. Such limitation is added to avoid any potential exploits by hackers. In your case, use of a template to render html is highly recommended. However, you can also send raw html from python using format_html helper function.
I am using django-tables2 and I am trying to display the data of the next page without loading another page. I think AJAX could be used.
From what I have found, it seems that it might not be possible.
There has been some discussion about this Support AJAX sorting/pagination
Is there something that I should look at to figure it out ?
It is possible however it's not so easy since django-tables2 (and django in general) are geared more to the Server Side Rendered world. I will sketch a solution here and consider that interesting topic for my blog (https://spapas.github.io):
You'll need to override the django-tables2 template that'll be used. You can't use the default one since the pagination to it is done by normal links. You should override it to use Ajax calls - take a look at this question for more Is it possible to custom django-tables2 template for a specific page in this case?. What you have to do is disable the default link functionality of the page links and call them through ajax. Depending on how you're going to do it this may be possible to be done through a script in your view's template without the need to override the table template at all.
You'll need to modify your view to detect if it's called by ajax (i.e through these pagination buttons you've defined above) or not and return a different template each time. If it's called normally then you'll just render your classic template. If it is called through ajax then it will return only the portion of the html that only contains the table.
Now in your normal template you'll put the table inside a div named (f.e) #the_table - when the user clicks on a pagination link you'll do the ajax call and the response will contain only the table - you'll then replace the contents of the #the_table div with what you just received.
So you should have two templates for your view, your view.html which will be something like:
{% extends base.html %}
{% block content %}
blah blah
<div id='the_table'>
{% include 'partial_table.html' %}
</div>
{% endblock %}
{% block extra_script %}
<script>
// You can do something like (NOT TESTED):
// I don't remember if the pagination links belong to a class
// if not you may want to add that class yourself
$('pagination-link').click(function(e) {
// Don't follow the link
e.preventDefault();
// Do the ajax request
$.get($(this).attr("href"), function(data) {
// Replace the table with what you received
$('#the_table').html(data)
});
});
</script>
{% endblock %}
And your partial_table.html:
{% load django_tables2 %}
{% render_table table %}
Now in your view if for example you are using CBVs then you'll have to use the template_name = view.html as defined above and override get_template_names like this:
def get_template_names(self):
# Sometimes the is_ajax() is not working properly so if it doesn't
# just pass the ajax_partial query parameter to your ajax request
if self.request.is_ajax() or self.request.GET.get('ajax_partial'):
return 'partial_table.html'
return super().get_template_names()
More info can be found in this recipe at my Django CBV guide: https://spapas.github.io/2018/03/19/comprehensive-django-cbv-guide/#implement-a-partial-ajax-view
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.