|
1 from django import template |
|
2 from django.contrib.admin.views.main import AdminBoundField |
|
3 from django.template import loader |
|
4 from django.utils.text import capfirst |
|
5 from django.utils.encoding import force_unicode |
|
6 from django.utils.safestring import mark_safe |
|
7 from django.utils.html import escape |
|
8 from django.db import models |
|
9 from django.db.models.fields import Field |
|
10 from django.db.models.related import BoundRelatedObject |
|
11 from django.conf import settings |
|
12 import re |
|
13 |
|
14 register = template.Library() |
|
15 |
|
16 word_re = re.compile('[A-Z][a-z]+') |
|
17 absolute_url_re = re.compile(r'^(?:http(?:s)?:/)?/', re.IGNORECASE) |
|
18 |
|
19 def class_name_to_underscored(name): |
|
20 return u'_'.join([s.lower() for s in word_re.findall(name)[:-1]]) |
|
21 |
|
22 def include_admin_script(script_path): |
|
23 """ |
|
24 Returns an HTML script element for including a script from the admin |
|
25 media url (or other location if an absolute url is given). |
|
26 |
|
27 Example usage:: |
|
28 |
|
29 {% include_admin_script "js/calendar.js" %} |
|
30 |
|
31 could return:: |
|
32 |
|
33 <script type="text/javascript" src="/media/admin/js/calendar.js"> |
|
34 """ |
|
35 if not absolute_url_re.match(script_path): |
|
36 script_path = '%s%s' % (settings.ADMIN_MEDIA_PREFIX, script_path) |
|
37 return mark_safe(u'<script type="text/javascript" src="%s"></script>' |
|
38 % script_path) |
|
39 include_admin_script = register.simple_tag(include_admin_script) |
|
40 |
|
41 def submit_row(context): |
|
42 opts = context['opts'] |
|
43 change = context['change'] |
|
44 is_popup = context['is_popup'] |
|
45 return { |
|
46 'onclick_attrib': (opts.get_ordered_objects() and change |
|
47 and 'onclick="submitOrderForm();"' or ''), |
|
48 'show_delete_link': (not is_popup and context['has_delete_permission'] |
|
49 and (change or context['show_delete'])), |
|
50 'show_save_as_new': not is_popup and change and opts.admin.save_as, |
|
51 'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']), |
|
52 'show_save_and_continue': not is_popup and context['has_change_permission'], |
|
53 'show_save': True |
|
54 } |
|
55 submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row) |
|
56 |
|
57 def field_label(bound_field): |
|
58 class_names = [] |
|
59 if isinstance(bound_field.field, models.BooleanField): |
|
60 class_names.append("vCheckboxLabel") |
|
61 colon = "" |
|
62 else: |
|
63 if not bound_field.field.blank: |
|
64 class_names.append('required') |
|
65 if not bound_field.first: |
|
66 class_names.append('inline') |
|
67 colon = ":" |
|
68 class_str = class_names and u' class="%s"' % u' '.join(class_names) or u'' |
|
69 return mark_safe(u'<label for="%s"%s>%s%s</label> ' % |
|
70 (bound_field.element_id, class_str, |
|
71 escape(force_unicode(capfirst(bound_field.field.verbose_name))), |
|
72 colon)) |
|
73 field_label = register.simple_tag(field_label) |
|
74 |
|
75 class FieldWidgetNode(template.Node): |
|
76 nodelists = {} |
|
77 default = None |
|
78 |
|
79 def __init__(self, bound_field_var): |
|
80 self.bound_field_var = template.Variable(bound_field_var) |
|
81 |
|
82 def get_nodelist(cls, klass): |
|
83 if klass not in cls.nodelists: |
|
84 try: |
|
85 field_class_name = klass.__name__ |
|
86 template_name = u"widget/%s.html" % class_name_to_underscored(field_class_name) |
|
87 nodelist = loader.get_template(template_name).nodelist |
|
88 except template.TemplateDoesNotExist: |
|
89 super_klass = bool(klass.__bases__) and klass.__bases__[0] or None |
|
90 if super_klass and super_klass != Field: |
|
91 nodelist = cls.get_nodelist(super_klass) |
|
92 else: |
|
93 if not cls.default: |
|
94 cls.default = loader.get_template("widget/default.html").nodelist |
|
95 nodelist = cls.default |
|
96 |
|
97 cls.nodelists[klass] = nodelist |
|
98 return nodelist |
|
99 else: |
|
100 return cls.nodelists[klass] |
|
101 get_nodelist = classmethod(get_nodelist) |
|
102 |
|
103 def render(self, context): |
|
104 bound_field = self.bound_field_var.resolve(context) |
|
105 |
|
106 context.push() |
|
107 context['bound_field'] = bound_field |
|
108 |
|
109 output = self.get_nodelist(bound_field.field.__class__).render(context) |
|
110 context.pop() |
|
111 return output |
|
112 |
|
113 class FieldWrapper(object): |
|
114 def __init__(self, field ): |
|
115 self.field = field |
|
116 |
|
117 def needs_header(self): |
|
118 return not isinstance(self.field, models.AutoField) |
|
119 |
|
120 def header_class_attribute(self): |
|
121 return self.field.blank and mark_safe(' class="optional"') or '' |
|
122 |
|
123 def use_raw_id_admin(self): |
|
124 return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \ |
|
125 and self.field.rel.raw_id_admin |
|
126 |
|
127 class FormFieldCollectionWrapper(object): |
|
128 def __init__(self, field_mapping, fields, index): |
|
129 self.field_mapping = field_mapping |
|
130 self.fields = fields |
|
131 self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original']) |
|
132 for field in self.fields] |
|
133 self.index = index |
|
134 |
|
135 class TabularBoundRelatedObject(BoundRelatedObject): |
|
136 def __init__(self, related_object, field_mapping, original): |
|
137 super(TabularBoundRelatedObject, self).__init__(related_object, field_mapping, original) |
|
138 self.field_wrapper_list = [FieldWrapper(field) for field in self.relation.editable_fields()] |
|
139 |
|
140 fields = self.relation.editable_fields() |
|
141 |
|
142 self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields, i) |
|
143 for (i,field_mapping) in self.field_mappings.items() ] |
|
144 self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list]) |
|
145 self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url') |
|
146 |
|
147 def template_name(self): |
|
148 return "admin/edit_inline_tabular.html" |
|
149 |
|
150 class StackedBoundRelatedObject(BoundRelatedObject): |
|
151 def __init__(self, related_object, field_mapping, original): |
|
152 super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original) |
|
153 fields = self.relation.editable_fields() |
|
154 self.field_mappings.fill() |
|
155 self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields, i) |
|
156 for (i,field_mapping) in self.field_mappings.items()] |
|
157 self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url') |
|
158 |
|
159 def template_name(self): |
|
160 return "admin/edit_inline_stacked.html" |
|
161 |
|
162 class EditInlineNode(template.Node): |
|
163 def __init__(self, rel_var): |
|
164 self.rel_var = template.Variable(rel_var) |
|
165 |
|
166 def render(self, context): |
|
167 relation = self.rel_var.resolve(context) |
|
168 context.push() |
|
169 if relation.field.rel.edit_inline == models.TABULAR: |
|
170 bound_related_object_class = TabularBoundRelatedObject |
|
171 elif relation.field.rel.edit_inline == models.STACKED: |
|
172 bound_related_object_class = StackedBoundRelatedObject |
|
173 else: |
|
174 bound_related_object_class = relation.field.rel.edit_inline |
|
175 original = context.get('original', None) |
|
176 bound_related_object = relation.bind(context['form'], original, bound_related_object_class) |
|
177 context['bound_related_object'] = bound_related_object |
|
178 t = loader.get_template(bound_related_object.template_name()) |
|
179 output = t.render(context) |
|
180 context.pop() |
|
181 return output |
|
182 |
|
183 def output_all(form_fields): |
|
184 return u''.join([force_unicode(f) for f in form_fields]) |
|
185 output_all = register.simple_tag(output_all) |
|
186 |
|
187 def auto_populated_field_script(auto_pop_fields, change = False): |
|
188 t = [] |
|
189 for field in auto_pop_fields: |
|
190 if change: |
|
191 t.append(u'document.getElementById("id_%s")._changed = true;' % field.name) |
|
192 else: |
|
193 t.append(u'document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name) |
|
194 |
|
195 add_values = u' + " " + '.join([u'document.getElementById("id_%s").value' % g for g in field.prepopulate_from]) |
|
196 for f in field.prepopulate_from: |
|
197 t.append(u'document.getElementById("id_%s").onkeyup = function() {' \ |
|
198 ' var e = document.getElementById("id_%s");' \ |
|
199 ' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % ( |
|
200 f, field.name, add_values, field.max_length)) |
|
201 return mark_safe(u''.join(t)) |
|
202 auto_populated_field_script = register.simple_tag(auto_populated_field_script) |
|
203 |
|
204 def filter_interface_script_maybe(bound_field): |
|
205 f = bound_field.field |
|
206 if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface: |
|
207 return mark_safe(u'<script type="text/javascript">addEvent(window, "load", function(e) {' \ |
|
208 ' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % ( |
|
209 f.name, escape(f.verbose_name.replace('"', '\\"')), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)) |
|
210 else: |
|
211 return '' |
|
212 filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe) |
|
213 |
|
214 def field_widget(parser, token): |
|
215 bits = token.contents.split() |
|
216 if len(bits) != 2: |
|
217 raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0] |
|
218 return FieldWidgetNode(bits[1]) |
|
219 field_widget = register.tag(field_widget) |
|
220 |
|
221 def edit_inline(parser, token): |
|
222 bits = token.contents.split() |
|
223 if len(bits) != 2: |
|
224 raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0] |
|
225 return EditInlineNode(bits[1]) |
|
226 edit_inline = register.tag(edit_inline) |
|
227 |
|
228 def admin_field_line(context, argument_val): |
|
229 if isinstance(argument_val, AdminBoundField): |
|
230 bound_fields = [argument_val] |
|
231 else: |
|
232 bound_fields = [bf for bf in argument_val] |
|
233 add = context['add'] |
|
234 change = context['change'] |
|
235 |
|
236 class_names = ['form-row'] |
|
237 for bound_field in bound_fields: |
|
238 for f in bound_field.form_fields: |
|
239 if f.errors(): |
|
240 class_names.append('errors') |
|
241 break |
|
242 |
|
243 # Assumes BooleanFields won't be stacked next to each other! |
|
244 if isinstance(bound_fields[0].field, models.BooleanField): |
|
245 class_names.append('checkbox-row') |
|
246 |
|
247 return { |
|
248 'add': context['add'], |
|
249 'change': context['change'], |
|
250 'bound_fields': bound_fields, |
|
251 'class_names': " ".join(class_names), |
|
252 } |
|
253 admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line) |