Welcome to django-classy-tags’s documentation!¶
django-classy-tags is an approach at making writing template tags in Django easier, shorter and more fun by providing an extensible argument parser which reduces most of the boiler plate code you usually have to write when coding custom template tags.
django-classy-tags does no magic by design. Thus you will not get automatic
registering/loading of your tags like other solutions provide. You will not get
automatic argument guessing from function signatures but rather you have to
declare what arguments your tag accepts. There is also no magic in your template
tag class either, it’s just a subclass of django.template.Node
which
invokes a parser class to parse the arguments when it’s initialized and resolves
those arguments into keyword arguments in it’s render
method and calls it’s
render_tag
method with those keyword arguments.
Contents:
Installation¶
To install django-classy-tags, please use
sudo pip install django-classy-tags
.
If you don’t want to use pip
, download the latest version from
pypi, unpack the tarball and
run sudo python setup.py install
.
django-classy-tags has no dependencies other than Django. Django 1.11 and later are supported.
Usage¶
A simple example¶
A very basic tag which takes no arguments and always returns 'hello world'
would be:
from classytags.core import Tag
from django import template
register = template.Library()
class HelloWorld(Tag):
name = 'hello_world'
def render_tag(self, context):
return 'hello world'
register.tag(HelloWorld)
Now let’s explain this. To create a tag, you subclass classytags.core.Tag
and define a classytags.core.Tag.render_tag()
method which takes the
context and any template tag options you define as arguments to the method.
Since we did not define any options for this tag, it only takes context. The
classytags.core.Tag.render_tag()
method should always return a string.
classytags.core.Tag.render_tag
on a tag class is what is used when
registering the tag with a Django template tag library and also what will be
used in the template.
Defining options¶
Defining options is done by setting the classytags.core.Tag.options
attribute on your tag class to an instance of classytags.core.Options
.
The Options
class takes any amount of argument objects or strings (called
breakpoints) as initialization arguments.
Let’s build a tag which takes a single argument and an optional ‘as varname’ argument:
from classytags.core import Tag, Options
from classytags.arguments import Argument
from django import template
register = template.Library()
class Hello(Tag):
name = 'hello'
options = Options(
Argument('name'),
'as',
Argument('varname', required=False, resolve=False)
)
def render_tag(self, context, name, varname):
output = 'hello %s' % name
if varname:
context[varname] = output
return ''
else:
return output
register.tag(Hello)
In a template we could now do either {% hello "world" %}
which would output
'hello world'
or {% hello "world" as "varname" %}
which would output
nothing but set the {{ varname }}
template variable to 'hello world'
.
You may also use {% hello "world" as varname %}
to achieve the same result
like the last example.
Writing a block tag¶
You can write tags which wrap a block (nodelist) in the template. An example for
this kind of tag is Django’s built-in {% with %}
tag.
To write the {% with %}
tag from Django using django-classy-tags you would
do:
from classytags.core import Tag, Options
from classytags.arguments import Argument
from django import template
register = template.Library()
class With(Tag):
name = 'with'
options = Options(
Argument('variable'),
'as',
Argument('varname', resolve=False),
blocks=[('endwith', 'nodelist')],
)
def render_tag(self, context, variable, varname, nodelist):
context.push()
context[varname] = variable
output = nodelist.render(context)
context.pop()
return output
register.tag(With)
Working with multiple blocks¶
If you’re working with multiple, optional blocks, the nodelist is always credited to the leftmost block name.
For example the Django for
tag accepts an optional empty
block. Let’s
take following classytag options:
options = Options(
CommaSeperatableMultiValueArgument('loopvars'),
'in',
arguments.Argument('values'),
blocks=[('empty', 'pre_empty'), ('endfor', 'post_empty')],
)
If you use it with {% for x in y %}hello{% empty %}world{% enfor %}
the
pre_empty argument to your classytags.arguments.Argument.render_tag`()
would hold a nodelist containing hello
, post_empty would contain
world
. Now if you have {% for x in y%}{{ hello }}{% endfor %}
,
pre_empty remains the same, but post_empty is an empty nodelist.
Easy ‘as’ Tags¶
There is a helper class for tags which store their output (optionally) in the
context. This class is in classytags.helpers.AsTag
and instead of
defining a render_tag method, you define a
classytags.helpers.AsTag.get_value()
method which returns the value you
want to either display or be put into the context.
Here is a small example:
from classytags.core import Options
from classytags.arguments import Argument
from classytags.helpers import AsTag
from django import template
register = template.Library()
class Dummy(AsTag):
options = Options(
'as',
Argument('varname', resolve=False, required=False),
)
def get_value(self, context):
return 'dummy'
register.tag(Dummy)
Now if you do {% dummy %}
in your templates, it will output ‘dummy’ there.
If you use {% dummy as myvar %}
‘dummy’ will be stored into the myvar
context variable.
Inclusion Tags¶
A helper class for inclusion tags (template tags which render a template) is
provided at classytags.helpers.InclusionTag
.
Instead of the usual render_tag method it provides two methods
classytags.helpers.InclusionTag.get_template()
which by default returns
the attribute classytags.helpers.InclusionTag.template
and defines the
template to use for rendering. The method
classytags.helpers.InclusionTag.get_context()
should return a dictionary
holding the content to use for rendering the template. Both those methods get
the context and the arguments of the tag passed as arguments.
A very simple example would be:
from classytags.core import Options
from classytags.arguments import Argument
from classytags.helpers import InclusionTag
from django import template
register = template.Library()
class Dummy(InclusionTag):
template = 'dummy.html'
def get_context(self, context):
return {'varname': 'dummy'}
register.tag(Dummy)
With the following template for dummy.html:
varname: {{ varname }}
This would always render as varname: dummy
.
Advanced Block Definition¶
Sometimes you might want to allow your blocktag to be terminated by a variable
end tag to make templates more readable. This is for example done in the
block
tag in Django, where you can do
{% block myblock %}...{% endblock %}
as well as
{% block myblock %}...{% endblock myblock %}
. To do so in classytags, you
have to use advanced block definitions using the
classytags.blocks.BlockDefinition
class together with the
classytags.blocks.VariableBlockName
class.
An example for a tag with the same signature as Django’s block tag:
class Block(Tag):
options = Options(
Argument('name', resolve=False),
blocks=[
BlockDefinition('nodelist', VariableBlockName('endblock %(value)s', 'name'), 'endblock')
]
)
Arguments in depth¶
Examples given here are in tabular form. The non-standard options are a list of
options to the argument class which are not the default value. The input is the
single token in your template tag that gets consumed by this argument class. The
output is the value you get in your render_tag
method for the keyword of
this argument.
Context for all examples: {'name': 'classytags'}
Argument¶
Non-standard options |
Input |
Output |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
KeywordArgument¶
Non-standard options |
Input |
Output |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IntegerArgument¶
Non-standard options |
Input |
Output |
---|---|---|
|
|
|
|
|
ChoiceArgument¶
Non-standard options |
Input |
Output |
---|---|---|
|
|
|
|
|
|
MultiValueArgument¶
Non-standard options |
Input |
Output |
---|---|---|
|
||
|
|
MultiKeywordArgument¶
Non-standard options |
Input |
Output |
---|---|---|
|
||
|
|
|
|
|
|
Flag¶
Non-standard options |
Input |
Output |
---|---|---|
|
|
|
|
|
|
|
|
|
Reference¶
classytags.arguments
¶
This module contains standard argument types.
- class classytags.arguments.Argument(name[, default=None][, required=True], [resolve=True])¶
A basic single value argument with name as it’s name.
default is used if required is False and this argument is not given.
If resolve is
False
, the argument will not try to resolve it’s contents against the context. This is especially useful for ‘as varname’ arguments. Note that quotation marks around the argument will be removed if there are any.- value_class¶
The class to be used to wrap the value in. Defaults to
classytags.values.StringValue.
- get_default()¶
Returns the default value for this argument
- parse(parser, token, tagname, kwargs)¶
Parses a single token into kwargs. Should return
True
if it consumed this token orFalse
if it didn’t.
- parse_token(parser, token)¶
Parses a single token using parser into an object which is can be resolved against a context. Usually this is a template variable, a filter expression or a
classytags.utils.TemplateConstant
.
- class classytags.arguments.Argument(name[, default=None][, required=True], [resolve=True])¶
Same as
classytags.arguments.Argument
but withclassytags.values.StrictStringValue
asvalue_class
.
- class classytags.arguments.KeywordArgument(name[, default=None][, required=True] [, resolve=True][, defaultkey=None][, splitter='='])¶
An argument that allows
key=value
notation.defaultkey is used as key if no key is given or the default value should be used.
splitter is used to split the key value pair.
- wrapper_class¶
Class to use to wrap the key value pair in. Defaults to
classytags.values.DictValue
.
- class classytags.arguments.IntegerArgument¶
Same as
classytags.arguments.Argument
but withclassytags.values.IntegerValue
asvalue_class
.
- class classytags.arguments.ChoiceArgument(name, choices[, default=None][, required=True] [, resolve=True])¶
An argument which validates it’s input against predefined choices.
- class classytags.arguments.MultiValueArgument(name[, default=NULL][, required=True] [, max_values=None][, resolve=True])¶
An argument which accepts a variable amount of values. The maximum amount of accepted values can be controlled with the max_values argument which defaults to
None
, meaning there is no maximum amount of values.default is an empty list if required is
False
.resolve has the same effects as in
classytags.arguments.Argument
however applies to all values of this argument.The default value for
value_class
is$classytags.values.ListValue
.- sequence_class¶
Class to be used to build the sequence. Defaults to
classytags.utils.ResolvableList
.
- class classytags.arguments.MultiKeywordArgument(name[, default=None][, required=True] [, resolve=True][, max_values=None] [, splitter='='])¶
Similar to
classytags.arguments.KeywordArgument
but allows multiple key value pairs to be given. The will be merged into one dictionary.Arguments are the same as for
classytags.arguments.KeywordArgument
andclassytags.arguments.MultiValueArgument
, except that default_key is not accepted and default should be a dictionary orNone
.
- class classytags.arguments.Flag(name[, default=NULL][, true_values=None][, false_values=None] [, case_sensitive=False])¶
A boolean flag. Either true_values or false_values must be provided.
If default is not given, this argument is required.
true_values and false_values must be either a list or a tuple of strings. If both true_values and false_values are given, any value not in those sequences will raise a
classytag.exceptions.InvalidFlag
exception.case_sensitive defaults to
False
and controls whether the values are matched case sensitive or not.
classytags.blocks
¶
This module contains classes for Advanced Block Definition.
- class classytags.blocks.BlockDefintion(alias, *names)¶
A block definition with the given alias and a sequence of names. The members of the names sequence must either be strings,
classytags.blocks.VariableBlockName
instances or other objects implementing at least acollect()
method compatible with the one ofclassytags.blocks.VariableBlockName
.- alias¶
The alias for this definition to be used in the tag’s kwargs.
- names¶
Sequence of strings or block name definitions.
- validate(options)¶
Validates this definition against an instance of
classytags.core.Options
by calling thevalidate()
on all it’snames
if such a method is available.
- collect(parser)¶
Returns a sequence of strings to be used in the
parse_until
statement. This is a sequence of strings that this block accepts to be handled. The parser argument is an instance ofclassytags.parser.Parser
.
- class classytags.blocks.VariableBlockName(template, argname)¶
A block name definition to be used in
classytags.blocks.BlockDefinition
to implement block names that depend on the (unresolved) value of an argument. The template argument to this class should be a string with thevalue
string substitution placeholder. For example:'end_my_block %(value)s'
. The argname argument is the name of the argument from which the value should be extracted.- validate(options)¶
Validates that the given argname is actually available on the tag.
- collect(parser)¶
Returns the template substitued with the value extracted from the tag.
classytags.core
¶
This module contains the core objects to create tags.
- class classytags.core.Options(*options, **kwargs)¶
Holds the options of a tag. options should be a sequence of
classytags.arguments.Argument
subclasses or strings (for breakpoints). You can give they keyword argument blocks to define a list of blocks to parse until. You can specify a custom argument parser by providing the keyword argument parser_class.- all_argument_names¶
A list of all argument names in this tag options. Used by
classytags.blocks.VariableBlockName
to validate it’s definition.
- get_parser_class()¶
Returns
classytags.parser.Parser
or a subclass of it.
- bootstrap()¶
An internal method to bootstrap the arguments. Returns an instance of
classytags.utils.StructuredOptions
.
- parse(parser, token):
An internal method to parse the template tag. Returns a tuple
(arguments, blocks)
.
- class classytags.core.TagMeta¶
The metaclass of
classytags.core.Tag
which ensures the tag has a name attribute by setting one based on the classes name if none is provided.
- class classytags.core.Tag(parser, token)¶
The
Tag
class is nothing other than a subclass ofdjango.template.Node
which handles argument parsing in it’s__init__()
method rather than an external function. In a normal use case you should only overridename
,options
andrender_tag()
.Note
When registering your template tag, register the class object, not an instance of it.
- name¶
The name of this tag (for use in templates). This attribute is optional and if not provided, the un-camelcase class name will be used instead. So MyTag becomes my_tag.
- options¶
An instance of
classytags.core.Options
which holds the options of this tag.
- __init__(parser, token):
Warning
This is an internal method. It is only documented here for those who would like to extend django-classy-tags.
This is where the arguments to this tag get parsed. It’s the equivalent to a compile function in Django’s standard templating system. This method does nothing else but assing the
kwargs
andblocks
attributes to the output ofoptions.parse()
with the given parser and token.
- render(context)¶
Warning
This is an internal method. It is only documented here for those who would like to extend django-classy-tags.
This method resolves the arguments to this tag against the context and then calls
render_tag()
with the context and those arguments and returns the return value of that method.
- render_tag(context[, **kwargs])¶
The method used to render this tag for a given context. kwargs is a dictionary of the (already resolved) options of this tag as well as the blocks (as nodelists) this tag parses until if any are given. This method should return a string.
classytags.exceptions
¶
This module contains the custom exceptions used by django-classy-tags.
- exception classytags.exceptions.BaseError¶
The base class for all custom excpetions, should never be raised directly.
- exception classytags.exceptions.ArgumentRequiredError(argument, tagname)¶
Gets raised if an option of a tag is required but not provided.
- exception classytags.exceptions.InvalidFlag(argname, actual_value, allowed_values, tagname)¶
Gets raised if a given value for a flag option is neither in true_values nor false_values.
- exception classytags.exceptions.BreakpointExpected(tagname, breakpoints, got)¶
Gets raised if a breakpoint was expected, but another argument was found.
- exception classytags.exceptions.TooManyArguments(tagname, extra)¶
Gets raised if too many arguments are provided for a tag.
classytags.helpers
¶
This modules contains helper classes to make building template tags even easier.
- class classytags.helpers.AsTag¶
A helper tag base class to build ‘as varname’ tags. Note that the option class still has to contain the ‘as varname’ information. This tag will use the last argument in the options class to set the value into the context.
This class implements the method
classytags.helpers.AsTag.get_value()
which gets the context and all arguments except for the varname argument as arguments. It should always return the value this tag comes up with, the class then takes care of either putting the value into the context or returns it if the varname argument is not provided.Note
You should not override the
render_tag()
method of this class.- get_value_for_context(context, **kwargs):
New in version 0.5.
Should return the value of this tag if used in the ‘as varname’ form. By default this method just calls
get_value
and returns that.You may want to use this method if you want to suppress exceptions in the ‘as varname’ case.
- get_value(context, **kwargs)¶
Should return the value of this tag. The context setting is done in the
classytags.core.Tag.render_tag()
method of this class.
- class classytags.helpers.InclusionTag¶
A helper class for writing inclusion tags (template tags which render a template).
Note
You should not override the
render_tag()
method of this class.- template¶
The template to use if
get_template()
is not overridden.
- push_context¶
New in version 0.5.2.
By default, this is
False
. If it’s set toTrue
the context will be pushed before rendering the included template, preventing context pollution.
- get_template(context, **kwargs)¶
This method should return a template (path) for this context and arguments. By default returns the value of
template
.
- get_context(context, **kwargs)¶
Should return the context (as a dictionary or an instance of
django.template.Context
or a subclass of it) to use to render the template. By default returns an empty dictionary.
classytags.parser
¶
The default argument parser lies here.
- class classytags.parser.Parser(options)¶
The default argument parser class. It get’s initialized with an instance of
classytags.utils.StructuredOptions
.- options¶
The
classytags.utils.StructuredOptions
instance given when the parser was instantiated.
- parser¶
The (template) parser used to parse this tag.
- bits¶
The split tokens.
- tagname¶
Name of this tag.
- kwargs¶
The data extracted from the bits.
- blocks¶
A dictionary holding the block nodelists.
- arguments¶
The arguments in the current breakpoint scope.
- current_argument¶
The current argument if any.
- todo¶
Remaining bits. Used for more helpful exception messages.
- parse(parser, token)¶
Parses a token stream. This is called when your template tag is parsed.
- handle_bit(bit)¶
Handle the current bit (token).
- handle_next_breakpoint(bit)¶
The current bit is the next breakpoint. Make sure the current scope can be finished successfully and shift to the next one.
- handle_breakpoints(bit)¶
The current bit is a future breakpoint, try to close all breakpoint scopes before that breakpoint and shift to it.
- parse_blocks()¶
Parses the blocks this tag wants to parse until if any are provided.
- finish()¶
After all bits have been parsed, finish all remaining breakpoint scopes.
- check_required()¶
A helper method to check if there’s any required arguments left in the current breakpoint scope. Raises a
classytags.exceptions.ArgumentRequiredError
if one is found and contributes all optional arguments tokwargs
.
classytags.utils
¶
Utility classes and methods for django-classy-tags.
- class classytags.utils.NULL¶
A pseudo type.
- class classytags.utils.TemplateConstant(value)¶
A constant pseudo template variable which always returns it’s initial value when resolved.
- literal¶
Used by the
classytags.blocks.VariableBlockName
to generate it’s final name.
- class classytags.utils.StructuredOptions(options, breakpoints)¶
A helper class to organize options.
- options¶
The arguments in this options.
- breakpoints¶
A copy of the breakpoints in this options
- blocks¶
A copy of the list of tuples (blockname, alias) of blocks of this tag.
- current_breakpoint¶
The current breakpoint.
- next_breakpoint¶
The next breakpoint (if there is any).
- shift_breakpoint()¶
Shift to the next breakpoint and update
current_breakpoint
andnext_breakpoint
.
- get_arguments()¶
Returns a copy of the arguments in the current breakpoint scope.
- class classytags.utils.ResolvableList(item)¶
A subclass of list which resolves all it’s items against a context when it’s resolve method gets called.
- classytags.utils.get_default_name(name)¶
Turns ‘CamelCase’ into ‘camel_case’.
classytags.values
¶
- class classytags.values.StringValue(var)¶
- errors¶
A dictionary holding error messages which can be caused by this value class. Defaults to an empty dictionary.
- value_on_error¶
The value to use when the validation of a input value fails in non-debug mode. Defaults to an empty string.
- var¶
The variable wrapped by this value instance.
- resolve(context)¶
Resolve
var
against context and validate it by calling theclean()
method with the resolved value.
- clean(value)¶
Validates and/or cleans a resolved value. This method should always return something. If validation fails, the
error()
helper method should be used to properly handle debug modes.
- error(value, category)¶
Handles an error in category caused by value. In debug mode this will cause a
django.template.TemplateSyntaxError
to be raised, otherwise a TemplateSyntaxWarning is called andvalue_on_error
is returned. The message to be used for both the exception and the warning will be constructed by the message inerrors
if category is in it. The value can be used as a named string formatting parameter.
- class classytags.values.StrictStringValue(var)¶
Same as
StringValue
but enforces that the value passed to it is a string (instance ofbasestring
).
- class classytags.values.IntegerValue(var)¶
Subclass of
StringValue
.- clean(value)¶
Tries to convert the value to an integer.
- class classytags.values.ListValue(value)¶
Subclass of
StringValue
andlist
.Appends the initial value to itself in initialization.
- resolve(context)¶
Resolves all items in itself against context and calls
clean()
with the list of resolved values.
- class classytags.values.DictValue(dict)¶
Subclass of
StringValue
anddict
.- resolve(context)¶
Resolves all values against context and calls
clean()
with the resolved dictionary.
Extending django-classy-tags¶
You can extend django-classy-tags by writing your own subclasses of
classytags.arguments.Argument
which behave to your needs. If that does
not cover your needs, you may also subclass classytags.core.Options
and
set a custom argument parser, which should subclass
classytags.parser.Parser
.
Creating a custom argument class¶
The most important method in this class for customization is
classytags.arguments.Argument.parse()
, so let’s have a closer look at it.
It takes exactly four arguments, which are as follows:
parser: An instance of
django.template.Parser
.token: The current token as a string.
tagname: The name of the tag being handled.
kwargs: The dictionary of already parsed arguments.
The parse method must return a boolean value:
If your method returns
True
, it means it has successfully handled the provided token. Your method has to add content to kwargs itself. The parser does not do that! When you returnTrue
, the next token will also try to get parsed by this argument’s parse method.If your method returns
False
, it means it has not handled this token and the next argument class in the stack should be used to handle this token. Usually you would returnFalse
when your argument’s name is already in kwargs. Obviously this only applies to single-value arguments.
So let’s look at the standard classytags.arguments.Argument.parse()
:
def parse(self, parser, token, tagname, kwargs):
"""
Parse a token.
"""
if self.name in kwargs:
return False
else:
kwargs[self.name] = self.parse_token(parser, token)
return True
First it checks if the name is already in kwargs. If so, return False
and
let the next argument handle this token. Otherwise do some checking if we should
resolve this token or not and add it to kwargs. Finally return True
.
You might notice the classytags.arguments.Argument.parse_token()
method
used there. This method is responsible for turning an token into a template
variable, a filter expression or any other object which allows to be resolved
against a context. The one in classytags.arguments.Argument
looks like
this:
def parse_token(self, parser, token):
if self.resolve:
return parser.compile_filter(token)
else:
return TemplateConstant(token)
Cleaning arguments¶
If all you want to do is clean arguments or enforce a certain type, you can
just change the classytags.arguments.Argument.value_class
of your
subclass of classytags.arguments.Argument
to a subclass of
classytags.values.StringValue
which implements a clean method in
which you can check the type and/or cast a type on the value. For further
information on value classes, see classytags.values
.
Custom argument parser¶
The argument parser was written with extensibility in mind. All important steps
are split into individual methods which can be overwritten. For information
about those methods, please refer to the reference about
classytags.parser.Parser
.
To use a custom parser, provide it as the parser_class
keyword argument to
classytags.core.Options
.
Note
Each time your tag gets parsed, a new instance of the parser class gets
created. This makes it safe to use self
.
Example¶
Let’s make an argument which, when resolved, returns a template.
First we need a helper class which, after resolving loads the template specified by the value:
from django.template.loader import get_template
class TemplateResolver:
def __init__(self, real):
self.real = real
def resolve(self, context):
value = self.real.resolve(context)
return get_template(value)
Now for the real argument:
from classytags.arguments import Argument
class TemplateArgument(Argument):
def parse_token(self, parser, token):
real = super().parse_token(parser, token)
return TemplateResolver(real)
Contribute to django-classy-tags¶
As with (hopefully) any open source project, contribution is very welcome.
Review the documentation¶
A great way to contribute to django-classy-tags, which requires absolutely no coding skills and can be done by anyone who knows English, is to read this documentation and inform me about any mistakes in the documentation. Also any suggestion on how to write the documentation clearer and easier to understand would be greatly appreciated.
If you use django-classy-tags yourself as a developer and found certain things in the documentation to be unclear to you or you would like to have more in depth documentation on some features, please also tell me so I can make it easier for people to use this project.
Spread the word¶
Tell people about this project! The more people use it the higher the chance the quality of this project will increase.
Improve the test suite¶
There is a handy little test framework located in the testdata folder. It allows automated testing of django-classy-tags implementation against builtin Django tags. Have a look at the existing tags and feel free to add more!
Also adding good old unit tests is a great way to help too.
Running the test suite¶
From the root folder of your django-classy-tags checkout, run ./runtests.sh
.
Write code¶
Want a feature implemented or a bug eliminated? The quickest way to get there is to write the code yourself and get it back to me.
Please note that your code must have full test coverage and full documentation coverage, otherwise it won’t be pulled into the main repository.
Changelog¶
See CHANGELOG.rst for a full list.