How can I use {{variable}} in custom templatetag?

106 views
Skip to first unread message

Josh

unread,
Aug 8, 2011, 11:51:52 AM8/8/11
to django...@googlegroups.com
I'm trying to create custom templatetags now, while learning Django. When an entry is displayed I want to list other entries with the same categories below the entry. I also wrote a templatetag (following the Practical Django Projects from James Bennet)

I'm having problems with using a variable as input in a custom templatetag. The custom templatetag is:

{% get_related_entries weblog.entry 5 from {{object.categories}} as related_entries %}
     {% for entry in related_entries %}
          <p><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
     {% endfor %}

After trial and error the do_related_entries(parser, token) works. The RelatedEntryNode is giving me problems now. 

class RelatedEntryNode(template.Node):
    def __init__(self, model, number, categories, related_entries):
        self.model = model
        self.number = int(number)
        self.categories = categories
        self.related_entries = related_entries

    def render(self, context):
        for cat in self.categories:
                context[self.related_entries] = self.model._default_manager.filter(categories=cat)[:self.number]
       return ''

I'm stuck now at the following error and the debug-information says:

Caught ValueError while rendering: invalid literal for int() with base 10: '{'

../sandbox/lib/python2.6/site-packages/Django-1.3-py2.6.egg/django/db/models/fields/related.py in _pk_trace
        v = getattr(field, prep_func)(lookup_type, v, **kwargs) ...
../sandbox/lib/python2.6/site-packages/Django-1.3-py2.6.egg/django/db/models/fields/__init__.py in get_prep_lookup
            return self.get_prep_value(value) ...
../sandbox/lib/python2.6/site-packages/Django-1.3-py2.6.egg/django/db/models/fields/__init__.py in get_prep_value
        return int(value) ...


It gets to the filter and gives an UTF error. The Postgres database is UTF8 and I also explicitly added the # -*- coding: utf-8 -*- in the django-file to be sure. I'm using Python 2.6.5 and Django 1.3

I think I'm using the wrong approach with regard to the {{object.categories}} in the customtag. What am I doing wrong and how can I solve this? 


Josh

unread,
Aug 8, 2011, 2:53:04 PM8/8/11
to django...@googlegroups.com
I think the problem is in the render part. I've modified the __init__ of the Node to self.categories = template.Variable(categories)

Any advice is welcome. All I want  to do is a query on the Entries model that have corresponding categories and return it to the template.




Daniel Roseman

unread,
Aug 8, 2011, 2:58:56 PM8/8/11
to django...@googlegroups.com
On Monday, 8 August 2011 16:51:52 UTC+1, Josh wrote:
I'm trying to create custom templatetags now, while learning Django. When an entry is displayed I want to list other entries with the same categories below the entry. I also wrote a templatetag (following the Practical Django Projects from James Bennet)

I'm having problems with using a variable as input in a custom templatetag. The custom templatetag is:

{% get_related_entries weblog.entry 5 from {{object.categories}} as related_entries %}
     {% for entry in related_entries %}
          <p><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
     {% endfor %}

<snip> 
 I'm stuck now at the following error and the debug-information says:

Caught ValueError while rendering: invalid literal for int() with base 10: '{'

../sandbox/lib/python2.6/site-packages/Django-1.3-py2.6.egg/django/db/models/fields/related.py in _pk_trace
        v = getattr(field, prep_func)(lookup_type, v, **kwargs) ...
../sandbox/lib/python2.6/site-packages/Django-1.3-py2.6.egg/django/db/models/fields/__init__.py in get_prep_lookup
            return self.get_prep_value(value) ...
../sandbox/lib/python2.6/site-packages/Django-1.3-py2.6.egg/django/db/models/fields/__init__.py in get_prep_value
        return int(value) ...


It gets to the filter and gives an UTF error. The Postgres database is UTF8 and I also explicitly added the # -*- coding: utf-8 -*- in the django-file to be sure. I'm using Python 2.6.5 and Django 1.3

I think I'm using the wrong approach with regard to the {{object.categories}} in the customtag. What am I doing wrong and how can I solve this? 


The problem is your use of the variable tag delimiters inside another tag. There's no reason for this: the general semantics of Django templatetags is that inside them, the syntax is Python-like. For example, you do `{% for category in object_categories %}`, not `{% for category in {{ object.categories }} %}`. So you should just refer to `object.categories` directly:

    {% get_related_entries weblog.entry 5 from object.categories as related_entries %}
