1

Reference: How do I construct a Django reverse/url using query args?

This is in somepage.html

<a href={% query_urls from={{from}} to={{to}} %}> LOL LOSER</a>

First go to somepage views, and then clicking whatever button will redirect to move views.

def move(request):
    to = request.GET.get('to', 'None')
    ffrom = request.GET.get('from', 'None')
    #raise AssertionError(ffrom)
    return render_to_response(request, "move.html", {'to': to, 'from': ffrom})

def somepage(request):
    to = '../mydir'
    ffrom = './heere.py'
    return render_to_response(request, "somepage.html", {'to': to, 'from': ffrom})

Instead of getting something like http://localhost/web/move?from=./here.py&to=../mydir I get this

http://localhost/web/move?from={{from}}&to={{to}}

Those context vars didn't get render at all, probably because the custom tag(applied to somepage views) takes all the parameters as string. How do I force to render it first?

Thanks.


** EDIT ** Small question: If I want to achieve this

url(r"^search/<?P(cbid)\d+>/", 'views.search', name='search')

I get Malformed arguments to query_urls tag if I put this in template

<a href={% query_url 'search' 12456 from=from to=to %}> MY LINK </a>

What's the generic way of writing my custom tag to allow this?

Currently, this is what I do... which works...

def render(self, context):
    view_name = self.view_name.resolve(context)
    kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context))
                   for k, v in self.kwargs.items()])
    cbid = kwargs['cbid']
    kwargs = sorted(kwargs.items(), key=lambda x:x[0]) # sorted and generate a list of 2-tuple
    # kwargs query set now contains no cbid
    kwargs = [ value for index, value in enumerate(kwargs) if value[0] != 'cbid']

    #raise AssertionError(urllib.urlencode(kwargs))
    return (reverse(view_name, args=[(cbid),], current_app=context.current_app)
            + '?' + urllib.urlencode(kwargs))

I want to make it more generic, to match any pattern, not just cbid.

<a href={% query_url 'search' cbid=12456 from=from to=to %}> MY LINK </a>

A dumb way (and probably the only way) is to write something like this in the template

{% query_url 'view_func' args=[(cbid, some_text, more_text,)], from=foo to=bar %}

where args is taken literally as a list of args just as in a regular python function. We can probably eval this into a list instead of a literal string.

Community
  • 1
  • 1
User007
  • 1,519
  • 6
  • 22
  • 35
  • 1
    There's no need for the `{{ }}` in a template tag: you should just write `{% query_urls from=from to=to %}` – Gareth Rees Mar 15 '12 at 23:45
  • @GarethRees Thanks. I just tried it. It doesn't work. It is giving me `from=from&to=to` – User007 Mar 15 '12 at 23:52
  • 1
    Ah, I see: you're using the `myurl` template tag from the question you linked to. That doesn't do variable evaluation. I'll write a detailed answer. – Gareth Rees Mar 16 '12 at 00:24

2 Answers2

2

I assume that the query_url template tag you're calling is the myurl tag from the answer you linked to. That template tag doesn't resolve its arguments against the template context. Here's a more complete implementation that you might try, that also does a reverse lookup to get the URL to append the query arguments to.

In Django 1.4, this is a straightforward use of the simple_tag decorator:

from django import template

register = template.Library()

@register.simple_tag
def query_url(view_name, **kwargs):
    """
    Returns an absolute URL matching given view, with query parameters
    appended. For example, if you have this URL in your configuration:

        ('^search/$', 'myapp.search')

    then in a template you can create a search link like this:

        {% query_url 'myapp.search' q=value1 id=value2 %}

    The first argument is a path to a view. The other arguments become
    query parameters, so the URL will look something like:
    ``/search/?q=querystring&id=123``.
    """
    return reverse(view_name) + '?' + urllib.urlencode(kwargs)

But in Django 1.3 you have to spell it all out in detail:

from django import template
from django.core.urlresolvers import reverse
from django.template import Library, Node, TemplateSyntaxError
from django.utils.encoding import smart_str
import re
import urllib

register = template.Library()

class QueryURLNode(Node):
    def __init__(self, view_name, kwargs):
        self.view_name = view_name
        self.kwargs = kwargs

    def render(self, context):
        view_name = self.view_name.resolve(context)
        kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context))
                       for k, v in self.kwargs.items()])
        return (reverse(view_name, current_app=context.current_app)
                + '?' + urllib.urlencode(kwargs))

@register.tag
def query_url(parser, token):
    """
    Returns an absolute URL matching given view, with query parameters
    appended. For example, if you have this URL in your configuration:

        ('^search/$', 'myapp.search')

    then in a template you can create a search link like this:

        {% query_url 'myapp.search' q=object.name id=object.id %}

    The first argument is a path to a view. The other arguments become
    query parameters, so the URL will look something like:
    ``/search/?q=name&id=123``.
    """
    bits = token.split_contents()
    if len(bits) < 2:
        raise TemplateSyntaxError("'%s' takes at least one argument"
                                  " (path to a view)" % bits[0])
    viewname = parser.compile_filter(bits[1])
    kwargs = {}
    kwarg_re = re.compile(r"(\w+)=(.+)")
    for bit in bits[2:]:
        match = kwarg_re.match(bit)
        if not match:
            raise TemplateSyntaxError("Malformed arguments to %s tag" % bits[0])
        name, value = match.groups()
        kwargs[name] = parser.compile_filter(value)
    return QueryURLNode(viewname, kwargs)
Community
  • 1
  • 1
Gareth Rees
  • 64,967
  • 9
  • 133
  • 163
  • Thanks. Why do we use `parser.compile_filter`?? What does that do? Thanks. – User007 Mar 16 '12 at 01:39
  • 1
    It constructs a [`FilterExpression`](https://code.djangoproject.com/browser/django/trunk/django/template/base.py#L507) object that "parses a variable token and its optional filters". So you can use template filters on the arguments to the tag: `{% query_url 'myapp.view' a=var|upper b=var|truncatechars:10 %}` or whatever. – Gareth Rees Mar 16 '12 at 09:55
  • 1
    @GarethRees Thank you very much. Just another small question... if I have a url pattern like `'^search/`, what should be the right parameters to insert in the template? I am getting errors like "No Match reverse". I am trying to figure this out on my own as well. I am hoping the final result will end up like this: `search/123455/?q=helloworld&qq=foobar` – User007 Mar 19 '12 at 03:30
  • I can solve this by brute force in render function. Please take a look at my edit above. How can I make it more generic which accepts any kind of patterns? (sued by multiple views) which has different patterns. – User007 Mar 19 '12 at 04:38
1

The problem is that you didn't resolve the context variable in your custom tag.

Read this: Passing template variable to a tag

CppLearner
  • 16,273
  • 32
  • 108
  • 163