2008-07-19 07:54:34 +08:00
|
|
|
"""
|
|
|
|
Form Widget classes specific to the Django admin site.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import copy
|
|
|
|
|
2008-07-19 09:22:26 +08:00
|
|
|
from django import forms
|
|
|
|
from django.forms.widgets import RadioFieldRenderer
|
|
|
|
from django.forms.util import flatatt
|
2009-09-11 17:42:17 +08:00
|
|
|
from django.utils.html import escape
|
2008-07-22 11:24:09 +08:00
|
|
|
from django.utils.text import truncate_words
|
2008-07-19 07:54:34 +08:00
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from django.utils.encoding import force_unicode
|
|
|
|
from django.conf import settings
|
2009-03-31 07:16:34 +08:00
|
|
|
from django.core.urlresolvers import reverse, NoReverseMatch
|
2008-07-19 07:54:34 +08:00
|
|
|
|
|
|
|
class FilteredSelectMultiple(forms.SelectMultiple):
|
|
|
|
"""
|
|
|
|
A SelectMultiple with a JavaScript filter interface.
|
|
|
|
|
2008-09-01 01:20:43 +08:00
|
|
|
Note that the resulting JavaScript assumes that the jsi18n
|
|
|
|
catalog has been loaded in the page
|
2008-07-19 07:54:34 +08:00
|
|
|
"""
|
2008-09-01 01:20:43 +08:00
|
|
|
class Media:
|
|
|
|
js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js",
|
|
|
|
settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js",
|
|
|
|
settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js")
|
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
|
|
|
|
self.verbose_name = verbose_name
|
|
|
|
self.is_stacked = is_stacked
|
|
|
|
super(FilteredSelectMultiple, self).__init__(attrs, choices)
|
|
|
|
|
|
|
|
def render(self, name, value, attrs=None, choices=()):
|
|
|
|
output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
|
|
|
|
output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
|
|
|
|
# TODO: "id_" is hard-coded here. This should instead use the correct
|
|
|
|
# API to determine the ID dynamically.
|
|
|
|
output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \
|
|
|
|
(name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
|
|
|
|
return mark_safe(u''.join(output))
|
|
|
|
|
|
|
|
class AdminDateWidget(forms.TextInput):
|
|
|
|
class Media:
|
2008-09-02 06:43:38 +08:00
|
|
|
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
|
2008-07-19 07:54:34 +08:00
|
|
|
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
|
2008-09-02 06:43:38 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def __init__(self, attrs={}):
|
|
|
|
super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
|
|
|
|
|
|
|
|
class AdminTimeWidget(forms.TextInput):
|
|
|
|
class Media:
|
2008-09-02 06:43:38 +08:00
|
|
|
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
|
2008-07-19 07:54:34 +08:00
|
|
|
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
|
|
|
|
|
|
|
|
def __init__(self, attrs={}):
|
|
|
|
super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
|
2008-09-02 06:43:38 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
class AdminSplitDateTime(forms.SplitDateTimeWidget):
|
|
|
|
"""
|
|
|
|
A SplitDateTime Widget that has some admin-specific styling.
|
|
|
|
"""
|
|
|
|
def __init__(self, attrs=None):
|
|
|
|
widgets = [AdminDateWidget, AdminTimeWidget]
|
|
|
|
# Note that we're calling MultiWidget, not SplitDateTimeWidget, because
|
|
|
|
# we want to define widgets.
|
|
|
|
forms.MultiWidget.__init__(self, widgets, attrs)
|
|
|
|
|
|
|
|
def format_output(self, rendered_widgets):
|
|
|
|
return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \
|
|
|
|
(_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1]))
|
|
|
|
|
|
|
|
class AdminRadioFieldRenderer(RadioFieldRenderer):
|
|
|
|
def render(self):
|
|
|
|
"""Outputs a <ul> for this set of radio fields."""
|
|
|
|
return mark_safe(u'<ul%s>\n%s\n</ul>' % (
|
|
|
|
flatatt(self.attrs),
|
|
|
|
u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self]))
|
|
|
|
)
|
|
|
|
|
|
|
|
class AdminRadioSelect(forms.RadioSelect):
|
|
|
|
renderer = AdminRadioFieldRenderer
|
|
|
|
|
|
|
|
class AdminFileWidget(forms.FileInput):
|
|
|
|
"""
|
|
|
|
A FileField Widget that shows its current value if it has one.
|
|
|
|
"""
|
|
|
|
def __init__(self, attrs={}):
|
|
|
|
super(AdminFileWidget, self).__init__(attrs)
|
2008-09-02 06:43:38 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def render(self, name, value, attrs=None):
|
|
|
|
output = []
|
2008-08-10 09:07:40 +08:00
|
|
|
if value and hasattr(value, "url"):
|
2008-08-09 04:59:02 +08:00
|
|
|
output.append('%s <a target="_blank" href="%s">%s</a> <br />%s ' % \
|
|
|
|
(_('Currently:'), value.url, value, _('Change:')))
|
2008-07-19 07:54:34 +08:00
|
|
|
output.append(super(AdminFileWidget, self).render(name, value, attrs))
|
|
|
|
return mark_safe(u''.join(output))
|
|
|
|
|
|
|
|
class ForeignKeyRawIdWidget(forms.TextInput):
|
|
|
|
"""
|
|
|
|
A Widget for displaying ForeignKeys in the "raw_id" interface rather than
|
|
|
|
in a <select> box.
|
|
|
|
"""
|
|
|
|
def __init__(self, rel, attrs=None):
|
|
|
|
self.rel = rel
|
|
|
|
super(ForeignKeyRawIdWidget, self).__init__(attrs)
|
|
|
|
|
|
|
|
def render(self, name, value, attrs=None):
|
2008-09-04 05:16:05 +08:00
|
|
|
if attrs is None:
|
|
|
|
attrs = {}
|
2008-07-19 07:54:34 +08:00
|
|
|
related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
|
2008-09-02 14:10:14 +08:00
|
|
|
params = self.url_parameters()
|
|
|
|
if params:
|
|
|
|
url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
|
|
|
|
else:
|
|
|
|
url = ''
|
2008-07-19 07:54:34 +08:00
|
|
|
if not attrs.has_key('class'):
|
2008-09-08 13:45:17 +08:00
|
|
|
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
|
2008-07-19 07:54:34 +08:00
|
|
|
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
|
|
|
|
# TODO: "id_" is hard-coded here. This should instead use the correct
|
|
|
|
# API to determine the ID dynamically.
|
|
|
|
output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
|
|
|
|
(related_url, url, name))
|
2008-08-29 04:17:31 +08:00
|
|
|
output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
|
2008-07-19 07:54:34 +08:00
|
|
|
if value:
|
|
|
|
output.append(self.label_for_value(value))
|
|
|
|
return mark_safe(u''.join(output))
|
2009-07-17 00:16:13 +08:00
|
|
|
|
2008-09-02 14:10:14 +08:00
|
|
|
def base_url_parameters(self):
|
|
|
|
params = {}
|
|
|
|
if self.rel.limit_choices_to:
|
2008-09-03 02:57:10 +08:00
|
|
|
items = []
|
|
|
|
for k, v in self.rel.limit_choices_to.items():
|
|
|
|
if isinstance(v, list):
|
2008-11-27 05:21:43 +08:00
|
|
|
v = ','.join([str(x) for x in v])
|
2008-09-03 02:57:10 +08:00
|
|
|
else:
|
|
|
|
v = str(v)
|
2008-11-27 05:21:43 +08:00
|
|
|
items.append((k, v))
|
2008-09-03 02:57:10 +08:00
|
|
|
params.update(dict(items))
|
2009-07-17 00:16:13 +08:00
|
|
|
return params
|
|
|
|
|
2008-09-02 14:10:14 +08:00
|
|
|
def url_parameters(self):
|
|
|
|
from django.contrib.admin.views.main import TO_FIELD_VAR
|
|
|
|
params = self.base_url_parameters()
|
|
|
|
params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
|
|
|
|
return params
|
2009-07-17 00:16:13 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def label_for_value(self, value):
|
2008-09-02 06:43:38 +08:00
|
|
|
key = self.rel.get_related_field().name
|
2008-11-15 10:20:00 +08:00
|
|
|
obj = self.rel.to._default_manager.get(**{key: value})
|
2009-09-11 17:42:17 +08:00
|
|
|
return ' <strong>%s</strong>' % escape(truncate_words(obj, 14))
|
2008-09-02 06:43:38 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
|
|
|
"""
|
|
|
|
A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
|
|
|
|
in a <select multiple> box.
|
|
|
|
"""
|
|
|
|
def __init__(self, rel, attrs=None):
|
|
|
|
super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
|
2008-09-02 06:43:38 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def render(self, name, value, attrs=None):
|
|
|
|
attrs['class'] = 'vManyToManyRawIdAdminField'
|
|
|
|
if value:
|
|
|
|
value = ','.join([str(v) for v in value])
|
|
|
|
else:
|
|
|
|
value = ''
|
|
|
|
return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
|
2009-07-17 00:16:13 +08:00
|
|
|
|
2008-09-02 14:10:14 +08:00
|
|
|
def url_parameters(self):
|
|
|
|
return self.base_url_parameters()
|
2009-07-17 00:16:13 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def label_for_value(self, value):
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
|
|
value = data.get(name, None)
|
|
|
|
if value and ',' in value:
|
|
|
|
return data[name].split(',')
|
|
|
|
if value:
|
|
|
|
return [value]
|
|
|
|
return None
|
2008-09-02 06:43:38 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def _has_changed(self, initial, data):
|
|
|
|
if initial is None:
|
|
|
|
initial = []
|
|
|
|
if data is None:
|
|
|
|
data = []
|
|
|
|
if len(initial) != len(data):
|
|
|
|
return True
|
|
|
|
for pk1, pk2 in zip(initial, data):
|
|
|
|
if force_unicode(pk1) != force_unicode(pk2):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
class RelatedFieldWidgetWrapper(forms.Widget):
|
|
|
|
"""
|
|
|
|
This class is a wrapper to a given widget to add the add icon for the
|
|
|
|
admin interface.
|
|
|
|
"""
|
|
|
|
def __init__(self, widget, rel, admin_site):
|
|
|
|
self.is_hidden = widget.is_hidden
|
|
|
|
self.needs_multipart_form = widget.needs_multipart_form
|
|
|
|
self.attrs = widget.attrs
|
|
|
|
self.choices = widget.choices
|
|
|
|
self.widget = widget
|
|
|
|
self.rel = rel
|
|
|
|
# so we can check if the related object is registered with this AdminSite
|
|
|
|
self.admin_site = admin_site
|
|
|
|
|
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
obj = copy.copy(self)
|
|
|
|
obj.widget = copy.deepcopy(self.widget, memo)
|
|
|
|
obj.attrs = self.widget.attrs
|
|
|
|
memo[id(self)] = obj
|
|
|
|
return obj
|
|
|
|
|
2008-09-01 01:20:43 +08:00
|
|
|
def _media(self):
|
|
|
|
return self.widget.media
|
|
|
|
media = property(_media)
|
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def render(self, name, value, *args, **kwargs):
|
|
|
|
rel_to = self.rel.to
|
2009-03-31 07:16:34 +08:00
|
|
|
info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
|
|
|
|
try:
|
2009-07-17 00:16:13 +08:00
|
|
|
related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
|
2009-03-31 07:16:34 +08:00
|
|
|
except NoReverseMatch:
|
2009-07-23 22:31:33 +08:00
|
|
|
info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower())
|
|
|
|
related_url = '%s%s/%s/add/' % info
|
2008-07-19 07:54:34 +08:00
|
|
|
self.widget.choices = self.choices
|
|
|
|
output = [self.widget.render(name, value, *args, **kwargs)]
|
|
|
|
if rel_to in self.admin_site._registry: # If the related object has an admin interface:
|
|
|
|
# TODO: "id_" is hard-coded here. This should instead use the correct
|
|
|
|
# API to determine the ID dynamically.
|
2009-03-31 07:16:34 +08:00
|
|
|
output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
|
2008-07-19 07:54:34 +08:00
|
|
|
(related_url, name))
|
2008-08-29 04:17:31 +08:00
|
|
|
output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
|
2008-07-19 07:54:34 +08:00
|
|
|
return mark_safe(u''.join(output))
|
|
|
|
|
|
|
|
def build_attrs(self, extra_attrs=None, **kwargs):
|
|
|
|
"Helper function for building an attribute dictionary."
|
|
|
|
self.attrs = self.widget.build_attrs(extra_attrs=None, **kwargs)
|
|
|
|
return self.attrs
|
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
|
|
return self.widget.value_from_datadict(data, files, name)
|
|
|
|
|
|
|
|
def _has_changed(self, initial, data):
|
|
|
|
return self.widget._has_changed(initial, data)
|
|
|
|
|
|
|
|
def id_for_label(self, id_):
|
|
|
|
return self.widget.id_for_label(id_)
|
2008-08-09 03:54:34 +08:00
|
|
|
|
|
|
|
class AdminTextareaWidget(forms.Textarea):
|
2008-08-09 05:27:03 +08:00
|
|
|
def __init__(self, attrs=None):
|
|
|
|
final_attrs = {'class': 'vLargeTextField'}
|
|
|
|
if attrs is not None:
|
|
|
|
final_attrs.update(attrs)
|
2008-08-09 05:33:55 +08:00
|
|
|
super(AdminTextareaWidget, self).__init__(attrs=final_attrs)
|
2008-08-09 03:54:34 +08:00
|
|
|
|
|
|
|
class AdminTextInputWidget(forms.TextInput):
|
2008-08-09 05:27:03 +08:00
|
|
|
def __init__(self, attrs=None):
|
|
|
|
final_attrs = {'class': 'vTextField'}
|
|
|
|
if attrs is not None:
|
|
|
|
final_attrs.update(attrs)
|
|
|
|
super(AdminTextInputWidget, self).__init__(attrs=final_attrs)
|
2008-08-09 03:54:34 +08:00
|
|
|
|
|
|
|
class AdminURLFieldWidget(forms.TextInput):
|
2008-08-09 05:27:03 +08:00
|
|
|
def __init__(self, attrs=None):
|
|
|
|
final_attrs = {'class': 'vURLField'}
|
|
|
|
if attrs is not None:
|
|
|
|
final_attrs.update(attrs)
|
|
|
|
super(AdminURLFieldWidget, self).__init__(attrs=final_attrs)
|
2008-08-09 03:54:34 +08:00
|
|
|
|
|
|
|
class AdminIntegerFieldWidget(forms.TextInput):
|
2008-08-09 05:27:03 +08:00
|
|
|
def __init__(self, attrs=None):
|
|
|
|
final_attrs = {'class': 'vIntegerField'}
|
|
|
|
if attrs is not None:
|
|
|
|
final_attrs.update(attrs)
|
|
|
|
super(AdminIntegerFieldWidget, self).__init__(attrs=final_attrs)
|
2008-08-29 04:58:10 +08:00
|
|
|
|
|
|
|
class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput):
|
|
|
|
def __init__(self, attrs=None):
|
|
|
|
final_attrs = {'class': 'vCommaSeparatedIntegerField'}
|
|
|
|
if attrs is not None:
|
|
|
|
final_attrs.update(attrs)
|
|
|
|
super(AdminCommaSeparatedIntegerFieldWidget, self).__init__(attrs=final_attrs)
|