235

I want to concatenate a string in a Django template tag, like:

{% extend shop/shop_name/base.html %}

Here shop_name is my variable and I want to concatenate this with rest of path.

Suppose I have shop_name=example.com and I want result to extend shop/example.com/base.html.

daaawx
  • 3,273
  • 2
  • 17
  • 16
Ahsan
  • 11,516
  • 12
  • 52
  • 79

15 Answers15

438

Use with:

{% with "shop/"|add:shop_name|add:"/base.html" as template %}
{% include template %}
{% endwith %}
Steven
  • 5,654
  • 1
  • 16
  • 19
  • 2
    I was totally confused by this answer as it uses the include tag instead of the extend tag, but apparently it just works. Though I would recommend Ahsan's own answer as it also workes and is (in my opinion) semantically more correct and raises less confusion. – gitaarik Apr 02 '13 at 16:23
  • 20
    This may work but shouldn't be considered as a general answer to concatenate strings in django templates. See http://stackoverflow.com/a/23783666/781695 – user May 21 '14 at 12:49
  • As the saying in Django documentation, "Strings that can be coerced to integers will be summed, not concatenated" So, for example, if you want to concatenate model object's primary keys (may be useful for create unique cache key), it does not work. – Vladimir Chub Sep 25 '15 at 05:36
  • I think this doesn't escape `shop_name` at all, so it's dangerous. – Flimm Jan 12 '16 at 19:08
  • Be aware, as already mentioned, this works with strings only! If you translate `shop_name` before passing it to the context in a view's `get_context_data` make sure it is translated using `ugettext` instead of `ugettext_lazy`. – Kim Jan 31 '17 at 13:51
152

Don't use add for strings, you should define a custom tag like this :

Create a file : <appname>\templatetags\<appname>_extras.py

from django import template

register = template.Library()

@register.filter
def addstr(arg1, arg2):
    """concatenate arg1 & arg2"""
    return str(arg1) + str(arg2)

and then use it as @Steven says

{% load <appname>_extras %}

{% with "shop/"|addstr:shop_name|addstr:"/base.html" as template %}
    {% include template %}
{% endwith %}

Reason for avoiding add:

According to the docs

This filter will first try to coerce both values to integers... Strings that can be coerced to integers will be summed, not concatenated...

If both variables happen to be integers, the result would be unexpected.

Roger Dahl
  • 15,132
  • 8
  • 62
  • 82
user
  • 17,781
  • 20
  • 98
  • 124
  • Shouldn't that be @register.filter(name='addstr')? – seddonym Aug 26 '14 at 08:46
  • Good catch, I've changed function name to `addstr` – user Aug 26 '14 at 09:54
  • Looks like just one integer is enough... Thank you for this, it would have taken me ages to catch! – mccc Jan 27 '15 at 14:25
  • 11
    This should be marked as best answer because working correctly with values which can be coerced by Python as integers. – Vladimir Chub Sep 25 '15 at 05:41
  • Useful filter. Thanks! – madtyn Oct 04 '15 at 20:50
  • 2
    I dont know why you are not the one with the most "up" because it's *your* answer which is right, the "`add`" alone just doesn't use `str()` in first place and didn't work *at all* for me whereas your solution works flawlessly – Olivier Pons Jan 04 '16 at 23:17
  • 1
    Because the highly voted answer is the easiest - and, depending on the data, correct. If you have way for numbers to be in the field, then there is no problem. That said - having this here is a very important reminder that the data going into this tag must be considered. I +1'd both answers. – Shadow May 19 '16 at 02:06
  • 1
    Your answer has saved me! – Ljubisa Livac Apr 05 '18 at 11:06
  • 6
    Remember to load your custom filter at the top of your template file: `{% load _extras %}` – Susanne Peng Jul 19 '18 at 05:48
  • @SusannePeng and don't forget to restart your server if you introduced the _extras.py because of this – Jan Salvador van der Ven Feb 03 '20 at 10:30
  • Why do we use the with/endwith tags here? I believe it is possible to directly use the outcome of `addstr` in the include tag in this example, no? Is it just to illustrate how to turn the returned value into a variable so it can be used again or am I missing something more fundamental here? – Mitchell van Zuylen Jan 31 '22 at 06:29
  • why not use "add" if "shop/" and "base.html" are in fact strings. Personally I see little problem with built-in "add" here. – tikej Feb 08 '22 at 20:17