--
DR.
 

Josh

unread,
Aug 8, 2011, 3:14:14 PM8/8/11
to django...@googlegroups.com
>    {% get_related_entries weblog.entry 5 from object.categories as related_entries %}

I found that out already. Now I'm pretty sure the problem is in the rendering. As far as I can tell ( I don't know how to test template tags in a shell ) I get a list of the names of the categories back.

  • When I try to iterate the list, I get the  'Caught TypeError while rendering: 'Variable' object is not iterable' error.
  • When I don't iterate the variable I get the 'Caught TypeError while rendering: int() argument must be a string or a number, not 'Variable'
  • When I try to append queries (with Q or the normal way, because I need an OR relation), that works neither.
??? I don't know how to continue. I'm thinking now of solving this outside Django in a 'normal' python script (somehow) and pass the values as formatted html back to the template. That shouldn't be necessary I think. I'm sure I'm overlooking a very fundamental issue with templates and templatetags, but I just don't get it (yet). 

Ian Clelland

unread,
Aug 8, 2011, 4:44:05 PM8/8/11
to django...@googlegroups.com
On Mon, Aug 8, 2011 at 12:14 PM, Josh <jos.car...@yahoo.com> wrote:
>    {% get_related_entries weblog.entry 5 from object.categories as related_entries %}

I found that out already. Now I'm pretty sure the problem is in the rendering. As far as I can tell ( I don't know how to test template tags in a shell ) I get a list of the names of the categories back.

  • When I try to iterate the list, I get the  'Caught TypeError while rendering: 'Variable' object is not iterable' error.
  • When I don't iterate the variable I get the 'Caught TypeError while rendering: int() argument must be a string or a number, not 'Variable'
  • When I try to append queries (with Q or the normal way, because I need an OR relation), that works neither.
It sounds like you're not resolving the variable at all in your render method --

When you construct the template node, you work with Variable objects, which represent the context variable as it exists in the template source (eg, html).

At the point where you actually render your output, you want to resolve that Variable, so that the template framework actually determines what the contents of that variable should be, given the current context.

The example at https://docs.djangoproject.com/en/1.3/howto/custom-template-tags/#passing-template-variables-to-the-tag shows how it should be done. Construct a Variable object in node.__init__(), and use variable.resolve(context) in node.render()

Ian
 
??? I don't know how to continue. I'm thinking now of solving this outside Django in a 'normal' python script (somehow) and pass the values as formatted html back to the template. That shouldn't be necessary I think. I'm sure I'm overlooking a very fundamental issue with templates and templatetags, but I just don't get it (yet). 

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-users/-/yYKP0Xoz9XkJ.

To post to this group, send email to django...@googlegroups.com.
To unsubscribe from this group, send email to django-users...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.



--
Regards,
Ian Clelland
<clel...@gmail.com>

Josh

unread,
Aug 8, 2011, 5:20:24 PM8/8/11
to django...@googlegroups.com
>It sounds like you're not resolving the variable at all in your render method --
>
>When you construct the template node, you work with Variable objects, which represent the >context variable as it exists in the template source (eg, html).
>
>At the point where you actually render your output, you want to resolve that Variable, so >that the template framework actually determines what the contents of that variable should >be, given the current context.
>
>The example at https://docs.djangoproject.com/en/1.3/howto/custom-template-tags/#passing->template-variables-to-the-tag shows how it should be done. Construct a Variable object in >node.__init__(), and use variable.resolve(context) in node.render()
>
>Ian

I already tried that route from the sample and that creates the unicode error. Relevant parts of my code are below. 

models.py
categories = models.ManyToManyField('Category', blank=True, null=True, default = None)

{% get_related_entries weblog.entry 5 from object.categories as related_entries %}
     {% for entry in related_entries %}
          <p><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
     {% endfor %}


def __init__(self, model, number, categories, varname):
    self.categories = template.Variable(categories)


def render(self, context):
    related = self.varname.resolve(context)
    return related    

Caught AttributeError while rendering: 'unicode' object has no attribute 'resolve'

When I view the entry.categories from the manage shell, it returns a list of categories (I also tried with from object instead of object.categories in the templatetag, but that gave the same error)

