Skip to content

Commit dc38839

Browse files
committed
Add django-widget-tweaks 1.3 app to repository
- Legacy-Id: 8990
1 parent 8eff2f3 commit dc38839

5 files changed

Lines changed: 521 additions & 0 deletions

File tree

widget_tweaks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

widget_tweaks/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import re
2+
from django.template import Library, Node, Variable, TemplateSyntaxError
3+
register = Library()
4+
5+
6+
def silence_without_field(fn):
7+
def wrapped(field, attr):
8+
if not field:
9+
return ""
10+
return fn(field, attr)
11+
return wrapped
12+
13+
14+
def _process_field_attributes(field, attr, process):
15+
16+
# split attribute name and value from 'attr:value' string
17+
params = attr.split(':', 1)
18+
attribute = params[0]
19+
value = params[1] if len(params) == 2 else ''
20+
21+
# decorate field.as_widget method with updated attributes
22+
old_as_widget = field.as_widget
23+
24+
def as_widget(self, widget=None, attrs=None, only_initial=False):
25+
attrs = attrs or {}
26+
process(widget or self.field.widget, attrs, attribute, value)
27+
return old_as_widget(widget, attrs, only_initial)
28+
29+
bound_method = type(old_as_widget)
30+
try:
31+
field.as_widget = bound_method(as_widget, field, field.__class__)
32+
except TypeError: # python 3
33+
field.as_widget = bound_method(as_widget, field)
34+
return field
35+
36+
37+
@register.filter("attr")
38+
@silence_without_field
39+
def set_attr(field, attr):
40+
41+
def process(widget, attrs, attribute, value):
42+
attrs[attribute] = value
43+
44+
return _process_field_attributes(field, attr, process)
45+
46+
47+
@register.filter("add_error_attr")
48+
@silence_without_field
49+
def add_error_attr(field, attr):
50+
if hasattr(field, 'errors') and field.errors:
51+
return set_attr(field, attr)
52+
return field
53+
54+
55+
@register.filter("append_attr")
56+
@silence_without_field
57+
def append_attr(field, attr):
58+
def process(widget, attrs, attribute, value):
59+
if attrs.get(attribute):
60+
attrs[attribute] += ' ' + value
61+
elif widget.attrs.get(attribute):
62+
attrs[attribute] = widget.attrs[attribute] + ' ' + value
63+
else:
64+
attrs[attribute] = value
65+
return _process_field_attributes(field, attr, process)
66+
67+
68+
@register.filter("add_class")
69+
@silence_without_field
70+
def add_class(field, css_class):
71+
return append_attr(field, 'class:' + css_class)
72+
73+
74+
@register.filter("add_error_class")
75+
@silence_without_field
76+
def add_error_class(field, css_class):
77+
if hasattr(field, 'errors') and field.errors:
78+
return add_class(field, css_class)
79+
return field
80+
81+
82+
@register.filter("set_data")
83+
@silence_without_field
84+
def set_data(field, data):
85+
return set_attr(field, 'data-' + data)
86+
87+
88+
@register.filter(name='field_type')
89+
def field_type(field):
90+
"""
91+
Template filter that returns field class name (in lower case).
92+
E.g. if field is CharField then {{ field|field_type }} will
93+
return 'charfield'.
94+
"""
95+
if hasattr(field, 'field') and field.field:
96+
return field.field.__class__.__name__.lower()
97+
return ''
98+
99+
100+
@register.filter(name='widget_type')
101+
def widget_type(field):
102+
"""
103+
Template filter that returns field widget class name (in lower case).
104+
E.g. if field's widget is TextInput then {{ field|widget_type }} will
105+
return 'textinput'.
106+
"""
107+
if hasattr(field, 'field') and hasattr(field.field, 'widget') and field.field.widget:
108+
return field.field.widget.__class__.__name__.lower()
109+
return ''
110+
111+
112+
# ======================== render_field tag ==============================
113+
114+
ATTRIBUTE_RE = re.compile(r"""
115+
(?P<attr>
116+
[\w_-]+
117+
)
118+
(?P<sign>
119+
\+?=
120+
)
121+
(?P<value>
122+
['"]? # start quote
123+
[^"']*
124+
['"]? # end quote
125+
)
126+
""", re.VERBOSE | re.UNICODE)
127+
128+
@register.tag
129+
def render_field(parser, token):
130+
"""
131+
Render a form field using given attribute-value pairs
132+
133+
Takes form field as first argument and list of attribute-value pairs for
134+
all other arguments. Attribute-value pairs should be in the form of
135+
attribute=value or attribute="a value" for assignment and attribute+=value
136+
or attribute+="value" for appending.
137+
"""
138+
error_msg = '%r tag requires a form field followed by a list of attributes and values in the form attr="value"' % token.split_contents()[0]
139+
try:
140+
bits = token.split_contents()
141+
tag_name = bits[0]
142+
form_field = bits[1]
143+
attr_list = bits[2:]
144+
except ValueError:
145+
raise TemplateSyntaxError(error_msg)
146+
147+
form_field = parser.compile_filter(form_field)
148+
149+
set_attrs = []
150+
append_attrs = []
151+
for pair in attr_list:
152+
match = ATTRIBUTE_RE.match(pair)
153+
if not match:
154+
raise TemplateSyntaxError(error_msg + ": %s" % pair)
155+
dct = match.groupdict()
156+
attr, sign, value = dct['attr'], dct['sign'], parser.compile_filter(dct['value'])
157+
if sign == "=":
158+
set_attrs.append((attr, value))
159+
else:
160+
append_attrs.append((attr, value))
161+
162+
return FieldAttributeNode(form_field, set_attrs, append_attrs)
163+
164+
165+
class FieldAttributeNode(Node):
166+
def __init__(self, field, set_attrs, append_attrs):
167+
self.field = field
168+
self.set_attrs = set_attrs
169+
self.append_attrs = append_attrs
170+
171+
def render(self, context):
172+
bounded_field = self.field.resolve(context)
173+
field = getattr(bounded_field, 'field', None)
174+
if (getattr(bounded_field, 'errors', None) and
175+
'WIDGET_ERROR_CLASS' in context):
176+
bounded_field = append_attr(bounded_field, 'class:%s' %
177+
context['WIDGET_ERROR_CLASS'])
178+
if field and field.required and 'WIDGET_REQUIRED_CLASS' in context:
179+
bounded_field = append_attr(bounded_field, 'class:%s' %
180+
context['WIDGET_REQUIRED_CLASS'])
181+
for k, v in self.set_attrs:
182+
bounded_field = set_attr(bounded_field, '%s:%s' % (k,v.resolve(context)))
183+
for k, v in self.append_attrs:
184+
bounded_field = append_attr(bounded_field, '%s:%s' % (k,v.resolve(context)))
185+
return bounded_field

0 commit comments

Comments
 (0)