Opened 10 years ago

Last modified 3 years ago

#2539 new New feature

Custom tags and filters can be restricted by namespace

Reported by: limodou@… Owned by:
Component: Template system Version:
Severity: Normal Keywords: djangocon
Cc: me@…, ramusus@…, mhf@… Triage Stage: Accepted
Has patch: yes Needs documentation: yes
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

How to use namespace? For example,

{% load example %}
{% example.testtag %}

And I think if the namespace can be optional is better. I don't know
if it's useful, but sometimes I read others projects, and as I reading
the template, I don't know which tag is in which file. So find the
correct tag definition file is somewhat diffcult. If I can add
namespace in the front of a custom tag name, it'll be easy to locate
the source file.

The maillist discussion thread is HERE

Attachments (6)

template.diff (5.6 KB) - added by limodou 10 years ago.
template_01.diff (5.6 KB) - added by limodou@… 10 years ago.
Fix regular pattern [\w.]+ to [\w\.]+
template_02.diff (5.7 KB) - added by limodou 10 years ago.
move rsplit to rindex and reduce duplicate path entry in path
template_03.diff (5.7 KB) - added by limodou 10 years ago.
change rsplit to rindex. (upload again, last patch disappeared)
template_04.diff (5.5 KB) - added by racter 9 years ago.
adjusted limodou's patch to apply to trunk as of 6213
patch_2539_code_tests.diff (17.4 KB) - added by durdinator 9 years ago.
Patch against [6589], including tests and docs

Download all attachments as: .zip

Change History (38)

Changed 10 years ago by limodou

Attachment: template.diff added

comment:1 Changed 10 years ago by limodou@…

Summary: Can custom tag use namespace[Patch]Custom tags and filters can be restricted by namespace

This patch include three modified files. So you can load a custom taglib in two styles in template file:

{% load example %}
{% load testapp.example %}

Then you can use tags and filters through three styles, for example:

{% testtag %}    <---- current usage
{% example.testtag %}
{% testapp.example.testtag %}

and you can also apply this style to filter, for example:

{{ "string"|testfilter }}
{{ "string"|example.testfilter }}
{{ "string"|testapp.example.testfilter }}

comment:2 Changed 10 years ago by limodou@…

I forgot that you can also use:

{% load testapp1.example %}
{% load testapp2.example %}

So you will see you can load homonymy taglibs distinquished by app name with this patch. And if there are homonymy tags, and you didn't restrict them with namespace, the last one will be effective. But you can restrict them by namespace, just like:

{% testapp1.example.tag1 %}
{% testapp2.example.tag2 %}

So this time, each tag will be different.

comment:3 Changed 10 years ago by anonymous

line 507: (?P<filter_name>[\w.]+)
should be (?P<filter_name>[\w\.]+)
right? otherwise you'll include any character, not just dots.

comment:4 Changed 10 years ago by limodou

Right. I'll change it.

Changed 10 years ago by limodou@…

Attachment: template_01.diff added

Fix regular pattern [\w.]+ to [\w\.]+

comment:5 Changed 10 years ago by Tom Tobin <korpios@…>

That's no so, anonymous:

[] Used to indicate a set of characters. Characters can be listed individually, or a range of characters can be indicated by giving two characters and separating them by a "-". Special characters are not active inside sets.

(Emphasis mine; from http://docs.python.org/lib/re-syntax.html)

limodou, there's no need for that patch. :)

comment:6 Changed 10 years ago by Tom Tobin <korpios@…>

Err, I should say there's no need for that patched patch. :">

comment:7 Changed 10 years ago by limodou@…

I tried two pattern, both are right.

comment:8 Changed 10 years ago by mhf@…

This patch depends on rsplit, a function not found in python2.3:

>>> "foo.bar.baz".rsplit('.')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'str' object has no attribute 'rsplit'

The guilty line is in templatetags/init.py, line 5:

        __path__.extend(__import__(a.rsplit('.', 1)[0], '', '', ['']).__path__)

I think this could be used instead:

        __path__.extend(__import__('.'.join(a.split('.')[:a.count('.') and -1 or None])), '', '', ['']).__path__)

Changed 10 years ago by limodou

Attachment: template_02.diff added

move rsplit to rindex and reduce duplicate path entry in path

Changed 10 years ago by limodou

Attachment: template_03.diff added

change rsplit to rindex. (upload again, last patch disappeared)

comment:9 Changed 10 years ago by limodou

Why my attachment can not be accepted? I upload it successful, but I cann't see them.

comment:10 Changed 10 years ago by limodou

My god, after submit last comment, I see them.

comment:11 Changed 10 years ago by anonymous

Triage Stage: UnreviewedDesign decision needed

comment:12 Changed 10 years ago by Michael Radziej <mir@…>

Triage Stage: Design decision neededUnreviewed

No anonymous stage changes, please.

comment:13 Changed 10 years ago by Simon G. <dev@…>

Has patch: set
Needs tests: set
Summary: [Patch]Custom tags and filters can be restricted by namespaceCustom tags and filters can be restricted by namespace
Triage Stage: UnreviewedDesign decision needed

comment:14 Changed 9 years ago by Adrian Holovaty

Triage Stage: Design decision neededAccepted

Yes, yes, yes. This is a great suggestion.

comment:15 Changed 9 years ago by anonymous

Needs documentation: set
Owner: changed from nobody to anonymous
Patch needs improvement: set
Status: newassigned

patch no longer applies

comment:16 Changed 9 years ago by racter

Owner: changed from anonymous to racter
Status: assignednew

Changed 9 years ago by racter