In [7]: c.categories.all()
Out[7]: [<Category: C1>, <Category: C2>, <Category: C3>]

Somehow I have to iterate the list of categories (which consist of the name and not the Category.id and retrieve other entries with the same category in the Node render. I have been looking into include-tags too, but I also got iterating errors. I cannot imagine that there isn't an easy solution for these kind of queries. These kind of queries are pretty common to perform, but how ???

Ian Clelland

unread,
Aug 8, 2011, 6:23:08 PM8/8/11
to django...@googlegroups.com
On Mon, Aug 8, 2011 at 2:20 PM, Josh <jos.car...@yahoo.com> wrote:
>It sounds like you're not resolving the variable at all in your render method --
>
>When you construct the template node, you work with Variable objects, which represent the >context variable as it exists in the template source (eg, html).
>
>At the point where you actually render your output, you want to resolve that Variable, so >that the template framework actually determines what the contents of that variable should >be, given the current context.
>
>The example at https://docs.djangoproject.com/en/1.3/howto/custom-template-tags/#passing->template-variables-to-the-tag shows how it should be done. Construct a Variable object in >node.__init__(), and use variable.resolve(context) in node.render()
>
>Ian

I already tried that route from the sample and that creates the unicode error. Relevant parts of my code are below. 


These are my assumptions about the bits of code you've posted:
 
models.py
categories = models.ManyToManyField('Category', blank=True, null=True, default = None)

Assumption: categories is a field defined within a standard Django model; Category is another model you have defined.
 

{% get_related_entries weblog.entry 5 from object.categories as related_entries %}
     {% for entry in related_entries %}
          <p><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
     {% endfor %}


Assumption: weblog and object are context variables provided to the template from the view. object is an instance of the model with the categories m2m field you showed above.

I'm not sure how weblog.entry is relevant.

 

def __init__(self, model, number, categories, varname):
    self.categories = template.Variable(categories)

Assumption: model, number, categories, and varname are the parameters from the template tag you show in the template snippet above. I am guessing that they are all just the strings, exactly as they appear in the template.


def render(self, context):
    related = self.varname.resolve(context)
    return related    

At this point, I presume that you have defined self.varname in __init__ above, as "self.varname = varname", and you have just chosen not to paste that line.
 

Caught AttributeError while rendering: 'unicode' object has no attribute 'resolve'

First off, self.varname, if I am correct, is just the string "related_entries" here. It doesn't have a "resolve" method, because it's just a string. Variable objects, like self.context, have a "resolve" method. 

If you want to access the categories that you have mentioned in the template, then you can use
self.categories.resolve(context)

If you want to access "weblog.entry", then you will need to create a Variable from it, like you did with categories:

def __init__(...):
    ...
    self.model = template.Variable(model)
    ...

and then resolve it in render(), as self.model.resolve(context)

If you need to push back a value in a variable call related_entries, then you will want to set a value in the context dictionary:

context[related_entries] = <some value here>

You won't be able to resolve it as a Variable, because it (presumably) doesn't even exist before you call your custom tag, but once you set it on the context object, it will be available to the rest of the template.

Hope this helps,

Ian





When I view the entry.categories from the manage shell, it returns a list of categories (I also tried with from object instead of object.categories in the templatetag, but that gave the same error)

In [7]: c.categories.all()
Out[7]: [<Category: C1>, <Category: C2>, <Category: C3>]

Somehow I have to iterate the list of categories (which consist of the name and not the Category.id and retrieve other entries with the same category in the Node render. I have been looking into include-tags too, but I also got iterating errors. I cannot imagine that there isn't an easy solution for these kind of queries. These kind of queries are pretty common to perform, but how ???

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-users/-/KsGYSEO2NUkJ.

To post to this group, send email to django...@googlegroups.com.
To unsubscribe from this group, send email to django-users...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Josh

unread,
Aug 8, 2011, 7:16:09 PM8/8/11
to django...@googlegroups.com
I've marked a few things that are important I think, because it is getting a bit bulky 

>These are my assumptions about the bits of code you've posted:
>models.py
>categories = models.ManyToManyField('Category', blank=True, null=True, default = None)
>
>Assumption: categories is a field defined within a standard Django model; Category is >another model you have defined.

Yes, categories is a field in model Entry


>{% get_related_entries weblog.entry 5 from object.categories as related_entries %}
>     {% for entry in related_entries %}
>          <p><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
>     {% endfor %}
>
>
>Assumption: weblog and object are context variables provided to the template from the view. object is an instance of the model with the categories m2m field you showed above.
>
>I'm not sure how weblog.entry is relevant.

weblog.entry is the model that is used. The templatetag is used when on a blogentry detail page and should give a list of related entries (with corresponding categories)

It comes from django.db.models.get_model() 
 (p117 of Practical Django Projects). That's one of the books I'm learning from and now I'm trying to understand how custom templatetags work. Already made a few and stepping up the complexities


>def __init__(self, model, number, categories, varname):
>    self.categories = template.Variable(categories)
>
>Assumption: model, number, categories, and varname are the parameters from the template tag you show in the template snippet above. I am guessing that they are all just the strings, exactly as they >appear in the template.

model = weblog.entry
number = number of entries to display
categories = object.categories ; these are the categories linked to the entry (in this case 3 categories: C1, C2 and C3 )
varname = related_entries


>
>
>def render(self, context):
>    related = self.varname.resolve(context)
>    return related    
>
>At this point, I presume that you have defined self.varname in __init__ above, as "self.varname = varname", and you have just chosen not to paste that line.

This was a typo, because I was going back to check your suggestion from the djangoproject example (which I already tried some time before)
I corrected this to self.categories.resolve(context) and I didn't get a Template error anymore. But I didn't get any other entries either, and there should be at least 1

>
>Caught AttributeError while rendering: 'unicode' object has no attribute 'resolve'
>
>First off, self.varname, if I am correct, is just the string "related_entries" here. It doesn't have a "resolve" method, because it's just a string. Variable objects, like self.context, have a "resolve" method. 
>
>If you want to access the categories that you have mentioned in the template, then you can use
>self.categories.resolve(context)

See above. My mistake :-(
>
>If you want to access "weblog.entry", then you will need to create a Variable from it, like you did with categories:
>
>def __init__(...):
>    ...
>    self.model = template.Variable(model)
>    ...
>
>and then resolve it in render(), as self.model.resolve(context)

I'll add the Category as a model. Lowercase just as django.db.models.get_model() needs it.

self.modelcat = category

The relations are stored in content_entries_categories table in the Database (id, entry.id, category.id). Django retrieves it while rendering the detail_entry template (because it returns the category names in the shell.

Basically I want to do the same. Retrieve all entries that have the same category.id as the entry on the detailpage. How can I do that in a templatetag?

>
>If you need to push back a value in a variable call related_entries, then you will want to set a value in the context dictionary:
>
>context[related_entries] = <some value here>
>
>You won't be able to resolve it as a Variable, because it (presumably) doesn't even exist >before you call your custom tag, but once you set it on the context object, it will be >available to the rest of the template.

This I don't understand. As I understand Django (but correct me if I'm wrong) the related_entries should be a dictionary or list  of Entry-instances.


>
>Hope this helps,
>
>Ian
>

Josh

unread,
Aug 8, 2011, 7:36:30 PM8/8/11
to django...@googlegroups.com
>
>If you need to push back a value in a variable call related_entries, then you will want to set a value in the context dictionary:
>
>context[related_entries] = <some value here>

Correction on earlier mail when trying some other things. I do this in the def render. This looks like:

class RelatedEntryNode(template.Node):

    def __init__(self, model, number, categories, varname):
        self.model = model
        self.number = int(number)
        self.categories = template.Variable(categories)
        self.varname = varname

    def render(self, context):
        related = self.categories.resolve(context)
        context[self.varname] = related
        return ''
 
>
>You won't be able to resolve it as a Variable, because it (presumably) doesn't even exist >before you call your custom tag, but once you set it on the context object, it will be >available to the rest of the template.

Ian Clelland

unread,
Aug 9, 2011, 1:58:01 AM8/9/11
to django...@googlegroups.com
On Mon, Aug 8, 2011 at 4:36 PM, Josh <jos.car...@yahoo.com> wrote:
>
>If you need to push back a value in a variable call related_entries, then you will want to set a value in the context dictionary:
>
>context[related_entries] = <some value here>

Correction on earlier mail when trying some other things. I do this in the def render. This looks like:

class RelatedEntryNode(template.Node):

    def __init__(self, model, number, categories, varname):
        self.model = model
        self.number = int(number)
        self.categories = template.Variable(categories)
        self.varname = varname

Ok, so this means that:
    self.model is a string; literally, in your example, "weblog.entry"
    self.number is an integer
    self.categories is a variable reference -- you can resolve it at render time, to obtain the value of object.categories (or, depending on what object happens to be, Django could try object['categories'], or object.categories(), or a few other things -- the important thing is that the Django template framework will do the work of getting you a value)
    self.varname is another string; in this case, "related_entries"

Now, I *think* that you want self.model to be a Variable as well, just like self.categories, because I *don't think* that you are actually interested in the string "weblog.entry" -- I think that you want the value of the entry attribute of the weblog object. You want Django to resolve that value for you at template render time.
 

    def render(self, context):
        related = self.categories.resolve(context)
        context[self.varname] = related
        return ''
 
>
>You won't be able to resolve it as a Variable, because it (presumably) doesn't even exist >before you call your custom tag, but once you set it on the context object, it will be >available to the rest of the template.

This I don't understand. As I understand Django (but correct me if I'm wrong) the related_entries should be a dictionary or list  of Entry-instances.


That depends entirely on what *you* want *your template tag* to do. Given all of your descriptions, though, I'm led to believe that you want your tag to create a new variable, which you can use in your template. So, when you put

{% get_related_entries weblog.entry 5 from object.categories as related_entries %}

in your template, {{related_entries}} doesn't exist as a variable *before* your tag, but *after* your tag, it has some value, which you will have computed in the render() method.

In that case, then *in your node class*, self.related_entries is just a string -- the name of the variable to create. In your example, it happens to be the string "related_entries", but you could have written

{% get_related_entries weblog.entry 5 from object.categories as cheeseburger_hotel %}

and then self.related_entries would contain the string "cheeseburger_hotel". But that's fine, because your render() method is going to compute some value, probably a list of Entry instances in this case, and then it will create a new context variable, with the statement;

context[self.related_entries] = <my_previously_computed_list>

And that is what is going to create the context variable {{related_entries}}, or {{cheeseburger_hotel}}, or whatever the template author asked for.


I hope that helps -- I think that you may be confusing things by using the same names for your python variables as you do for your template variables, when they really are very different things.

Josh

unread,
Aug 9, 2011, 3:54:40 AM8/9/11
to django...@googlegroups.com
{% get_related_entries weblog.entry 5 from object.categories as related_entries %}
     {% for entry in related_entries %}
          <p><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
     {% endfor %}

class RelatedEntryNode(template.Node):

     def __init__(self, model, number, categories, varname):
         self.model = model
         self.number = int(number)
         self.categories = template.Variable(categories)
         self.varname = varname


>    Ok, so this means that:
>        self.model is a string; literally, in your example, "weblog.entry"

No, self.model shouldn't be a string. It should be the model for Entry in app weblog (and I use it like that in other custom template tags that work)

    The templatetag is used when on a blog entry detail page and should give a list of related entries (with corresponding categories) .  It comes from django.db.models.get_model()  (p117 of Practical Django Projects)

>But that's fine, because your render() method is going to compute some
>value, probably a list of Entry instances in this case, and then it will
>create a new context variable, with the statement;

>context[self.related_entries] = <my_previously_computed_list>

This is where the problem is. I don't get a computed list. It returns nothing instead of 1 other related blogentry (in this case)

I'm really stuck now in solving this quite common query:

* a blog entry is shown in a detail page
* this blog entry has categories (m2m relation) Let's assume categories C1 and C2
* other blog entries have the same categories (also C1 and C2, I'll skip the OR relation for now)
* through a custom template tag I want to get the list of Entry instances that have categories C1 AND/OR C2 and display them on the detailpage.

* Three database tables are involved in this query
** weblog_entry (which contains the entries)
** category_entry (which contains the categories)
** weblog_entry_categories (which joins the entry with the category)

When displaying the blog entry, Django also references these 3 tables (otherwise I wouldn't get the category name in the template.)

I'm going around in circles and am afraid I don't see where the problem could be. I reread the books, went through the documentation and searched the internet, but am getting nowhere. I'm sure I'm overcomplicating things as this should be a quite easy common query to perform.

Maybe you could give a hint or a pointer how you would achieve this thorugh a template tag? 
Reply all
Reply to author
Forward
0 new messages