Wagtail blocks: accessing context and request in overridden get_context - python

I trying to get the page and the request from context to be able to use pagination inside the block. The only context i get is
context {'self': None, 'value': None}
Is it even possible to have pagination inside a streamfield block?
class CustomStaticBlock(blocks.StaticBlock):
def get_context(self, value):
context = super(CustomStaticBlock, self).get_context(value)
Rendering with
{% include_block block%}

It's now working (within get_context). If someone has the same issue with a StreamField, make sure to render it as:
{% for block in page.body %}
{% include_block block %}
{% endfor %}
The following won't work (empty parent_context):
{% include_block page.body %}
{{ page.body|safe }}

The context from the outer page is available within the block template, but unfortunately not within the get_context method. (This is due to the way the template context is built up - the result of get_context is merged into the parent context.) This is a known limitation:
https://github.com/wagtail/wagtail/pull/2786#issuecomment-230416360
https://github.com/wagtail/wagtail/issues/2824
A possible workaround would be to override the render method (which is admittedly not ideal, because you'd have to repeat some or all of the existing render logic there).

Related

Variable context between two blocks in Django templates?

I have two blocks that call the same method with same variables. I want to call the method only once, but the result is then outsite the scope of the block tags. I've tried calling this method in the parent template header.html and with a with tag, but nothing seems to work.
This is the layout:
{% extends "header.html" %}
{% load navigation_tags %}
{% block header %}
{% get_section site=site as section %}
{% include "foobar.html" with section=section %}
{% endblock header %}
{% block navigation %}
<nav>
<div class="container">
{% get_section site=site as section %}
{% navigation section.slug %}
</div>
</nav>
{% endblock navigation %}
navigation_tags.py
#register.assignment_tag
def get_parent_section(site):
if site.id == settings.FOOBAR_SITE_ID:
section = Section.objects.get(id=settings.FOOBAR_SECTION_ID)
else:
# This is also a section instance.
return site.default_section
As mentioned by 2pacho in another answer and Fernando Cezar in a comment, the easiest way to share values between different sections is to set it in the template context. If you are using the render shortcut function, you can pass a dict as the context parameter to add a value to the rendering context of the template. That would be a good place to add it and this would be the easiest place to put it.
return render(request, 'template.html', {'section': get_parent_section(site)})
However, if for some reason, you can't include it in the context, you can use a decorator to add memoization to your function, so that it will cache the computation results and return it immediately when called with the same parameters. You can use functools.lru_cache to do so, or it's Django backport at django.utils.lru_cache.lru_cache if you are using Python 2.x.
#register.assignment_tag
#functools.lru_cache()
def get_parent_section(site):
if site.id == settings.FOOBAR_SITE_ID:
section = Section.objects.get(id=settings.FOOBAR_SECTION_ID)
else:
# This is also a section instance.
return site.default_section
I wouldn't call a method outside .py . Think that this is using Jinja2 templates,
it's powerful but not in the way that the backend can be.
What I recommend you doing in this case is to generate a context for the template and use this variables there.
Would be as simple as adding it to your context where it's being generated.
context['site_parent'] = get_parent_section(site)
Think that Jinja2 (html) has to be as simple as possible and that can help you with basic coding and time saving (like loops to print the exact same information or show and hide code depending on the context) but I would keep it as simple you can when rendering.
If you would like you can read official django website about templates https://docs.djangoproject.com/en/2.0/topics/templates/
But from my expirience I would keep the method calls in the views.py

Trouble Rendering Template Variables in Django

I am hitting a brick wall when it comes to solving this problem. I have a template that is being included in an other template, but I am unable to render any template variables in the included template. I have a separate template tag file for the included template. I am at a total loss right now how to resolve this problem.
I would like to be able to render the field values from my model in the template (which consist of an image, a short description, etc.) I am fairly certain that I am going about this in the wrong way.
Below is my code:
The model:
class AddOnItem(models.Model):
base_product = models.ForeignKey(Product)
base_price = models.DecimalField(max_digits=8, decimal_places=2, default=0.0)
addon_image = models.ImageField(upload_to='uploads/shop/product/images/',
default='uploads/shop/product/images/', help_text='Max width: 450px')
short_description = models.CharField(max_length=255, blank=True)
def __unicode__(self):
return self.base_product.name
The template:
{% load addon_tags %}
{% if items_to_add %}
{% for items in items_to_add %}
<div id="addon-container">
<div id="left-addon" class="addon-container">
<img src="#" class="addon-image">
<p class="addon-description">{{items.short_description}}</p>
</div>
</div>
{% endfor %}
{% endif %}
addon_tags.py:
from django import template
from sho.models import AddOnItem
register = template.Library()
#register.inclusion_tag("foo/templates/v/sho/addon.html", takes_context=True)
def add_on_render():
context['items_to_add'] = AddOnItem()
return context
I imagine I am doing either a lot wrong (my assumption at the moment) or I am missing some minor bit. I have been working on this for several days and have gone over the docs repeatedly. I am simply completely missing something and this has become a major blocker for me. Any help would be appreciated.
Django version 1.4
Edit:
I ended up rewriting the view and did away with the templatetag. Thanks to both Daniel Roseman and Odif Yltsaeb for their replies.
1) From your post you are adding empty, new item into the context in add_on_render templateag.
2) I cant see in your post, WHERE you are using {% add_on_render %} templatetag. You have created templattag, but do not seem to be using it anywhere.
It is bit hard to understand what exactly are you trying to do or why you even need templatetag there.
If you want to display models field value, you do not need templateag for this and all the stuff that you show in your "template" part of this post, could be very well in your main template, which i assume, is not shown in your post.
If you want to use templatetag, then this templatetag should probably recieve AddOnItem istance as parameter like this:
#register.inclusion_tag("foo/templates/v/sho/addon.html", takes_context=True)
def add_on_render(item):
context['items_to_add'] = item
return context
And You could use it in some template like this:
{% load addon_tags %}
{% if items_to_add %}
{% for items in items_to_add %}
<div id="addon-container">
<div id="left-addon" class="addon-container">
<img src="#" class="addon-image">
<p class="addon-description">{% add_on_render items %}</p>
</div>
</div>
{% endfor %}
{% endif %}
and your foo/templates/v/sho/addon.html
would like like this:
{{ items_to_add.short_description }}
But doing it this way seems very unnecessary as you could achieve all that without templatag by using the template code that you already have outside your "main" template.
You haven't posted the template that you are attempting to include the tag in. I suspect you're not calling it at all, because there are a couple of errors that would cause exceptions if you did try and use the tag. You need to do {% add_on_render %} somewhere in your main template.
As I say though there are a couple of errors. Firstly, you don't define context (as an empty dict) before adding the items_to_add key. You can shortcut this by just doing it in one go.
Secondly you've made items_to_add a single, blank, AddOnItem. So in your included template, iterating through items_to_add does nothing at all. Not sure what you are trying to do there. Perhaps you want to pass all AddOnItem instances?
context = {'items_to_add': AddOnItem.objects.all()}
Or maybe you want to filter them by some criteria, in which case you probably want to pass those criteria to the inclusion tag itself:
def add_on_render(product):
context = {'items_to_add': AddOnItem.objects.filter(base_product=product)}
and you would call it from the main template like this:
{% add_on_render my_product %}
if you set "takes_context=True" you should take context as the first argument in decorated function:
#register.inclusion_tag("foo/templates/v/sho/addon.html", takes_context=True)
def add_on_render(context):
context['items_to_add'] = AddOnItem()
....

