I am using Python Tools for Visual Studio (Python 3 and django 1.6) and trying to access data from a table like this:
from django.http import HttpResponse
from django.template import Template, Context
from ticket.models import Task
def ticket_listing(request):
objects = Task.objects.all().order_by('-due_date')
template = Template('{%for elem in objects %} {{elem}}<br/> {% endfor %}')
context = Context({'objects', objects})
return HttpResponse(template.render(context))
The problem is that after Task, objects does not appear in the suggestions and it seems that it is not available. Why? If I run this code I get an empty template... There is entries in the database (3 rows) I have checked.
There is a typo in code but it doesn't make a TypeError... You have created a set instead of a dictionary to pass to the template. Django doesn't complain since it is an iterable and there is no type checking.
>>> {1, 2}
set([1, 2])
>>> {1: 2}
{1: 2}
You just have to replace the wrong line by:
context = Context({'objects': objects})
Related
This question is similar to Testing a custom Django template filter, but unlike in that example, the filter is actually defined in a module in the templatetags directory as described in https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/#writing-custom-template-filters. Here is the filter code in templatetags/label_with_classes.py:
from django import template
register = template.Library()
#register.filter(is_safe=True)
def label_with_classes(bound_field):
classes = f"{'active' if bound_field.value() else ''} {'invalid' if bound_field.errors else ''}"
return bound_field.label_tag(attrs={'class': classes})
Here is my first stab at a test for it:
from ..templatetags.label_with_classes import label_with_classes
from django.test import SimpleTestCase
from django.template import Context, Template
from ..forms.sessions import SessionForm
class CustomFilterTest(SimpleTestCase):
def test_1(self):
form = SessionForm(data={})
self.assertFalse(form.is_valid())
self.assertEqual(
form.errors,
{'session_number': ['This field is required.'],
'family': ['This field is required.'],
'session_type': ['This field is required.']})
template = Template('{{ form.session_number|label_with_classes }}')
context = Context({'form': form})
output = template.render(context)
The problem is that I get an error that the filter was not found:
django.template.exceptions.TemplateSyntaxError: Invalid filter: 'label_with_classes'
This is because the test case doesn't mimic the behavior of registering the filter and loading it in the template. It seems like in the Django source code, for example https://github.com/django/django/blob/master/tests/template_tests/filter_tests/test_join.py, there is an elaborate setup decorator which provides the test class with a self.engine whose render_to_string method has the required filters already installed.
Do I basically have to copy the Django source code to write an integration-style test for my custom filter? Or is there a simpler way (besides just testing it as a function)?
I suspect you need to load your template module:
...
template = Template("""{% load label_with_classes %}
{{ form.session_number|label_with_classes }}""")
...
See the relevant documentation.
Using Django 1.5.1
I have a simple code here -
count_by_media_type = defaultdict(int)
for user_media in user_media_data:
count_by_media_type[user_media['media_type']] += 1
This is a part of a view, which is rendered in the template using a for iteration loop -
{% for media_type in count_by_media_type %}
..........
{% endfor %}
The value of count_by_media_type just before the render_to_response method in the loop is
defaultdict(<type 'int'>, {u'photo': 1, u'audio': 4, u'video': 3})
But somehow, on rendering , there is a runtime error in iteration
dictionary changed size during iteration
And the value of count_by_media_type as seen in the template context becomes
defaultdict(<type 'int'>, {u'photo': 1, u'audio': 4, u'video': 3, u'media_type': 0})
This is quite weird, as how does a new key media_type is coming in the variable?
Now , when I switch the above code, mentioned in the view with
count_by_media_type = Counter([user_media['media_type'] for user_media in user_media_data])
Everything works fine.
Any clues anyone?
Using defaultdicts causes weird behaviour in Django templates, because of the way template variable lookups work. See the Behind the scenes box of the Django docs.
The Django docs suggest converting the defaultdict to a regular dict before passing to the template.
count_by_media_type = defaultdict(int)
for user_media in user_media_data:
count_by_media_type[user_media['media_type']] += 1
count_by_media_type = dict(count_by_media_type)
Or, as this answer suggests, you can disable the defaulting feature after you have finished inserting values.
count_by_media_type.default_factory = None
is there a way to get the template name ( being parsed ) in a template tag ?
I have read searched and found nothing, only this previous post
Getting the template name in django template
which doesn't help me much, since the answer relies on settings.DEBUG being true, which in my case can't be.
I don't really know where to start on this one, so any suggestion is welcome :)
EDIT
So basically what i want is to create a plugable tag that when rendered it checks for a Tag object, this would be the source for the tag object
class Tag(models.Model):
template = models.CharFIeld(max_length=50)
name = models.CharField(max_length=100)
plugins = models.ForeignKey(PluginBase)
if theres a tag object, then it displays all plugin objects, if not it creates a tag object unique to the name provided in the template tag and the template name, if getting the template name is not possible, then i guess i can just make it unique per name. The whole tag is kinda like a placeholder, for those familiar with django-cms
You could perhaps do this with a context processor, but I'm not sure if these have access to the name of the template.
What will work is to make a wrapper for the rendering calls you do. Say you currently do the following:
from django.shortcuts import render
def index(request):
return render(request, 'app/index.html', { 'foo': 'bar', })
If you create your own wrapper for this, you could add the template name to the dictionary before the actual render takes place:
from django.shortcuts import render
def myrender(request, template, dictionary):
dictionary.update({'template_name': template})
return render(request, template, dictionary)
Then in your views, change it as follows (assuming you saved the above function in myutils.py, and it is available on your path):
#from django.shortcuts import render <- delete this line
from myutils import myrender as render
def index(request):
return render(request, 'app/index.html', { 'foo': 'bar', })
Now all your render calls will update the dictionary with the template name. In any template, then just use {{ template_name }} to get the name. You can of course also update other rendering function like render_to_response and such in a similar fashion.
Also, the import myrender as render might or might not confuse you later on because it is named like the Django function... if so, just import it without the "as render", and replace all render calls with myrender. Personally I'd prefer this since this makes it a drop-in replacement for the existing rendering functions.
Looking at the source, while the Template object would have access to the template name (via .name) this value is never passed on to the Parser object and therefore not available to template tags.
There are various ways of making the template name available to the template itself (by adding it to the context) but not within the template tags.
As Daniel Roseman mentioned in the comments, if you can elaborate on what you're actually trying to achieve, there may be a better way to achieve what you want. No offence, but this sounds like it may be an XY problem.
Out of academic interest, I had a quick fiddle to see if it was possible. As far as I can see, it is possible but not without changing or monkey patching the django source.
Note: the following is not a recommended solution and merely hints at what may be required to actually make this work. Not to be used for production code.
By modifying django.template.base.py with the following changes, we add the .template_name attribute to the parser object making it available to template tags.
Added optional arg to compile_string
Added template name as extra attribute to parser
Passed in the template name when calling compile_string()
To test this out, I defined the following tag which simply returns the template name in caps:
from django.template.base import Node, Library
register = Library()
class TemplateNameNode(Node):
def __init__(self, template_name):
self.name = template_name
def render(self, context):
return self.name.upper()
#register.tag
def caps_template_name(parser, token):
return TemplateNameNode(parser.template_name)
and the following template:
{% load mytags %}
Template name in caps: {% caps_template_name %}
This seems to work when tested in ./manage.py shell:
>>> from django.template import loader, Context
>>> t = loader.get_template("test.html")
>>> t.render(Context({}))
u'\nTemplate name in caps: TEST.HTML\n'
While this seems to work, I should reiterate that manually patching the django source never a good solution and is subject to all sorts of misery when migrating to different versions.
I have a custom template filter I created under project/app/templatetags.
I want to add some regression tests for some bugs I just found. How would I go about doing so?
The easiest way to test a template filter is to test it as a regular function.
#register.filter decorator doesn't harm the underlying function, you can import the filter and use just it like if it is not decorated. This approach is useful for testing filter logic.
If you want to write more integration-style test then you should create a django Template instance and check if the output is correct (as shown in Gabriel's answer).
Here's how I do it (extracted from my django-multiforloop):
from django.test import TestCase
from django.template import Context, Template
class TagTests(TestCase):
def tag_test(self, template, context, output):
t = Template('{% load multifor %}'+template)
c = Context(context)
self.assertEqual(t.render(c), output)
def test_for_tag_multi(self):
template = "{% for x in x_list; y in y_list %}{{ x }}:{{ y }}/{% endfor %}"
context = {"x_list": ('one', 1, 'carrot'), "y_list": ('two', 2, 'orange')}
output = u"one:two/1:2/carrot:orange/"
self.tag_test(template, context, output)
This is fairly similar to how tests are laid out in Django's own test suite, but without relying on django's somewhat complicated testing machinery.
I have a pretty simple question. I want to make some date-based generic views on a Django site, but I also want to paginate them. According to the documentation the object_list view has page and paginate_by arguments, but the archive_month view does not. What's the "right" way to do it?
I created a template tag to do template-based pagination on collections passed to the templates that aren't already paginated. Copy the following code to an app/templatetags/pagify.py file.
from django.template import Library, Node, Variable
from django.core.paginator import Paginator
import settings
register = Library()
class PagifyNode(Node):
def __init__(self, items, page_size, varname):
self.items = Variable(items)
self.page_size = int(page_size)
self.varname = varname
def render(self, context):
pages = Paginator(self.items.resolve(context), self.page_size)
request = context['request']
page_num = int(request.GET.get('page', 1))
context[self.varname] = pages.page(page_num)
return ''
#register.tag
def pagify(parser, token):
"""
Usage:
{% pagify items by page_size as varname %}
"""
bits = token.contents.split()
if len(bits) != 6:
raise TemplateSyntaxError, 'pagify tag takes exactly 5 arguments'
if bits[2] != 'by':
raise TemplateSyntaxError, 'second argument to pagify tag must be "by"'
if bits[4] != 'as':
raise TemplateSyntaxError, 'fourth argument to pagify tag must be "as"'
return PagifyNode(bits[1], bits[3], bits[5])
To use it in the templates (assume we've passed in an un-paginated list called items):
{% load pagify %}
{% pagify items by 20 as page %}
{% for item in page %}
{{ item }}
{% endfor %}
The page_size argument (the 20) can be a variable as well. The tag automatically detects page=5 variables in the querystring. And if you ever need to get at the paginator that belong to the page (for a page count, for example), you can simply call:
{{ page.paginator.num_pages }}
Date based generic views don't have pagination. It seems you can't add pagination via wrapping them as well since they return rendered result.
I would simply write my own view in this case. You can check out generic views' code as well, but most of it will probably be unneeded in your case.
Since your question is a valid one, and looking at the code; I wonder why they didn't decouple queryset generation as separate functions. You could just use them and render as you wish then.
I was working on a problem similar to this yesterday, and I found the best solution for me personally was to use the object_list generic view for all date-based pages, but pass a filtered queryset, as follows:
import datetime, time
def post_archive_month(request, year, month, page=0, template_name='post_archive_month.html', **kwargs):
# Convert date to numeric format
date = datetime.date(*time.strptime('%s-%s' % (year, month), '%Y-%b')[:3])
return list_detail.object_list(
request,
queryset = Post.objects.filter(publish__year=date.year, publish__date.month).order_by('-publish',),
paginate_by = 5,
page = page,
template_name = template_name,
**kwargs)
Where the urls.py reads something like:
url(r'^blog/(?P<year>\d{4})/(?P<month>\w{3})/$',
view=path.to.generic_view,
name='archive_month'),
I found this the easiest way around the problem without resorting to hacking the other generic views or writing a custom view.
There is also excellent django-pagination add-on, which is completely independent of underlying view.
Django date-based generic views do not support pagination. There is an open ticket from 2006 on this. If you want, you can try out the code patches supplied to implement this feature. I am not sure why the patches have not been applied to the codebase yet.