23

You do not need to write a custom tag. Just evaluate the vars next to each other.

"{{ shop name }}{{ other_path_var}}"
David Jay Brady
  • 1,034
  • 8
  • 20
20

I have changed the folder hierarchy

/shop/shop_name/base.html To /shop_name/shop/base.html

and then below would work.

{% extends shop_name|add:"/shop/base.html"%} 

Now its able to extend the base.html page.

Ahsan
  • 11,516
  • 12
  • 52
  • 79
  • 1
    The other answers don't allow to use concatenation with `extends` as `extends must be the first template tag in the template – abumalick Sep 13 '20 at 20:31
13

Refer to Concatenating Strings in Django Templates:

  1. For earlier versions of Django:

    {{ "Mary had a little"|stringformat:"s lamb." }}

"Mary had a little lamb."

  1. Else:

    {{ "Mary had a little"|add:" lamb." }}

"Mary had a little lamb."

Daniel Holmes
  • 1,952
  • 2
  • 17
  • 28
bing
  • 172
  • 3
  • 6
3

Have a look at the add filter.

Edit: You can chain filters, so you could do "shop/"|add:shop_name|add:"/base.html". But that won't work because it is up to the template tag to evaluate filters in arguments, and extends doesn't.

I guess you can't do this within templates.

cezar
  • 11,616
  • 6
  • 48
  • 84