context KeyError in django templateTag

My app's templatetag code is throwing a KeyError for a missing key (page) in the context variable. In my template, I do not refer to context variables with context.variableKeyName, I just refer to variableKeyName (e.g. {% if is_paginated %}). And in my template, I can refer to the key page without any exceptions.
How should I get the context with the keys it needs into my templatetag?
Here are the details:
I am using django-profiles to return a list of some profiles:
url(r'^profiles/$', 'profiles.views.profile_list',
kwargs={ 'paginate_by':10 },
name='profiles_profile_detail'),
which calls this bit of code here:
https://bitbucket.org/ubernostrum/django-profiles..
In my template, I test {% if is_paginated %} before I call a templatetag:
{% if is_paginated %}{% load paginator %}{% paginator 3 %}{% endif %}
( I am using a templatetag inspired from http://www.tummy.com/.../django-pagination/ updated for django 1.3 http://djangosnippets.org/snippets/2680/ )
But this leads to the KeyError for 'paged'.
The documentation (comments in the class) of http://djangosnippets.org/snippets/2680/ says:
Required context variables: paged: The Paginator.page() instance.
It is also used in the template tag:
paged = context['paged']
You need to provide this context variable for this template tag to work. I think your best bet is to copy the code of profiles.views.profile_list view and add this context variable. It's unfortunately still a function-based view - otherwise extending it would have been a lot cleanier and easier.
the right way to code is:
{% paginator v 3 %}
v - variable that contains the db items

I have problems with setting up django-pagination

I'm making a template for Django site (it's quote database). I wanna have Digg-like pagination. Altough, author of the application has made his own pagination, unfortunately without page numering (just "previous" and "next" links). So I've installed django-pagination, but I can't use it with the site. I'm completly new in Django, even programming - I'm just a simple webdesigner... OK, here we go.
There is the original script: https://bitbucket.org/fleg/fqdb/
The first thing is a problem with template context processors. My settings.py didn't have this section, so I added it exactly like in django-pagination documentation. When I run the site, I get an error: "Put 'django.contrib.auth.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application". So how I have to order that?
A second problem is template. I use it exactly like on the screencast:
{% extends "fqdb/base.html" %}
{% load pagination_tags %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% if quotes %}
{% autopaginate quotes %}
{% for quote in quotes %}
{% include 'fqdb/quote_body.html' %}
{% endfor %}
{% paginate %}
{% else %}
<p>Brak cytatów.</p>
{% endif %}
{% endblock %}
But I get "Template error: Caught KeyError while rendering: request". But... Seriously, I don't know what's wrong with this code!
There is the paginated view - quote list. It work without pagination, so I don't think if it's a problem, but maybe.
def list_paged(request, page, order_by_what, title, reverse_name):
hash = get_ip_hash(request)
lista = Quote.objects.filter(accepted = True).order_by(order_by_what)[:]
returnDict = {'quotes': lista, 'title': title, 'hash': hash, 'sidebar': get_sidebar()}
return render_to_response('fqdb/quote_list.html', {'quotes': get_quotes(quotes)}, context_instance=RequestContext(request))
I have modified it to not paginating, because it's django-pagination task. You can find original view on Bitbucket.
Maybe do you know some better pagination solutions?
It looks like you need to add django.contrib.auth.context_processors.auth and django.core.context_processors.request context processors to your TEMPLATE_CONTEXT_PROCESSORS setting.
Before you defined TEMPLATE_CONTEXT_PROCESSORS, django would have used the default. It looks as if some of your code requires the auth processor, hence your first error message.
The KeyError looks to me as if you require the request processor.
Try the following in your settings file:
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
#"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
#"django.core.context_processors.static",
#"django.contrib.messages.context_processors.messages")
"django.core.context_processors.request"
)
I've used the default list given in the Django 1.3 request context docs, added the request processor, and commented out the ones that you don't seem to need.
The order of template context processors does not usually matter, as long as they do not define overlapping variable names.
If the objects are passed from a templatetag
def comment_app(context):
objects = Comments.objects.get_tree_for_object(context['content_object'])
return {
'comments_tree': objects,
'request': context['request']
}
register.inclusion_tag('comments/comment_app.html', takes_context=True)(comment_app)
note the: 'request': context['request']
{% autopaginate quotes N%}
N - how many items you need for each page

