├── .gitignore
├── MANIFEST.in
├── README.rst
├── django_widgets
├── __init__.py
├── base.py
├── loading.py
├── settings.py
└── templatetags
│ ├── __init__.py
│ └── widgets.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
3 | *.egg-info
4 | build
5 | dist
6 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | django-widgets
2 | ==============
3 |
4 | An alternative for templatetags. Based on http://code.google.com/p/django-widgets/
5 |
6 | Background
7 | ----------
8 |
9 | Django template tags are great, but writing renderes and compiler functions is
10 | often boring (ie. parsing options).
11 |
12 | Common usage of widgets is similar to ``{% with %}..{% include %}`` pair, but
13 | provides easy-to-use interface for more complex view-logic code.
14 |
15 | Major features are:
16 |
17 | - class-based style
18 | - widgets registry with autodiscovering.
19 |
20 |
21 | Django-widgets provides two useful templatetags with option parsers:
22 |
23 | - **include_widget**
24 |
25 | Simply includes widget instance found in registry by name, calls widget`s
26 | ``render()`` method with provided optional value and configuration.
27 |
28 | Syntax:
29 |
30 | ``{% include_widget widget_name [value] [option1=value1, [option2=value2, ...]] %}``
31 |
32 | - ``value``: optional value passed as ``value`` in ``get_context()`` and ``render()`` methods
33 | - ``opt1=val1``: dictionary key-value pairs passed as ``options`` in ``get_context()`` and ``render()``
34 |
35 |
36 |
37 | - **widget**
38 |
39 | Same as include_widget tag, but template source is taken from tag content
40 | instead of widget`s default template.
41 |
42 | Everything within ``{% widget %}`` and ``{% endwidget %}`` is used as template source.
43 | Also this template tag does **NOT** call widget`s ``render()`` method, but
44 | ``get_context()`` instead.
45 |
46 |
47 | Hints:
48 |
49 | - Context of your view will be unchanged.
50 | - Context of widget contains all view variables, similar to ``{% with %}`` tag.
51 | **No more hacks** like ``{{ settings.MEDIA_URL }}`` nor ``{% get_media_url %}`` :)
52 |
53 |
54 | Defining a Widget
55 | -----------------
56 |
57 | Widgets should extend from ``django_widgets.base.Widget`` class.
58 | Every widget, placed in **widgets.py** module of your app,
59 | is automatically registered by name (from ``class.__name__``)
60 |
61 | Simplest "hello world" widget example (place it in ``yourapp/widgets.py`` module):
62 |
63 | ::
64 |
65 | from django_widgets import Widget
66 |
67 | class HelloWorld(Widget):
68 | def render(self, context, value=None, options=None):
69 | return u'Hello world!'
70 |
71 |
72 | Calling from template:
73 |
74 | ::
75 |
76 | {% load widgets %}
77 |
78 |
{% include_widget HelloWorld %}
79 |
80 |
81 | Base Widget class has render() method which uses class-property
82 | **template** for rendering. But if not set ``render()`` raises
83 | NotImplementedError.
84 |
85 |
86 | Example of widget with custom template:
87 |
88 | ::
89 |
90 | from django_widgets import Widget
91 | from catalog import Category
92 |
93 | class CategoryTree(Widget):
94 | template = 'catalog/category_tree_widget.html'
95 |
96 | def get_context(self, root_category, options, context=None):
97 | return {
98 | 'tree': Category.objects.get_tree_for(root_category),
99 | 'root_category': root_category,
100 | 'max_level': options.get('max_level', 3),
101 | }
102 |
103 |
104 |
105 | In template:
106 |
107 | ::
108 |
109 | {% load widgets %}
110 |
111 | {% include_widget CategoryTree max_level=1 %}
112 |
113 |
114 | The ``catalog/category_tree_widget.html`` template will be used for
115 | rendering tree of categories.
116 |
117 |
118 | Or use inline tempalte with ``{% widget %}`` tag:
119 |
120 |
121 | ::
122 |
123 | {% load widgets %}
124 |
125 | {% widget CategoryTree max_level=1 %}
126 |
127 | ... widget template ...
128 |
129 | {% endwidget %}
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/django_widgets/__init__.py:
--------------------------------------------------------------------------------
1 | from loading import autodiscover
2 | from base import Widget
3 |
4 | autodiscover()
5 |
--------------------------------------------------------------------------------
/django_widgets/base.py:
--------------------------------------------------------------------------------
1 | """
2 | django widgets base module
3 |
4 | author: Marcin Nowak
5 | license: BSD
6 | """
7 |
8 | from django.forms.widgets import MediaDefiningClass
9 | from django.forms.widgets import Widget as BaseWidget
10 | from django.template.loader import get_template
11 | from loading import registry
12 |
13 | def context_wrapper(wrapped_method, context):
14 | """
15 | This decorator prepares args and kwargs for wrapped method.
16 | Backward compatibility is achieved by inspecting method args.
17 |
18 | Background:
19 | Sometimes access to template Context is needed within widget.
20 | render() method has context argument, but get_context() not.
21 |
22 | get_context() args are inspected and context is set as keyword
23 | argument, when `context` argument exist in method`s declaration.
24 | """
25 | import inspect
26 | kwargs = {}
27 |
28 | _ctxargs = inspect.getargspec(wrapped_method)[0] # Python2.5 compatibility
29 | if 'context' in _ctxargs:
30 | kwargs['context'] = context
31 |
32 | def wrapper(widget, *args):
33 | return wrapped_method(widget, *args, **kwargs)
34 |
35 | return wrapper
36 |
37 |
38 | class WidgetMeta(MediaDefiningClass):
39 | """
40 | initializes widget classes
41 | automatically adds widget instance into registry
42 | """
43 |
44 | def __init__(mcs, name, bases, attrs):
45 | MediaDefiningClass.__init__(mcs, name, bases, attrs)
46 | if 'template' not in attrs:
47 | mcs.template = None
48 | mcs.template_instance = None
49 | if name is not 'Widget':
50 | registry.register(name, mcs())
51 |
52 |
53 | class Widget(BaseWidget):
54 | """
55 | base widget class
56 | """
57 | __metaclass__ = WidgetMeta
58 | template = None
59 |
60 | def __init__(self, *args, **kwargs):
61 | super(BaseWidget, self).__init__(*args, **kwargs)
62 | self.template_instance = None
63 |
64 | def get_context(self, value, options):
65 | """
66 | returns context dictionary
67 | output my be customized by options dict
68 | """
69 | return {}
70 |
71 | def render(self, context, value=None, attrs=None):
72 | """
73 | main render method
74 | uses "template" class property for rendering
75 | or needs to be overriden for custom non-template widgets
76 | """
77 | if not self.template_instance:
78 | if not self.template:
79 | raise RuntimeError('Abstract method Widget.render()\
80 | is not implemented')
81 | self.template_instance = get_template(self.template)
82 |
83 | context.push()
84 | context.update(context_wrapper(self.get_context, context)(
85 | value, attrs or {}))
86 | result = self.template_instance.render(context)
87 | context.pop()
88 | return result
89 |
90 |
--------------------------------------------------------------------------------
/django_widgets/loading.py:
--------------------------------------------------------------------------------
1 | """
2 | widgets cache module
3 | """
4 |
5 | class WidgetsCache(object):
6 | """
7 | Widgets registry class
8 | contains all registered widget instances
9 | """
10 |
11 | def __init__(self):
12 | """
13 | initialize widgets cache
14 | """
15 | self.widgets = {}
16 |
17 | def register(self, name, widget):
18 | """
19 | registering widget instance using custom name
20 | """
21 | if self.widgets.has_key(name):
22 | raise KeyError('Widget "%s" is already registered' % name)
23 | self.widgets[name] = widget
24 |
25 | def unregister(self, widget):
26 | if isinstance(widget, (str, unicode)):
27 | self.widgets.pop(str(widget))
28 | for k, v in self.widgets.items():
29 | if isinstance(v, widget):
30 | self.widgets.pop(k)
31 |
32 | def get(self, name):
33 | """
34 | returns widget instance by name
35 | """
36 | return self.widgets[name]
37 |
38 |
39 | def autodiscover():
40 | from django.conf import settings
41 | for app in settings.INSTALLED_APPS:
42 | # Just loading the module will do the trick
43 | __import__(app, {}, {}, ['widgets'])
44 |
45 |
46 | registry = WidgetsCache()
47 |
--------------------------------------------------------------------------------
/django_widgets/settings.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
--------------------------------------------------------------------------------
/django_widgets/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcinn/django-widgets/1fbacae615d506b27d63a2f062fecd413271fa76/django_widgets/templatetags/__init__.py
--------------------------------------------------------------------------------
/django_widgets/templatetags/widgets.py:
--------------------------------------------------------------------------------
1 | """
2 | django-widgets template tags
3 | """
4 |
5 | from django.template import Library, Node, TemplateSyntaxError
6 | from django_widgets.loading import registry
7 | from django_widgets.base import context_wrapper
8 |
9 | register = Library()
10 |
11 |
12 | def _parse_args(bits, parser):
13 | if len(bits) < 2:
14 | raise TemplateSyntaxError("'%s' takes at least one argument"
15 | " (registered widget name)" % bits[0])
16 | value = None
17 | if len(bits)>2 and not '=' in bits[2]:
18 | # there is a value arg
19 | value = parser.compile_filter(bits[2])
20 | return (bits[1], value)
21 |
22 |
23 | def _parse_options(bits, parser):
24 | options = {}
25 | opts_arg = []
26 | if len(bits) > 2:
27 | if '=' in bits[2]:
28 | # there is no value arg provided
29 | bits = iter(bits[2:])
30 | else:
31 | # skip value arg
32 | bits = iter(bits[3:])
33 |
34 | for bit in bits:
35 | if '=' in bit:
36 | k, v = bit.split('=', 1)
37 | k = k.strip()
38 | options[k] = parser.compile_filter(v)
39 | elif bit:
40 | opts_arg.append(bit)
41 | return (options, opts_arg,)
42 |
43 |
44 | class IncludeWidgetNode(Node):
45 | def __init__(self, widget_name, value, opts_arg, options):
46 | self.widget_name = widget_name
47 | self.value = value
48 | self.options = options
49 | self.opts_arg = opts_arg
50 |
51 | def render(self, context):
52 | resolved_options = dict(zip(self.options.keys(), [self.options[v].resolve(context) for v in self.options]))
53 | if self.opts_arg:
54 | resolved_options.update(self.opts_arg.resolve(context))
55 | return registry.get(self.widget_name).render(context,
56 | self.value.resolve(context) if self.value else None,
57 | resolved_options)
58 |
59 |
60 | class WidgetNode(Node):
61 | def __init__(self, widget_name, value, opts_arg, options, nodelist):
62 | self.widget_name = widget_name
63 | self.value = value
64 | self.options = options
65 | self.opts_arg = opts_arg
66 | self.nodelist = nodelist
67 |
68 | def render(self, context):
69 | resolved_options = dict(zip(self.options.keys(),
70 | [self.options[v].resolve(context) for v in self.options]))
71 | if self.opts_arg:
72 | # create dictionary from arguments (all values are set to True)
73 | resolved_options.update(dict.fromkeys(self.opts_arg, True))
74 | widget = registry.get(self.widget_name)
75 |
76 | ctx = context_wrapper(widget.get_context, context)(
77 | self.value.resolve(context) if self.value else None,
78 | resolved_options)
79 |
80 | context.update(ctx)
81 | output = self.nodelist.render(context)
82 | context.pop()
83 | return output
84 |
85 | class WidgetMediaNode(Node):
86 | def __init__(self, widget_name):
87 | self.widget_name = widget_name
88 |
89 | def render(self, context):
90 | widget = registry.get(self.widget_name)
91 | if hasattr(widget, 'media'):
92 | return widget.media
93 |
94 |
95 | @register.tag(name='include_widget')
96 | def include_widget(parser, token):
97 | bits = token.split_contents()
98 | name, value = _parse_args(bits, parser)
99 | options, opts_arg = _parse_options(bits, parser)
100 | return IncludeWidgetNode(name, value, opts_arg, options)
101 |
102 |
103 | @register.tag(name='widget')
104 | def widget(parser, token):
105 | bits = token.split_contents()
106 | name, value = _parse_args(bits, parser)
107 | options, opts_arg = _parse_options(bits, parser)
108 | nodelist = parser.parse(('endwidget',))
109 | parser.delete_first_token()
110 | return WidgetNode(name, value, opts_arg, options, nodelist)
111 |
112 | @register.tag(name='widget_media')
113 | def widget_media(parser, token):
114 | bits = token.split_contents()
115 | if len(bits) != 2:
116 | raise TemplateSyntaxError("'%s' takes only one argument"
117 | " (registered widget name)" % bits[0])
118 | name = bits[1]
119 | return WidgetMediaNode(name)
120 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name='django-widgets',
5 | version='1.0',
6 | packages=find_packages(),
7 | include_package_data=True,
8 |
9 | description='An alternative for templatetags.',
10 | maintainer='Marcin Nowak',
11 | maintainer_email='marcin.j.nowak@gmail.com',
12 | url='https://github.com/marcinn/django-widgets',
13 | classifiers=[
14 | 'Framework :: Django',
15 | 'Intended Audience :: Developers',
16 | 'License :: OSI Approved :: BSD License',
17 | 'Operating System :: OS Independent',
18 | 'Programming Language :: Python :: 2.5',
19 | 'Programming Language :: Python :: 2.6',
20 | ],
21 | )
22 |
--------------------------------------------------------------------------------