Daniel Hepper
  • 28,981
  • 10
  • 72
  • 75
  • this is not going to work. i want to add my variable in middle of path. – Ahsan Dec 08 '10 at 10:53
  • add filter only summed not concatenate according to django docs – Ahsan Dec 08 '10 at 11:18
  • The docs say "strings that can be coerced to integers will be summed". Other strings are concatenated. But that doesn't really matter anyway because you can't use the filter :( – Daniel Hepper Dec 08 '10 at 13:27
2

From the docs:

This tag can be used in two ways:

  • {% extends "base.html" %} (with quotes) uses the literal value "base.html" as the name of the parent template to extend.
  • {% extends variable %} uses the value of variable. If the variable evaluates to a string, Django will use that string as the name of the parent template. If the variable evaluates to a Template object, Django will use that object as the parent template.

So seems like you can't use a filter to manipulate the argument. In the calling view you have to either instantiate the ancestor template or create an string variable with the correct path and pass it with the context.

User97693321
  • 3,336
  • 7
  • 45
  • 69
Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
2

I found working with the {% with %} tag to be quite a hassle. Instead I created the following template tag, which should work on strings and integers.

from django import template

register = template.Library()


@register.filter
def concat_string(value_1, value_2):
    return str(value_1) + str(value_2)

Then load the template tag in your template at the top using the following:

{% load concat_string %}

You can then use it the following way:

<a href="{{ SOME_DETAIL_URL|concat_string:object.pk }}" target="_blank">123</a>

I personally found this to be a lot cleaner to work with.

Bono
  • 4,757
  • 6
  • 48
  • 77
2

And multiple concatenation:

from django import template
register = template.Library()


@register.simple_tag
def concat_all(*args):
    """concatenate all args"""
    return ''.join(map(str, args))

And in Template:

{% concat_all 'x' 'y' another_var as string_result %}
concatenated string: {{ string_result }}
Gassan
  • 81
  • 2
  • 4
2

How about this! We have first_name and last_name, which we want to display space " " separated.

{% with first_name|add:' '|add:last_name as name %}
    <h1>{{ name }}</h1>
{% endwith %}

What we're actually doing is: first_name + ' ' + last_name

Ali Sajjad
  • 3,589
  • 1
  • 28
  • 38
1

@error's answer is fundamentally right, you should be using a template tag for this. However, I prefer a slightly more generic template tag that I can use to perform any kind of operations similar to this:

from django import template
register = template.Library()


@register.tag(name='captureas')
def do_captureas(parser, token):
    """
    Capture content for re-use throughout a template.
    particularly handy for use within social meta fields 
    that are virtually identical. 
    """
    try:
        tag_name, args = token.contents.split(None, 1)
    except ValueError:
        raise template.TemplateSyntaxError("'captureas' node requires a variable name.")
    nodelist = parser.parse(('endcaptureas',))
    parser.delete_first_token()
    return CaptureasNode(nodelist, args)


class CaptureasNode(template.Node):
    def __init__(self, nodelist, varname):
        self.nodelist = nodelist
        self.varname = varname

    def render(self, context):
        output = self.nodelist.render(context)
        context[self.varname] = output
        return ''

and then you can use it like this in your template:

{% captureas template %}shop/{{ shop_name }}/base.html{% endcaptureas %}
{% include template %}

As the comment mentions, this template tag is particularly useful for information that is repeatable throughout a template but requires logic and other things that will bung up your templates, or in instances where you want to re-use data passed between templates through blocks:

{% captureas meta_title %}{% spaceless %}{% block meta_title %}
    {% if self.title %}{{ self.title }}{% endif %}
    {% endblock %}{% endspaceless %} - DEFAULT WEBSITE NAME
{% endcaptureas %}

and then:

<title>{{ meta_title }}</title>
<meta property="og:title" content="{{ meta_title }}" />
<meta itemprop="name" content="{{ meta_title }}">
<meta name="twitter:title" content="{{ meta_title }}">

Credit for the captureas tag is due here: https://www.djangosnippets.org/snippets/545/

K3TH3R
  • 734
  • 5
  • 12
0

You can't do variable manipulation in django templates. You have two options, either write your own template tag or do this in view,

damir
  • 1,898
  • 3
  • 16
  • 23
  • my requirement is to do it in only templates so views option is not helpful. i also tried via custom template tag but {% load concat %} should after the {% extend .... %} tag. so how i can do it now? – Ahsan Dec 08 '10 at 10:35
  • Write an extended_extends tag which accepts an string format and arguments. – Paulo Scardine Dec 08 '10 at 11:33
  • can u please give me an example of how to write custom tags for default ones? – Ahsan Dec 08 '10 at 11:43
0

extends has no facility for this. Either put the entire template path in a context variable and use that, or copy the exist template tag and modify it appropriately.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • thanx for reply! for context variable i have to set in view.py which i cant due to my project requirement. and please give example of second one. – Ahsan Dec 08 '10 at 11:34
0

In my project I did it like this:

@register.simple_tag()
def format_string(string: str, *args: str) -> str:
    """
    Adds [args] values to [string]
    String format [string]: "Drew %s dad's %s dead."
    Function call in template: {% format_string string "Dodd's" "dog's" %}
    Result: "Drew Dodd's dad's dog's dead."
    """
    return string % args

Here, the string you want concatenate and the args can come from the view, for example.

In template and using your case:

{% format_string 'shop/%s/base.html' shop_name as template %}
{% include template %}

The nice part is that format_string can be reused for any type of string formatting in templates

Jailton Silva
  • 380
  • 4
  • 14
0

In my case I needed to concatenate to send a string concatenated by parameter to a simple_tag and I didn't need the with, which saves 2 lines:

{% method firstParam "stringSecondParam="|add:valueSecondParam thirdParam as result %} In this case the solution to the problem would be: "string="|add:object

IvanAllue
  • 434
  • 3
  • 11