Django - how to tell if a template fragment is already cached?

I am using Django's Template Fragment Caching so in a template.html file
{% extends 'base.html' %}
{% load cache %}
{% block content %}
{% cache 500 "myCacheKey" %}
My html here...
{% endcache %}
{% endblock %}
This is working fine - I can see it's getting cached and hit but the view is doing something expensive to provide data to this view and thats getting called every time.
In views.py
def index(request)
data = api.getSomeExpensiveData()
return render_to_response('template.html', {'data':data} )
So how do I tell if the cache is avail before the call to api.getSomeExpensiveData()?
I can't use cache.get('myCacheKey') as the cache isn't found - does it use some naming scheme and if so can I either use something like
cache.get(cache.getTemplateFragmentKey("myCacheKey"))
or
cache.getTemplateFragment("myCacheKey")
If you do not use that data in your view, something as simple as this might work:
def index(request)
get_data = api.getSomeExpensiveData
return render_to_response('template.html', {'get_data':get_data} )
In template
{% block content %}
{% cache 500 "myCacheKey" %}
{{ get_data.something }}
Or maybe
{% for something in get_data %}
{% endfor %}
{% endcache %}
{% endblock %}
Django template automatically calls all callable objects.
EDIT:
If you need to use get_data more than once in your template you'll need some wrapper. Something similar to this:
def index(request)
class get_data(object):
data = False
def __call__(self):
if not self.data:
self.data = api.getSomeExpensiveData()
return self.data
return render_to_response('template.html', {'get_data':get_data()} )
I found this SO - How do I access template cache?
And adapted it to
from django.utils.hashcompat import md5_constructor
from django.utils.http import urlquote
from django.core.cache import cache
def hasFragmentCache(key, variables = []):
hash = md5_constructor(u':'.join([urlquote(var) for var in variables]))
return cache.has_key(cache_key)
Edit - I've accepted skirmantas answer as whilst this does exactly as asked its the better approach as then the template and view are more loosly coupled. Using this method you need to know the name of each cache fragment and whats used where. A designer moves things around and it would fall over.

Categories