web/lib/django/contrib/admin/helpers.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 from django import forms
       
     2 from django.conf import settings
       
     3 from django.contrib.admin.util import flatten_fieldsets, lookup_field
       
     4 from django.contrib.admin.util import display_for_field, label_for_field
       
     5 from django.contrib.contenttypes.models import ContentType
       
     6 from django.core.exceptions import ObjectDoesNotExist
       
     7 from django.db.models.fields import FieldDoesNotExist
       
     8 from django.db.models.fields.related import ManyToManyRel
       
     9 from django.forms.util import flatatt
       
    10 from django.template.defaultfilters import capfirst
       
    11 from django.utils.encoding import force_unicode, smart_unicode
       
    12 from django.utils.html import escape, conditional_escape
       
    13 from django.utils.safestring import mark_safe
       
    14 from django.utils.translation import ugettext_lazy as _
       
    15 
       
    16 
       
    17 ACTION_CHECKBOX_NAME = '_selected_action'
       
    18 
       
    19 class ActionForm(forms.Form):
       
    20     action = forms.ChoiceField(label=_('Action:'))
       
    21     select_across = forms.BooleanField(label='', required=False, initial=0,
       
    22         widget=forms.HiddenInput({'class': 'select-across'}))
       
    23 
       
    24 checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False)
       
    25 
       
    26 class AdminForm(object):
       
    27     def __init__(self, form, fieldsets, prepopulated_fields, readonly_fields=None, model_admin=None):
       
    28         self.form, self.fieldsets = form, normalize_fieldsets(fieldsets)
       
    29         self.prepopulated_fields = [{
       
    30             'field': form[field_name],
       
    31             'dependencies': [form[f] for f in dependencies]
       
    32         } for field_name, dependencies in prepopulated_fields.items()]
       
    33         self.model_admin = model_admin
       
    34         if readonly_fields is None:
       
    35             readonly_fields = ()
       
    36         self.readonly_fields = readonly_fields
       
    37 
       
    38     def __iter__(self):
       
    39         for name, options in self.fieldsets:
       
    40             yield Fieldset(self.form, name,
       
    41                 readonly_fields=self.readonly_fields,
       
    42                 model_admin=self.model_admin,
       
    43                 **options
       
    44             )
       
    45 
       
    46     def first_field(self):
       
    47         try:
       
    48             fieldset_name, fieldset_options = self.fieldsets[0]
       
    49             field_name = fieldset_options['fields'][0]
       
    50             if not isinstance(field_name, basestring):
       
    51                 field_name = field_name[0]
       
    52             return self.form[field_name]
       
    53         except (KeyError, IndexError):
       
    54             pass
       
    55         try:
       
    56             return iter(self.form).next()
       
    57         except StopIteration:
       
    58             return None
       
    59 
       
    60     def _media(self):
       
    61         media = self.form.media
       
    62         for fs in self:
       
    63             media = media + fs.media
       
    64         return media
       
    65     media = property(_media)
       
    66 
       
    67 class Fieldset(object):
       
    68     def __init__(self, form, name=None, readonly_fields=(), fields=(), classes=(),
       
    69       description=None, model_admin=None):
       
    70         self.form = form
       
    71         self.name, self.fields = name, fields
       
    72         self.classes = u' '.join(classes)
       
    73         self.description = description
       
    74         self.model_admin = model_admin
       
    75         self.readonly_fields = readonly_fields
       
    76 
       
    77     def _media(self):
       
    78         if 'collapse' in self.classes:
       
    79             js = ['js/jquery.min.js', 'js/jquery.init.js', 'js/collapse.min.js']
       
    80             return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
       
    81         return forms.Media()
       
    82     media = property(_media)
       
    83 
       
    84     def __iter__(self):
       
    85         for field in self.fields:
       
    86             yield Fieldline(self.form, field, self.readonly_fields, model_admin=self.model_admin)
       
    87 
       
    88 class Fieldline(object):
       
    89     def __init__(self, form, field, readonly_fields=None, model_admin=None):
       
    90         self.form = form # A django.forms.Form instance
       
    91         if not hasattr(field, "__iter__"):
       
    92             self.fields = [field]
       
    93         else:
       
    94             self.fields = field
       
    95         self.model_admin = model_admin
       
    96         if readonly_fields is None:
       
    97             readonly_fields = ()
       
    98         self.readonly_fields = readonly_fields
       
    99 
       
   100     def __iter__(self):
       
   101         for i, field in enumerate(self.fields):
       
   102             if field in self.readonly_fields:
       
   103                 yield AdminReadonlyField(self.form, field, is_first=(i == 0),
       
   104                     model_admin=self.model_admin)
       
   105             else:
       
   106                 yield AdminField(self.form, field, is_first=(i == 0))
       
   107 
       
   108     def errors(self):
       
   109         return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields if f not in self.readonly_fields]).strip('\n'))
       
   110 
       
   111 class AdminField(object):
       
   112     def __init__(self, form, field, is_first):
       
   113         self.field = form[field] # A django.forms.BoundField instance
       
   114         self.is_first = is_first # Whether this field is first on the line
       
   115         self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput)
       
   116 
       
   117     def label_tag(self):
       
   118         classes = []
       
   119         if self.is_checkbox:
       
   120             classes.append(u'vCheckboxLabel')
       
   121             contents = force_unicode(escape(self.field.label))
       
   122         else:
       
   123             contents = force_unicode(escape(self.field.label)) + u':'
       
   124         if self.field.field.required:
       
   125             classes.append(u'required')
       
   126         if not self.is_first:
       
   127             classes.append(u'inline')
       
   128         attrs = classes and {'class': u' '.join(classes)} or {}
       
   129         return self.field.label_tag(contents=contents, attrs=attrs)
       
   130 
       
   131 class AdminReadonlyField(object):
       
   132     def __init__(self, form, field, is_first, model_admin=None):
       
   133         label = label_for_field(field, form._meta.model, model_admin)
       
   134         # Make self.field look a little bit like a field. This means that
       
   135         # {{ field.name }} must be a useful class name to identify the field.
       
   136         # For convenience, store other field-related data here too.
       
   137         if callable(field):
       
   138             class_name = field.__name__ != '<lambda>' and field.__name__ or ''
       
   139         else:
       
   140             class_name = field
       
   141         self.field = {
       
   142             'name': class_name,
       
   143             'label': label,
       
   144             'field': field,
       
   145         }
       
   146         self.form = form
       
   147         self.model_admin = model_admin
       
   148         self.is_first = is_first
       
   149         self.is_checkbox = False
       
   150         self.is_readonly = True
       
   151 
       
   152     def label_tag(self):
       
   153         attrs = {}
       
   154         if not self.is_first:
       
   155             attrs["class"] = "inline"
       
   156         label = self.field['label']
       
   157         contents = capfirst(force_unicode(escape(label))) + u":"
       
   158         return mark_safe('<label%(attrs)s>%(contents)s</label>' % {
       
   159             "attrs": flatatt(attrs),
       
   160             "contents": contents,
       
   161         })
       
   162 
       
   163     def contents(self):
       
   164         from django.contrib.admin.templatetags.admin_list import _boolean_icon
       
   165         from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
       
   166         field, obj, model_admin = self.field['field'], self.form.instance, self.model_admin
       
   167         try:
       
   168             f, attr, value = lookup_field(field, obj, model_admin)
       
   169         except (AttributeError, ValueError, ObjectDoesNotExist):
       
   170             result_repr = EMPTY_CHANGELIST_VALUE
       
   171         else:
       
   172             if f is None:
       
   173                 boolean = getattr(attr, "boolean", False)
       
   174                 if boolean:
       
   175                     result_repr = _boolean_icon(value)
       
   176                 else:
       
   177                     result_repr = smart_unicode(value)
       
   178                     if getattr(attr, "allow_tags", False):
       
   179                         result_repr = mark_safe(result_repr)
       
   180             else:
       
   181                 if value is None:
       
   182                     result_repr = EMPTY_CHANGELIST_VALUE
       
   183                 elif isinstance(f.rel, ManyToManyRel):
       
   184                     result_repr = ", ".join(map(unicode, value.all()))
       
   185                 else:
       
   186                     result_repr = display_for_field(value, f)
       
   187         return conditional_escape(result_repr)
       
   188 
       
   189 class InlineAdminFormSet(object):
       
   190     """
       
   191     A wrapper around an inline formset for use in the admin system.
       
   192     """
       
   193     def __init__(self, inline, formset, fieldsets, readonly_fields=None, model_admin=None):
       
   194         self.opts = inline
       
   195         self.formset = formset
       
   196         self.fieldsets = fieldsets
       
   197         self.model_admin = model_admin
       
   198         if readonly_fields is None:
       
   199             readonly_fields = ()
       
   200         self.readonly_fields = readonly_fields
       
   201 
       
   202     def __iter__(self):
       
   203         for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
       
   204             yield InlineAdminForm(self.formset, form, self.fieldsets,
       
   205                 self.opts.prepopulated_fields, original, self.readonly_fields,
       
   206                 model_admin=self.model_admin)
       
   207         for form in self.formset.extra_forms:
       
   208             yield InlineAdminForm(self.formset, form, self.fieldsets,
       
   209                 self.opts.prepopulated_fields, None, self.readonly_fields,
       
   210                 model_admin=self.model_admin)
       
   211         yield InlineAdminForm(self.formset, self.formset.empty_form,
       
   212             self.fieldsets, self.opts.prepopulated_fields, None,
       
   213             self.readonly_fields, model_admin=self.model_admin)
       
   214 
       
   215     def fields(self):
       
   216         fk = getattr(self.formset, "fk", None)
       
   217         for i, field in enumerate(flatten_fieldsets(self.fieldsets)):
       
   218             if fk and fk.name == field:
       
   219                 continue
       
   220             if field in self.readonly_fields:
       
   221                 yield {
       
   222                     'label': label_for_field(field, self.opts.model, self.model_admin),
       
   223                     'widget': {
       
   224                         'is_hidden': False
       
   225                     },
       
   226                     'required': False
       
   227                 }
       
   228             else:
       
   229                 yield self.formset.form.base_fields[field]
       
   230 
       
   231     def _media(self):
       
   232         media = self.opts.media + self.formset.media
       
   233         for fs in self:
       
   234             media = media + fs.media
       
   235         return media
       
   236     media = property(_media)
       
   237 
       
   238 class InlineAdminForm(AdminForm):
       
   239     """
       
   240     A wrapper around an inline form for use in the admin system.
       
   241     """
       
   242     def __init__(self, formset, form, fieldsets, prepopulated_fields, original,
       
   243       readonly_fields=None, model_admin=None):
       
   244         self.formset = formset
       
   245         self.model_admin = model_admin
       
   246         self.original = original
       
   247         if original is not None:
       
   248             self.original_content_type_id = ContentType.objects.get_for_model(original).pk
       
   249         self.show_url = original and hasattr(original, 'get_absolute_url')
       
   250         super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields,
       
   251             readonly_fields, model_admin)
       
   252 
       
   253     def __iter__(self):
       
   254         for name, options in self.fieldsets:
       
   255             yield InlineFieldset(self.formset, self.form, name,
       
   256                 self.readonly_fields, model_admin=self.model_admin, **options)
       
   257 
       
   258     def has_auto_field(self):
       
   259         if self.form._meta.model._meta.has_auto_field:
       
   260             return True
       
   261         # Also search any parents for an auto field.
       
   262         for parent in self.form._meta.model._meta.get_parent_list():
       
   263             if parent._meta.has_auto_field:
       
   264                 return True
       
   265         return False
       
   266 
       
   267     def field_count(self):
       
   268         # tabular.html uses this function for colspan value.
       
   269         num_of_fields = 0
       
   270         if self.has_auto_field():
       
   271             num_of_fields += 1
       
   272         num_of_fields += len(self.fieldsets[0][1]["fields"])
       
   273         if self.formset.can_order:
       
   274             num_of_fields += 1
       
   275         if self.formset.can_delete:
       
   276             num_of_fields += 1
       
   277         return num_of_fields
       
   278 
       
   279     def pk_field(self):
       
   280         return AdminField(self.form, self.formset._pk_field.name, False)
       
   281 
       
   282     def fk_field(self):
       
   283         fk = getattr(self.formset, "fk", None)
       
   284         if fk:
       
   285             return AdminField(self.form, fk.name, False)
       
   286         else:
       
   287             return ""
       
   288 
       
   289     def deletion_field(self):
       
   290         from django.forms.formsets import DELETION_FIELD_NAME
       
   291         return AdminField(self.form, DELETION_FIELD_NAME, False)
       
   292 
       
   293     def ordering_field(self):
       
   294         from django.forms.formsets import ORDERING_FIELD_NAME
       
   295         return AdminField(self.form, ORDERING_FIELD_NAME, False)
       
   296 
       
   297 class InlineFieldset(Fieldset):
       
   298     def __init__(self, formset, *args, **kwargs):
       
   299         self.formset = formset
       
   300         super(InlineFieldset, self).__init__(*args, **kwargs)
       
   301 
       
   302     def __iter__(self):
       
   303         fk = getattr(self.formset, "fk", None)
       
   304         for field in self.fields:
       
   305             if fk and fk.name == field:
       
   306                 continue
       
   307             yield Fieldline(self.form, field, self.readonly_fields,
       
   308                 model_admin=self.model_admin)
       
   309 
       
   310 class AdminErrorList(forms.util.ErrorList):
       
   311     """
       
   312     Stores all errors for the form/formsets in an add/change stage view.
       
   313     """
       
   314     def __init__(self, form, inline_formsets):
       
   315         if form.is_bound:
       
   316             self.extend(form.errors.values())
       
   317             for inline_formset in inline_formsets:
       
   318                 self.extend(inline_formset.non_form_errors())
       
   319                 for errors_in_inline_form in inline_formset.errors:
       
   320                     self.extend(errors_in_inline_form.values())
       
   321 
       
   322 def normalize_fieldsets(fieldsets):
       
   323     """
       
   324     Make sure the keys in fieldset dictionaries are strings. Returns the
       
   325     normalized data.
       
   326     """
       
   327     result = []
       
   328     for name, options in fieldsets:
       
   329         result.append((name, normalize_dictionary(options)))
       
   330     return result
       
   331 
       
   332 def normalize_dictionary(data_dict):
       
   333     """
       
   334     Converts all the keys in "data_dict" to strings. The keys must be
       
   335     convertible using str().
       
   336     """
       
   337     for key, value in data_dict.items():
       
   338         if not isinstance(key, str):
       
   339             del data_dict[key]
       
   340             data_dict[str(key)] = value
       
   341     return data_dict