Django wagtail accesing parent page variables from block templates - django

I have created a stream block with a custom template in my models.py file for my 'work' page as:
section = StreamBlock( [ ('section', SectionStreamBlock( template = 'personal_web/blocks/work_block.html') ])
I am listing my 'work page' objects in a 'work-index page'. As work-index page > work page > block
Now I am trying to access the 'work page object' itself in my block template. (I am using jinja2 )
I know that I can not pass it via {% include_block block %}.
Is there a way to pass it?

The {% include_block %} tag works in the similar way to the {% include %}tag from Jinja2 in terms of passing context to a child template. So if you render the section streamfield in your page template like this:
{% include_block page.section %}
In your personal_web/blocks/work_block.html you should be able to access your "work" page object using the page variable (for example, page.title to access page's title).

Related

Additional field shows up outside table in admin change_list view

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.

How do I use AJAX for pagination in Django-Tables2?

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

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.

breadcrumbs using django admin templates

I have created a site mainly using django's admin interface, plus a few custom views. As the majority of the site is using the admin (and I am not to hot with css), I have just used django's admin tempates for my custom views (they are extended generic views).
Anyway, most of my custom views look good, and match the look and feel of the admin interface, but I don't know how to get the breadcrumbs working.
So form an extended generic view, how and what do I pass to the tempate's
{% block breadcrumb %}
tag?
I saw one article that mentioned the context object, but didn't have any further details.
If you want to provide breadcrumbs in your template and get breadcrumbs from parent template you can use block breadcrumbs & block.super variable in it:
{% block breadcrumbs %}{{ block.super }} › My custom site{% endblock %}
Or just pass to the template variable title.

Passing context between templatetags, django

I am using django to create a web-application.
I have created a template in where I load a templatetag. In this templatetag I load another templatetag. From the template I pass context to the first templatetag, but the context is not available from the second templatetag (inside the first templatetag) - see below.
I hope this makes sense, and that one of you have the answer.
Template snippit:
{% load templatetags %}
{% some_tag argument %}
some_tag Templatetag:
{% load templatetags %}
{% some_other_tag another_argument %}
some_other_tag Templatetag:
In this templatetag I am trying to access context to get user info i.e. using
request = context['request']
request.user
Don't forget that the context to the subtemplate - and hence to the second template tag - is whatever is returned from the first template tag function. So you'll need to ensure that the request object is included in the dictionary you return there.