Attachment: template_04.diff added

adjusted limodou's patch to apply to trunk as of 6213

comment:17 Changed 9 years ago by anonymous

Patch needs improvement: unset

comment:18 Changed 9 years ago by racter

Owner: changed from racter to anonymous
Patch needs improvement: set
Status: newassigned

actually my revision of that patch still has some problems - i'm writing tests now & will try to work it out.

comment:19 Changed 9 years ago by durdinator

Cc: me@… added

This will need to play nicely with [6289] -- loading templatetags from subdirectories.

Due to that change, a conflict between app_name.tag_library and tag_library.subdirectory could arise:

foo/
    templatetags/
        bar.py

baz/
    templatetags/
        foo/
            bar.py

In such a case, the app_name.tag_library should take priority.

comment:20 Changed 9 years ago by durdinator

Needs tests: unset
Patch needs improvement: unset

OK, this looks like it's stagnating, and it's something I definitely want. So here's a patch against [6507]. It's not based on limodou's or racter's work, and is more comprehensive in what it tidies up. Here's a summary of the result:

All tag libraries have an associated fully qualified name, which looks like "app_name.tag_library_name"; this fully qualified name can be used instead of the unqualified library_name in the {% load %} tag:

Normally we'd do
{% load humanize %}
But if another installed app has a 'humanize' tag libary, we might need to do
{% load django.contrib.humanize.humanize %}

If more than one installed app has a tag library with the same name, then the app closest to the start of the INSTALLED_APPS list will take precedence if the unqualified name is used.

Correspondingly, templatetags and filters are also namespaced -- by the name of the tag library as given to {% load %}.
So you can do this:

{% load library %}
{% load otherapp.library %}

{% library.mytag %} # Outputs "A"
{% otherapp.library.mytag %} # Outputs "B"

But not this:

{% load firstapp.library %}
{% library.mytag %} # We've not loaded anything as 'library'

If there is a conflict in tag names, then the last loaded tag library takes precedence (the same as the behaviour in trunk in such a case).

All that applies to tags also applies to filters, of course.

The standard django tag libraries can be accessed via their fully qualified names "django.defaulttags", "django.defaultfilters", "django.loader_tags", and "django.i18n".

Possible backward-incompatible changes (not sure if any of these changed interfaces were intended to be publically used):

  • The items of django.template.libraries and django.template.builtins are now tuples, not Library instances.
  • Template.add_library and django.template.add_to_builtins require additional arguments.
  • The django.templatetags package no longer augments its path with from all installed apps' "templatetags" packages -- instead it is limited to the standard set of django tags.

A definite (though doubtless rare) backward-incompatible change:

  • {% load foo.bar %} will load from foo<app>.templatetags.bar in preference to <some_app>.templatetags.foo.bar.

Final notes:

The django and contrib tests that used template.libraries and/or add_to_builtins have been updated, so they should still pass (I've not checked the humanize or markup tests though).

The admin docs has been updated to list all tag libraries available, and list the fully qualified name alongside the unqualified name.

And I still need to modify the documentation to accommodate these changes, but I'll do that in another patch, because it's getting late.

comment:21 Changed 9 years ago by durdinator

Updated patch against [6589]; the docs for the "load" tag have been updated.

Changed 9 years ago by durdinator

Attachment: patch_2539_code_tests.diff added

Patch against [6589], including tests and docs

comment:22 Changed 8 years ago by anonymous

Keywords: djangocon added

comment:23 Changed 8 years ago by Eric Holscher

Needs tests: set
Patch needs improvement: set

I'm not assigning myself this ticket because I have a feeling that it will be evil and hard to do. I think that it can be made backwards compatible with an 'opt in' kind of model for it if it gets into core. In that the old functionality stays the default and if you want/need namespaces, then you can explicitly declare them. Then do the normal depreciation process from then out.

I am interested in helping with this however, so when some momentum gets going and I have some more time in a month or 2, Let's get this done.

comment:24 Changed 8 years ago by Eric Holscher

Owner: anonymous deleted
Status: assignednew

comment:25 Changed 8 years ago by simon

One way to do this in a backwards compatible manner would be to introduce a new {% import %} tag, which behaves like {% load %} except tags included using {% import %} must be accessed via a namespace. For example:

{% import humanize %}

{{ num|humanize.apnumber }}

Then we can encourage people to start using {% import %} and deprecate {% load %}, then remove load entirely later on.

comment:26 Changed 7 years ago by ramusus

Cc: ramusus@… added

comment:27 Changed 7 years ago by Johannes Dollinger

See also: #12772 (duplicate)

comment:28 Changed 5 years ago by Łukasz Rekucki

Type: enhancementNew feature

comment:29 Changed 5 years ago by Łukasz Rekucki

Severity: normalNormal

comment:30 Changed 5 years ago by anonymous

Easy pickings: unset
UI/UX: unset

This would be a fantastic addition. Coming from Grails, all of the taglib's there allow a namespace and it is extremely helpful in organization and maintenance of a project.

comment:31 Changed 5 years ago by mhf@…

Cc: mhf@… added

comment:32 Changed 3 years ago by jonathanslenders

Not entirely sure, but I that that if #20434 gets merged, it should also be fairly easy to add namespacing as part of the templatetag grammar. (If that's still a way we'd like to go.)
For instance, for i18n.blocktrans, it becomes:

{% load i18n %}
{% i18n.blocktrans %}...{% i18n.endblocktrans %}
Version 0, edited 3 years ago by jonathanslenders (next)
Note: See TracTickets for help on using tickets.
Back to Top