diff --git a/django/bin/validate.py b/django/bin/validate.py
index 220c173edc..9e59fe7823 100644
--- a/django/bin/validate.py
+++ b/django/bin/validate.py
@@ -16,19 +16,19 @@ def validate_class(klass):
assert isinstance(f.rel, meta.ManyToMany), \
"ManyToManyField %s should have 'rel' set to a ManyToMany instance." % f.name
# Inline related objects.
- for rel_opts, rel_field in opts.get_inline_related_objects():
- assert len([f for f in rel_opts.fields if f.core]) > 0, \
+ for related in opts.get_followed_related_objects():
+ assert len([f for f in related.opts.fields if f.core]) > 0, \
"At least one field in %s should have core=True, because it's being edited inline by %s." % \
- (rel_opts.object_name, opts.object_name)
+ (related.opts.object_name, opts.object_name)
# All related objects.
related_apps_seen = []
- for rel_opts, rel_field in opts.get_all_related_objects():
- if rel_opts in related_apps_seen:
- assert rel_field.rel.related_name is not None, \
+ for related in opts.get_all_related_objects():
+ if related.opts in related_apps_seen:
+ assert related.field.rel.related_name is not None, \
"Relationship in field %s.%s needs to set 'related_name' because more than one" \
" %s object is referenced in %s." % \
- (rel_opts.object_name, rel_field.name, opts.object_name, rel_opts.object_name)
- related_apps_seen.append(rel_opts)
+ (related.opts.object_name, related.field.name, opts.object_name, rel_opts.object_name)
+ related_apps_seen.append(related.opts)
# Etc.
if opts.admin is not None:
assert opts.admin.ordering or opts.ordering, \
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py
new file mode 100644
index 0000000000..31a6ba37c9
--- /dev/null
+++ b/django/contrib/admin/filterspecs.py
@@ -0,0 +1,152 @@
+"""
+FilterSpec encapsulates the logic for displaying filters in the Django admin.
+Filters are specified in models with the "list_filter" option.
+
+Each filter subclass knows how to display a filter for a field that passes a
+certain test -- e.g. being a DateField or ForeignKey.
+"""
+
+from django.core import meta
+import datetime
+
+class FilterSpec(object):
+ filter_specs = []
+ def __init__(self, f, request, params):
+ self.field = f
+ self.params = params
+
+ def register(cls, test, factory):
+ cls.filter_specs.append( (test, factory) )
+ register = classmethod(register)
+
+ def create(cls, f, request, params):
+ for test, factory in cls.filter_specs:
+ if test(f):
+ return factory(f, request, params)
+ create = classmethod(create)
+
+ def has_output(self):
+ return True
+
+ def choices(self, cl):
+ raise NotImplementedError()
+
+ def title(self):
+ return self.field.verbose_name
+
+ def output(self, cl):
+ t = []
+ if self.has_output():
+ t.append(_('
By %s:
\n
\n') % self.title())
+
+ for choice in self.choices(cl):
+ t.append('
{% endblock %}
-{% block title %}Documentation bookmarklets{% endblock %}
+{% block title %}{% trans "Documentation bookmarklets" %}{% endblock %}
{% block content %}
+{% blocktrans %}
To install bookmarklets, drag the link to your bookmarks
toolbar, or right-click the link and add it to your bookmarks. Now you can
select the bookmarklet from any page in the site. Note that some of these
bookmarklets require you to be viewing the site from a computer designated
as "internal" (talk to your system administrator if you aren't sure if
your computer is "internal").
\n' % \
- ((lookup_val is None and ' class="selected"' or ''),
- get_query_string(params, {}, [lookup_kwarg])))
- for val in lookup_choices:
- pk_val = getattr(val, f.rel.to.pk.attname)
- filter_template.append('
\n' % \
- ((lookup_val is None and ' class="selected"' or ''),
- get_query_string(params, {}, [lookup_kwarg])))
- for k, v in f.choices:
- filter_template.append('
\n' % \
- (((lookup_val == v and not lookup_val2) and ' class="selected"' or ''),
- get_query_string(params, {lookup_kwarg: v}, [lookup_kwarg2]), k))
- if isinstance(f, meta.NullBooleanField):
- filter_template.append('
' % \
- (get_query_string(params, {year_field: year_lookup, month_field: month.month}, [field_generic]), month.strftime('%B'), month.year))
+ # Get the total number of objects, with no filters applied.
+ real_lookup_params = lookup_params.copy()
+ del real_lookup_params['order_by']
+ if real_lookup_params:
+ full_result_count = lookup_mod.get_count()
else:
- for year in getattr(lookup_mod, 'get_%s_list' % field_name)('year', **lookup_params):
- raw_template.append('
')
- del filter_template
-
- # Result table.
- if result_list:
- # Table headers.
- raw_template.append('
\n\n
\n')
- for i, field_name in enumerate(lookup_opts.admin.list_display):
+ # Get the list of objects to display on this page.
+ if (show_all and can_show_all) or not multi_page:
+ result_list = lookup_mod.get_list(**lookup_params)
+ else:
try:
- f = lookup_opts.get_field(field_name)
- except meta.FieldDoesNotExist:
- # For non-field list_display values, check for the function
- # attribute "short_description". If that doesn't exist, fall
- # back to the method name. And __repr__ is a special-case.
- if field_name == '__repr__':
- header = lookup_opts.verbose_name
+ result_list = paginator.get_page(page_num)
+ except InvalidPage:
+ result_list = []
+ (self.result_count, self.full_result_count, self.result_list,
+ self.can_show_all, self.multi_page, self.paginator) = (result_count,
+ full_result_count, result_list, can_show_all, multi_page, paginator )
+
+ def url_for_result(self, result):
+ return "%s/" % getattr(result, self.pk_attname)
+
+ def get_ordering(self):
+ lookup_opts, params = self.lookup_opts, self.params
+ # For ordering, first check the "ordering" parameter in the admin options,
+ # then check the object's default ordering. If neither of those exist,
+ # order descending by ID by default. Finally, look for manually-specified
+ # ordering from the query string.
+ ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
+
+ # Normalize it to new-style ordering.
+ ordering = meta.handle_legacy_orderlist(ordering)
+
+ if ordering[0].startswith('-'):
+ order_field, order_type = ordering[0][1:], 'desc'
+ else:
+ order_field, order_type = ordering[0], 'asc'
+ if params.has_key(ORDER_VAR):
+ try:
+ try:
+ f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
+ except meta.FieldDoesNotExist:
+ pass
else:
- func = getattr(mod.Klass, field_name) # Let AttributeErrors propogate.
- try:
- header = func.short_description
- except AttributeError:
- header = func.__name__.replace('_', ' ')
- # Non-field list_display values don't get ordering capability.
- raw_template.append('
%s
' % capfirst(header))
- else:
- if isinstance(f.rel, meta.ManyToOne) and f.null:
- raw_template.append('
' % \
- ((th_classes and ' class="%s"' % ' '.join(th_classes) or ''),
- get_query_string(params, {ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
- capfirst(f.verbose_name)))
- raw_template.append('
\n\n')
- # Result rows.
- pk = lookup_opts.pk.attname
- for i, result in enumerate(result_list):
- raw_template.append('
\n' % (i % 2 + 1))
- for j, field_name in enumerate(lookup_opts.admin.list_display):
- row_class = ''
+ if not isinstance(f.rel, meta.ManyToOne) or not f.null:
+ order_field = f.name
+ except (IndexError, ValueError):
+ pass # Invalid ordering specified. Just use the default.
+ if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
+ order_type = params[ORDER_TYPE_VAR]
+ self.order_field, self.order_type = order_field, order_type
+
+ def get_lookup_params(self):
+ # Prepare the lookup parameters for the API lookup.
+ (params, order_field, lookup_opts, order_type, opts, query) = \
+ (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query)
+
+ lookup_params = params.copy()
+ for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
+ if lookup_params.has_key(i):
+ del lookup_params[i]
+ # If the order-by field is a field with a relationship, order by the value
+ # in the related table.
+ lookup_order_field = order_field
+ try:
+ f = lookup_opts.get_field(order_field)
+ except meta.FieldDoesNotExist:
+ pass
+ else:
+ if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOne):
+ f = lookup_opts.get_field(order_field)
+ rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column
+ lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering)
+ # Use select_related if one of the list_display options is a field with a
+ # relationship.
+ if lookup_opts.admin.list_select_related:
+ lookup_params['select_related'] = True
+ else:
+ for field_name in lookup_opts.admin.list_display:
try:
f = lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
- # For non-field list_display values, the value is a method
- # name. Execute the method.
- func = getattr(result, field_name)
- try:
- result_repr = str(func())
- except ObjectDoesNotExist:
- result_repr = EMPTY_CHANGELIST_VALUE
- else:
- # Strip HTML tags in the resulting text, except if the
- # function has an "allow_tags" attribute set to True.
- if not getattr(func, 'allow_tags', False):
- result_repr = strip_tags(result_repr)
+ pass
else:
- field_val = getattr(result, f.attname)
- # Foreign-key fields are special: Use the repr of the
- # related object.
if isinstance(f.rel, meta.ManyToOne):
- if field_val is not None:
- result_repr = getattr(result, 'get_%s' % f.name)()
- else:
- result_repr = EMPTY_CHANGELIST_VALUE
- # Dates and times are special: They're formatted in a certain way.
- elif isinstance(f, meta.DateField) or isinstance(f, meta.TimeField):
- if field_val:
- (date_format, datetime_format, time_format) = get_date_formats()
- if isinstance(f, meta.DateTimeField):
- result_repr = capfirst(dateformat.format(field_val, datetime_format))
- elif isinstance(f, meta.TimeField):
- result_repr = capfirst(dateformat.time_format(field_val, time_format))
- else:
- result_repr = capfirst(dateformat.format(field_val, date_format))
- else:
- result_repr = EMPTY_CHANGELIST_VALUE
- row_class = ' class="nowrap"'
- # Booleans are special: We use images.
- elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField):
- BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
- result_repr = '' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
- # ImageFields are special: Use a thumbnail.
- elif isinstance(f, meta.ImageField):
- from django.parts.media.photos import get_thumbnail_url
- result_repr = '' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
- # FloatFields are special: Zero-pad the decimals.
- elif isinstance(f, meta.FloatField):
- if field_val is not None:
- result_repr = ('%%.%sf' % f.decimal_places) % field_val
- else:
- result_repr = EMPTY_CHANGELIST_VALUE
- # Fields with choices are special: Use the representation
- # of the choice.
- elif f.choices:
- result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
- else:
- result_repr = strip_tags(str(field_val))
- # Some browsers don't like empty "
"s.
- if result_repr == '':
- result_repr = ' '
- if j == 0: # First column is a special case
- result_id = getattr(result, pk)
- raw_template.append('
\n')
- del result_list # to free memory
- raw_template.append('
\n')
- else:
- raw_template.append('
No %s matched your search criteria.
' % opts.verbose_name_plural)
+ lookup_params['select_related'] = True
+ break
+ lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,)
+ if lookup_opts.admin.search_fields and query:
+ or_queries = []
+ for bit in query.split():
+ or_query = []
+ for field_name in lookup_opts.admin.search_fields:
+ or_query.append(('%s__icontains' % field_name, bit))
+ or_queries.append(or_query)
+ lookup_params['_or'] = or_queries
- # Pagination.
- raw_template.append('
')
- if (show_all and can_show_all) or not multi_page:
- pass
- else:
- raw_template.append('Page › ')
- ON_EACH_SIDE = 3
- ON_ENDS = 2
- DOT = '.'
- # If there are 10 or fewer pages, display links to every page.
- # Otherwise, do some fancy
- if p.pages <= 10:
- page_range = range(p.pages)
- else:
- # Insert "smart" pagination links, so that there are always ON_ENDS
- # links at either end of the list of pages, and there are always
- # ON_EACH_SIDE links at either end of the "current page" link.
- page_range = []
- if page_num > (ON_EACH_SIDE + ON_ENDS):
- page_range.extend(range(0, ON_EACH_SIDE - 1))
- page_range.append(DOT)
- page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
- else:
- page_range.extend(range(0, page_num + 1))
- if page_num < (p.pages - ON_EACH_SIDE - ON_ENDS - 1):
- page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
- page_range.append(DOT)
- page_range.extend(range(p.pages - ON_ENDS, p.pages))
- else:
- page_range.extend(range(page_num + 1, p.pages))
- for i in page_range:
- if i == DOT:
- raw_template.append('... ')
- elif i == page_num:
- raw_template.append('%d ' % (i+1))
- else:
- raw_template.append('%d ' % \
- (get_query_string(params, {PAGE_VAR: i}), (i == p.pages-1 and ' class="end"' or ''), i+1))
- raw_template.append('%s %s' % (result_count, result_count == 1 and opts.verbose_name or opts.verbose_name_plural))
- if can_show_all and not show_all and multi_page:
- raw_template.append(' Show all' % \
- get_query_string(params, {ALL_VAR: ''}))
- raw_template.append('
{%% endif %%}{%% endif %%}' % \
- (app_label, opts.get_delete_permission()))
- if change and opts.admin.save_as:
- t.append('{%% if not is_popup %%}{%% endif %%}' % \
- (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
- if not opts.admin.save_as or add:
- t.append('{%% if not is_popup %%}{%% endif %%}' % \
- (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
- t.append('{%% if not is_popup %%}{%% endif %%}' % \
- (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
- t.append('' % \
- (ordered_objects and change and 'onclick="submitOrderForm();"' or ''))
- t.append('
\n')
- return t
-def _get_template(opts, app_label, add=False, change=False, show_delete=False, form_url=''):
- admin_field_objs = opts.admin.get_field_objs(opts)
- ordered_objects = opts.get_ordered_objects()[:]
- auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
- t = ['{% extends "admin/base_site" %}\n']
- t.append('{% block extrahead %}')
-
- # Put in any necessary JavaScript imports.
- javascript_imports = ['%sjs/core.js' % ADMIN_MEDIA_PREFIX, '%sjs/admin/RelatedObjectLookups.js' % ADMIN_MEDIA_PREFIX]
+def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_sets):
+# Put in any necessary JavaScript imports.
+ js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
if auto_populated_fields:
- javascript_imports.append('%sjs/urlify.js' % ADMIN_MEDIA_PREFIX)
+ js.append('js/urlify.js')
if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
- javascript_imports.extend(['%sjs/calendar.js' % ADMIN_MEDIA_PREFIX, '%sjs/admin/DateTimeShortcuts.js' % ADMIN_MEDIA_PREFIX])
+ js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
if ordered_objects:
- javascript_imports.extend(['%sjs/getElementsBySelector.js' % ADMIN_MEDIA_PREFIX, '%sjs/dom-drag.js' % ADMIN_MEDIA_PREFIX, '%sjs/admin/ordering.js' % ADMIN_MEDIA_PREFIX])
+ js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
if opts.admin.js:
- javascript_imports.extend(opts.admin.js)
+ js.extend(opts.admin.js)
seen_collapse = False
- for _, options in admin_field_objs:
- if not seen_collapse and 'collapse' in options.get('classes', ''):
+ for field_set in field_sets:
+ if not seen_collapse and 'collapse' in field_set.classes:
seen_collapse = True
- javascript_imports.append('%sjs/admin/CollapsedFieldsets.js' % ADMIN_MEDIA_PREFIX)
- for field_list in options['fields']:
+ js.append('js/admin/CollapsedFieldsets.js' )
+
+ for field_line in field_set:
try:
- for f in field_list:
+ for f in field_line:
if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
- javascript_imports.extend(['%sjs/SelectBox.js' % ADMIN_MEDIA_PREFIX, '%sjs/SelectFilter2.js' % ADMIN_MEDIA_PREFIX])
+ js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
raise StopIteration
except StopIteration:
break
- for j in javascript_imports:
- t.append('' % j)
+ return js
- t.append('{% endblock %}\n')
- if ordered_objects:
- coltype = 'colMS'
- else:
- coltype = 'colM'
- t.append('{%% block coltype %%}%s{%% endblock %%}\n' % coltype)
- t.append('{%% block bodyclass %%}%s-%s change-form{%% endblock %%}\n' % (app_label, opts.object_name.lower()))
- breadcrumb_title = add and "Add %s" % opts.verbose_name or '{{ original|striptags|truncatewords:"18" }}'
- t.append('{%% block breadcrumbs %%}{%% if not is_popup %%}
\n{% endblock %}')
- return ''.join(t)
-def _get_admin_field(field_list, name_prefix, rel, add, change):
- "Returns the template code for editing the given list of fields in the admin template."
- field_names = []
- for f in field_list:
- field_names.extend(f.get_manipulator_field_names(name_prefix))
- div_class_names = ['form-row', '{%% if %s %%} error{%% endif %%}' % ' or '.join(['%s.errors' % n for n in field_names])]
- # Assumes BooleanFields won't be stacked next to each other!
- if isinstance(field_list[0], meta.BooleanField):
- div_class_names.append('checkbox-row')
- t = []
- t.append('
\n' % ' '.join(div_class_names))
- for n in field_names:
- t.append('{%% if %s.errors %%}{{ %s.html_error_list }}{%% endif %%}\n' % (n, n))
- for i, field in enumerate(field_list):
- label_name = 'id_%s%s' % ((rel and "%s{{ forloop.counter0 }}." % name_prefix or ""), field.get_manipulator_field_names('')[0])
- # BooleanFields are a special case, because the checkbox widget appears to
- # the *left* of the label.
- if isinstance(field, meta.BooleanField):
- t.append(_get_admin_field_form_widget(field, name_prefix, rel, add, change))
- t.append(' ' % (label_name, capfirst(field.verbose_name)))
- else:
- class_names = []
- if not field.blank:
- class_names.append('required')
- if i > 0:
- class_names.append('inline')
- t.append(' ' % (label_name, class_names and ' class="%s"' % ' '.join(class_names) or '', capfirst(field.verbose_name)))
- t.append(_get_admin_field_form_widget(field, name_prefix, rel, add, change))
- if change and field.primary_key:
- t.append('{{ %soriginal.%s }}' % ((rel and name_prefix or ''), field.name))
- if change and use_raw_id_admin(field):
- if isinstance(field.rel, meta.ManyToOne):
- if_bit = '%soriginal.get_%s' % (rel and name_prefix or '', field.name)
- obj_repr = if_bit + '|truncatewords:"14"'
- elif isinstance(field.rel, meta.ManyToMany):
- if_bit = '%soriginal.get_%s_list' % (rel and name_prefix or '', field.name)
- obj_repr = if_bit + '|join:", "|truncatewords:"14"'
- t.append('{%% if %s %%} {{ %s }}{%% endif %%}' % (if_bit, obj_repr))
- if field.help_text:
- t.append('
%s
\n' % field.help_text)
- t.append('
\n\n')
- return ''.join(t)
+class AdminBoundField(BoundField):
+ def __init__(self, field, field_mapping, original):
+ super(AdminBoundField, self).__init__(field,field_mapping,original)
-def _get_admin_field_form_widget(field, name_prefix, rel, add, change):
- "Returns JUST the formfield widget for the field's admin interface."
- field_names = field.get_manipulator_field_names(name_prefix)
- if isinstance(field, meta.DateTimeField):
- return '
Date: {{ %s }} Time: {{ %s }}
' % tuple(field_names)
- t = ['{{ %s }}' % n for n in field_names]
- if change and isinstance(field, meta.FileField):
- return '{%% if %soriginal.%s %%}Currently: {{ %soriginal.%s }} Change: %s{%% else %%}%s{%% endif %%}' % \
- (name_prefix, field.name, name_prefix, field.name, name_prefix, field.name, ''.join(t), ''.join(t))
- field_id = 'id_%s%s' % ((rel and "%s{{ forloop.counter0 }}." % name_prefix or ""), field.get_manipulator_field_names('')[0])
- # raw_id_admin fields get the little lookup link next to them
- if use_raw_id_admin(field):
- t.append(' ' % \
- (field.rel.to.app_label, field.rel.to.module_name, field_id))
- t.append('' % ADMIN_MEDIA_PREFIX)
- # fields with relationships to editable objects get an "add another" link,
- # but only if the field doesn't have raw_admin ('cause in that case they get
- # the "add" button in the popup)
- elif field.rel and (isinstance(field.rel, meta.ManyToOne) or isinstance(field.rel, meta.ManyToMany)) and field.rel.to.admin:
- t.append('{%% if perms.%s.%s %%}' % (field.rel.to.app_label, field.rel.to.get_add_permission()))
- t.append(' ' % \
- (field.rel.to.app_label, field.rel.to.module_name, field_id))
- t.append('' % ADMIN_MEDIA_PREFIX)
- t.append('{% endif %}')
- return ''.join(t)
+ self.element_id = self.form_fields[0].get_id()
+ self.has_label_first = not isinstance(self.field, meta.BooleanField)
+ self.raw_id_admin = use_raw_id_admin(field)
+ self.is_date_time = isinstance(field, meta.DateTimeField)
+ self.is_file_field = isinstance(field, meta.FileField)
+ self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOne) or isinstance(field.rel, meta.ManyToMany) and field.rel.to.admin
+ self.hidden = isinstance(self.field, meta.AutoField)
+ self.first = False
+
+ classes = []
+ if(self.raw_id_admin):
+ classes.append('nowrap')
+ if max([bool(f.errors()) for f in self.form_fields]):
+ classes.append('error')
+ if classes:
+ self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
+ self._repr_filled = False
+
+ def _fetch_existing_display(self, func_name):
+ class_dict = self.original.__class__.__dict__
+ func = class_dict.get(func_name)
+ return func(self.original)
+
+ def _fill_existing_display(self):
+ if self._display_filled:
+ return
+ #HACK
+ if isinstance(self.field.rel, meta.ManyToOne):
+ func_name = 'get_%s' % self.field.name
+ self._display = self._fetch_existing_display(func_name)
+ elif isinstance(self.field.rel, meta.ManyToMany):
+ func_name = 'get_%s_list' % self.field.name
+ self._display = ",".join(self._fetch_existing_display(func_name))
+ self._display_filled = True
+
+ def existing_display(self):
+ self._fill_existing_display()
+ return self._display
+
+ def __repr__(self):
+ return repr(self.__dict__)
+
+ def html_error_list(self):
+ return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])
+
+class AdminBoundFieldLine(BoundFieldLine):
+ def __init__(self, field_line, field_mapping, original):
+ super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField)
+ for bound_field in self:
+ bound_field.first = True
+ break
+
+class AdminBoundFieldSet(BoundFieldSet):
+ def __init__(self, field_set, field_mapping, original):
+ super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine)
+
+class BoundManipulator(object):
+ def __init__(self, opts, manipulator, field_mapping):
+ self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
+ self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None
+ self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet)
+ for field_set in opts.admin.get_field_sets(opts)]
+ self.ordered_objects = opts.get_ordered_objects()[:]
+
+class AdminBoundManipulator(BoundManipulator):
+ def __init__(self, opts, manipulator, field_mapping):
+ super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping)
+ field_sets = opts.admin.get_field_sets(opts)
+
+ self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
+ self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets);
+
+ self.coltype = self.ordered_objects and 'colMS' or 'colM'
+ self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
+ self.form_enc_attrib = opts.has_field_type(meta.FileField) and \
+ 'enctype="multipart/form-data" ' or ''
+
+ self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
+ self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects]
+
+ self.save_on_top = opts.admin.save_on_top
+ self.save_as = opts.admin.save_as
+
+ self.content_type_id = opts.get_content_type_id()
+ self.verbose_name_plural = opts.verbose_name_plural
+ self.verbose_name = opts.verbose_name
+ self.object_name = opts.object_name
+
+ def get_ordered_object_pk(self, ordered_obj):
+ for name in self.ordered_object_pk_names:
+ if hasattr(ordered_obj, name):
+ return str(getattr(ordered_obj, name))
+ return ""
+
+def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''):
+ extra_context = {
+ 'add': add,
+ 'change': change,
+ 'bound_manipulator' : AdminBoundManipulator(opts, manipulator, context['form']),
+ 'has_delete_permission' : context['perms'][app_label][opts.get_delete_permission()],
+ 'form_url' : form_url,
+ 'app_label': app_label,
+ }
+ context.update(extra_context)
+ return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ),
+ "admin/%s/change_form" % app_label ,
+ "admin/change_form"], context_instance=context)
+
+def log_add_message(user, opts,manipulator,new_object):
+ pk_value = getattr(new_object, opts.pk.attname)
+ log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.ADDITION)
def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
mod, opts = _get_mod_opts(app_label, module_name)
@@ -780,19 +404,17 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
if opts.has_field_type(meta.FileField):
new_data.update(request.FILES)
errors = manipulator.get_validation_errors(new_data)
+ manipulator.do_html2python(new_data)
+
if not errors and not request.POST.has_key("_preview"):
- for f in opts.many_to_many:
- if f.rel.raw_id_admin:
- new_data.setlist(f.name, new_data[f.name].split(","))
- manipulator.do_html2python(new_data)
new_object = manipulator.save(new_data)
- pk_value = getattr(new_object, opts.pk.attname)
- log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)
- msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)
+ log_add_message(request.user, opts,manipulator,new_object)
+ msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':opts.verbose_name, 'obj':new_object}
+ pk_value = getattr(new_object,opts.pk.attname)
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if request.POST.has_key("_continue"):
- request.user.add_message("%s You may edit it again below." % msg)
+ request.user.add_message(msg + ' ' + _("You may edit it again below."))
if request.POST.has_key("_popup"):
post_url_continue += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value)
@@ -800,70 +422,49 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
return HttpResponse('' % \
(pk_value, repr(new_object).replace('"', '\\"')))
elif request.POST.has_key("_addanother"):
- request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
+ request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect(request.path)
else:
request.user.add_message(msg)
return HttpResponseRedirect(post_url)
- if request.POST.has_key("_preview"):
- manipulator.do_html2python(new_data)
else:
- new_data = {}
# Add default data.
- for f in opts.fields:
- if f.has_default():
- new_data.update(_get_flattened_data(f, f.get_default()))
- # In required many-to-one fields with only one available choice,
- # select that one available choice. Note: We have to check that
- # the length of choices is *2*, not 1, because SelectFields always
- # have an initial "blank" value.
- elif not f.blank and ((isinstance(f.rel, meta.ManyToOne) and not f.rel.raw_id_admin) or f.choices) and len(manipulator[f.name].choices) == 2:
- new_data[f.name] = manipulator[f.name].choices[1][0]
- # In required many-to-many fields with only one available choice,
- # select that one available choice.
- for f in opts.many_to_many:
- if not f.blank and not f.rel.edit_inline and not f.rel.raw_id_admin and len(manipulator[f.name].choices) == 1:
- new_data[f.name] = [manipulator[f.name].choices[0][0]]
- # Add default data for related objects.
- for rel_opts, rel_field in opts.get_inline_related_objects():
- var_name = rel_opts.object_name.lower()
- for i in range(rel_field.rel.num_in_admin):
- for f in rel_opts.fields + rel_opts.many_to_many:
- if f.has_default():
- for field_name in f.get_manipulator_field_names(''):
- new_data['%s.%d.%s' % (var_name, i, field_name)] = f.get_default()
+ new_data = manipulator.flatten_data()
+
# Override the defaults with request.GET, if it exists.
new_data.update(request.GET)
errors = {}
# Populate the FormWrapper.
- form = formfields.FormWrapper(manipulator, new_data, errors)
- for rel_opts, rel_field in opts.get_inline_related_objects():
- var_name = rel_opts.object_name.lower()
- wrapper = []
- for i in range(rel_field.rel.num_in_admin):
- collection = {}
- for f in rel_opts.fields + rel_opts.many_to_many:
- if f.editable and f != rel_field and not isinstance(f, meta.AutoField):
- for field_name in f.get_manipulator_field_names(''):
- full_field_name = '%s.%d.%s' % (var_name, i, field_name)
- collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, ''), errors.get(full_field_name, []))
- wrapper.append(formfields.FormFieldCollection(collection))
- setattr(form, rel_opts.module_name, wrapper)
+ form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True)
c = Context(request, {
- 'title': 'Add %s' % opts.verbose_name,
- "form": form,
- "is_popup": request.REQUEST.has_key("_popup"),
+ 'title': _('Add %s') % opts.verbose_name,
+ 'form': form,
+ 'is_popup': request.REQUEST.has_key('_popup'),
+ 'show_delete': show_delete,
})
if object_id_override is not None:
c['object_id'] = object_id_override
- raw_template = _get_template(opts, app_label, add=True, show_delete=show_delete, form_url=form_url)
-# return HttpResponse(raw_template, mimetype='text/plain')
- t = loader.get_template_from_string(raw_template)
- return HttpResponse(t.render(c))
+
+ return render_change_form(opts, manipulator, app_label, c, add=True)
add_stage = staff_member_required(add_stage)
+def log_change_message(user, opts,manipulator,new_object):
+ pk_value = getattr(new_object, opts.pk.column)
+ # Construct the change message.
+ change_message = []
+ if manipulator.fields_added:
+ change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
+ if manipulator.fields_changed:
+ change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
+ if manipulator.fields_deleted:
+ change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
+ change_message = ' '.join(change_message)
+ if not change_message:
+ change_message = _('No fields changed.')
+ log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.CHANGE, change_message)
+
def change_stage(request, app_label, module_name, object_id):
mod, opts = _get_mod_opts(app_label, module_name)
if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
@@ -875,120 +476,72 @@ def change_stage(request, app_label, module_name, object_id):
except ObjectDoesNotExist:
raise Http404
- inline_related_objects = opts.get_inline_related_objects()
if request.POST:
new_data = request.POST.copy()
if opts.has_field_type(meta.FileField):
new_data.update(request.FILES)
errors = manipulator.get_validation_errors(new_data)
+
+ manipulator.do_html2python(new_data)
if not errors and not request.POST.has_key("_preview"):
- for f in opts.many_to_many:
- if f.rel.raw_id_admin:
- new_data.setlist(f.name, new_data[f.name].split(","))
- manipulator.do_html2python(new_data)
new_object = manipulator.save(new_data)
- pk_value = getattr(new_object, opts.pk.attname)
-
- # Construct the change message.
- change_message = []
- if manipulator.fields_added:
- change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and'))
- if manipulator.fields_changed:
- change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and'))
- if manipulator.fields_deleted:
- change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and'))
- change_message = ' '.join(change_message)
- if not change_message:
- change_message = 'No fields changed.'
-
- log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.CHANGE, change_message)
- msg = 'The %s "%s" was changed successfully.' % (opts.verbose_name, new_object)
+ log_change_message(request.user,opts,manipulator,new_object)
+ msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj':new_object}
+ pk_value = getattr(new_object,opts.pk.attname)
if request.POST.has_key("_continue"):
- request.user.add_message("%s You may edit it again below." % msg)
+ request.user.add_message(msg + ' ' + _("You may edit it again below."))
if request.REQUEST.has_key('_popup'):
return HttpResponseRedirect(request.path + "?_popup=1")
else:
return HttpResponseRedirect(request.path)
elif request.POST.has_key("_saveasnew"):
- request.user.add_message('The %s "%s" was added successfully. You may edit it again below.' % (opts.verbose_name, new_object))
+ request.user.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
return HttpResponseRedirect("../%s/" % pk_value)
elif request.POST.has_key("_addanother"):
- request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
+ request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect("../add/")
else:
request.user.add_message(msg)
return HttpResponseRedirect("../")
- if request.POST.has_key("_preview"):
- manipulator.do_html2python(new_data)
else:
# Populate new_data with a "flattened" version of the current data.
- new_data = {}
- obj = manipulator.original_object
- for f in opts.fields:
- new_data.update(_get_flattened_data(f, getattr(obj, f.attname)))
- for f in opts.many_to_many:
- get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular)
- if f.rel.raw_id_admin:
- new_data[f.name] = ",".join([str(getattr(i, f.rel.to.pk.attname)) for i in get_list_func()])
- elif not f.rel.edit_inline:
- new_data[f.name] = [getattr(i, f.rel.to.pk.attname) for i in get_list_func()]
- for rel_obj, rel_field in inline_related_objects:
- var_name = rel_obj.object_name.lower()
- for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
- for f in rel_obj.fields:
- if f.editable and f != rel_field:
- for k, v in _get_flattened_data(f, getattr(rel_instance, f.attname)).items():
- new_data['%s.%d.%s' % (var_name, i, k)] = v
- for f in rel_obj.many_to_many:
- new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
+ new_data = manipulator.flatten_data()
+ # TODO: do this in flatten_data...
# If the object has ordered objects on its admin page, get the existing
# order and flatten it into a comma-separated list of IDs.
+
id_order_list = []
for rel_obj in opts.get_ordered_objects():
- id_order_list.extend(getattr(obj, 'get_%s_order' % rel_obj.object_name.lower())())
+ id_order_list.extend(getattr(manipulator.original_object, 'get_%s_order' % rel_obj.object_name.lower())())
if id_order_list:
new_data['order_'] = ','.join(map(str, id_order_list))
errors = {}
# Populate the FormWrapper.
- form = formfields.FormWrapper(manipulator, new_data, errors)
+ form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
form.original = manipulator.original_object
form.order_objects = []
- for rel_opts, rel_field in inline_related_objects:
- var_name = rel_opts.object_name.lower()
- wrapper = []
- orig_list = getattr(manipulator.original_object, 'get_%s_list' % opts.get_rel_object_method_name(rel_opts, rel_field))()
- count = len(orig_list) + rel_field.rel.num_extra_on_change
- if rel_field.rel.min_num_in_admin:
- count = max(count, rel_field.rel.min_num_in_admin)
- if rel_field.rel.max_num_in_admin:
- count = min(count, rel_field.rel.max_num_in_admin)
- for i in range(count):
- collection = {'original': (i < len(orig_list) and orig_list[i] or None)}
- for f in rel_opts.fields + rel_opts.many_to_many:
- if f.editable and f != rel_field:
- for field_name in f.get_manipulator_field_names(''):
- full_field_name = '%s.%d.%s' % (var_name, i, field_name)
- collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, f.get_default()), errors.get(full_field_name, []))
- wrapper.append(formfields.FormFieldCollection(collection))
- setattr(form, rel_opts.module_name, wrapper)
- if rel_opts.order_with_respect_to and rel_opts.order_with_respect_to.rel and rel_opts.order_with_respect_to.rel.to == opts:
+
+ #TODO Should be done in flatten_data / FormWrapper construction
+ for related in opts.get_followed_related_objects():
+ wrt = related.opts.order_with_respect_to
+ if wrt and wrt.rel and wrt.rel.to == opts:
+ func = getattr(manipulator.original_object, 'get_%s_list' %
+ related.get_method_name_part())
+ orig_list = func()
form.order_objects.extend(orig_list)
c = Context(request, {
- 'title': 'Change %s' % opts.verbose_name,
- "form": form,
+ 'title': _('Change %s') % opts.verbose_name,
+ 'form': form,
'object_id': object_id,
'original': manipulator.original_object,
- 'is_popup' : request.REQUEST.has_key('_popup'),
+ 'is_popup' : request.REQUEST.has_key('_popup')
})
- raw_template = _get_template(opts, app_label, change=True)
-# return HttpResponse(raw_template, mimetype='text/plain')
- t = loader.get_template_from_string(raw_template)
- return HttpResponse(t.render(c))
-change_stage = staff_member_required(change_stage)
+
+ return render_change_form(opts,manipulator, app_label, c, change=True)
def _nest_help(obj, depth, val):
current = obj
@@ -1002,75 +555,77 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if current_depth > 16:
return # Avoid recursing too deep.
objects_seen = []
- for rel_opts, rel_field in opts.get_all_related_objects():
- if rel_opts in objects_seen:
+ for related in opts.get_all_related_objects():
+ if related.opts in objects_seen:
continue
- objects_seen.append(rel_opts)
- rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)
- if isinstance(rel_field.rel, meta.OneToOne):
+ objects_seen.append(related.opts)
+ rel_opts_name = related.get_method_name_part()
+ if isinstance(related.field.rel, meta.OneToOne):
try:
sub_obj = getattr(obj, 'get_%s' % rel_opts_name)()
except ObjectDoesNotExist:
pass
else:
if rel_opts.admin:
- p = '%s.%s' % (rel_opts.app_label, rel_opts.get_delete_permission())
+ p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
if not user.has_perm(p):
- perms_needed.add(rel_opts.verbose_name)
+ perms_needed.add(related.opts.verbose_name)
# We don't care about populating deleted_objects now.
continue
- if rel_field.rel.edit_inline or not rel_opts.admin:
+ if related.field.rel.edit_inline or not related.opts.admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
- nh(deleted_objects, current_depth, ['%s: %r' % (capfirst(rel_opts.verbose_name), sub_obj), []])
+ nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), sub_obj), []])
else:
# Display a link to the admin page.
- nh(deleted_objects, current_depth, ['%s: %r' % \
- (capfirst(rel_opts.verbose_name), rel_opts.app_label, rel_opts.module_name,
- getattr(sub_obj, rel_opts.pk.attname), sub_obj), []])
- _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, rel_opts, current_depth+2)
+ nh(deleted_objects, current_depth, ['%s: %s' % \
+ (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name,
+ getattr(sub_obj, related.opts.pk.attname), sub_obj), []])
+ _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
else:
has_related_objs = False
for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
has_related_objs = True
- if rel_field.rel.edit_inline or not rel_opts.admin:
+ if related.field.rel.edit_inline or not related.opts.admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
- nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(rel_opts.verbose_name), strip_tags(repr(sub_obj))), []])
+ nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), strip_tags(str(sub_obj))), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, ['%s: %s' % \
- (capfirst(rel_opts.verbose_name), rel_opts.app_label, rel_opts.module_name, sub_obj.id, strip_tags(repr(sub_obj))), []])
- _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, rel_opts, current_depth+2)
+ (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(sub_obj))), []])
+ _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
# If there were related objects, and the user doesn't have
# permission to delete them, add the missing perm to perms_needed.
- if rel_opts.admin and has_related_objs:
- p = '%s.%s' % (rel_opts.app_label, rel_opts.get_delete_permission())
+ if related.opts.admin and has_related_objs:
+ p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
if not user.has_perm(p):
perms_needed.add(rel_opts.verbose_name)
- for rel_opts, rel_field in opts.get_all_related_many_to_many_objects():
- if rel_opts in objects_seen:
+ for related in opts.get_all_related_many_to_many_objects():
+ if related.opts in objects_seen:
continue
- objects_seen.append(rel_opts)
- rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)
+ objects_seen.append(related.opts)
+ rel_opts_name = related.get_method_name_part()
has_related_objs = False
for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
has_related_objs = True
- if rel_field.rel.edit_inline or not rel_opts.admin:
+ if related.field.rel.edit_inline or not related.opts.admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
- nh(deleted_objects, current_depth, ['One or more %s in %s: %s' % \
- (rel_field.name, rel_opts.verbose_name, strip_tags(repr(sub_obj))), []])
+ nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
+ {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': strip_tags(str(sub_obj))}, []])
else:
# Display a link to the admin page.
- nh(deleted_objects, current_depth, ['One or more %s in %s: %s' % \
- (rel_field.name, rel_opts.verbose_name, rel_opts.app_label, rel_opts.module_name, sub_obj.id, strip_tags(repr(sub_obj))), []])
+ nh(deleted_objects, current_depth, [
+ (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \
+ (' %s' % \
+ (related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(sub_obj)))), []])
# If there were related objects, and the user doesn't have
# permission to change them, add the missing perm to perms_needed.
- if rel_opts.admin and has_related_objs:
- p = '%s.%s' % (rel_opts.app_label, rel_opts.get_change_permission())
+ if related.opts.admin and has_related_objs:
+ p = '%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
if not user.has_perm(p):
- perms_needed.add(rel_opts.verbose_name)
+ perms_needed.add(related.opts.verbose_name)
def delete_stage(request, app_label, module_name, object_id):
import sets
@@ -1081,20 +636,20 @@ def delete_stage(request, app_label, module_name, object_id):
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
- deleted_objects = ['%s: %s' % (capfirst(opts.verbose_name), object_id, strip_tags(repr(obj))), []]
+ deleted_objects = ['%s: %s' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []]
perms_needed = sets.Set()
_get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
if request.POST: # The user has already confirmed the deletion.
if perms_needed:
raise PermissionDenied
- obj_repr = repr(obj)
+ obj_display = str(obj)
obj.delete()
- log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_repr, log.DELETION)
- request.user.add_message('The %s "%s" was deleted successfully.' % (opts.verbose_name, obj_repr))
+ log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, log.DELETION)
+ request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display})
return HttpResponseRedirect("../../")
return render_to_response('admin/delete_confirmation', {
- "title": "Are you sure?",
+ "title": _("Are you sure?"),
"object_name": opts.verbose_name,
"object": obj,
"deleted_objects": deleted_objects,
@@ -1109,7 +664,7 @@ def history(request, app_label, module_name, object_id):
# If no history was found, see whether this object even exists.
obj = get_object_or_404(mod, pk=object_id)
return render_to_response('admin/object_history', {
- 'title': 'Change history: %r' % obj,
+ 'title': _('Change history: %s') % obj,
'action_list': action_list,
'module_name': capfirst(opts.verbose_name_plural),
'object': obj,
diff --git a/django/core/formfields.py b/django/core/formfields.py
index 78fd044815..0cfe6b2890 100644
--- a/django/core/formfields.py
+++ b/django/core/formfields.py
@@ -90,15 +90,7 @@ class Manipulator:
expected to deal with invalid input.
"""
for field in self.fields:
- if new_data.has_key(field.field_name):
- new_data.setlist(field.field_name,
- [field.__class__.html2python(data) for data in new_data.getlist(field.field_name)])
- else:
- try:
- # individual fields deal with None values themselves
- new_data.setlist(field.field_name, [field.__class__.html2python(None)])
- except EmptyValue:
- new_data.setlist(field.field_name, [])
+ field.convert_post_data(new_data)
class FormWrapper:
"""
@@ -106,24 +98,36 @@ class FormWrapper:
This allows dictionary-style lookups of formfields. It also handles feeding
prepopulated data and validation error messages to the formfield objects.
"""
- def __init__(self, manipulator, data, error_dict):
+ def __init__(self, manipulator, data, error_dict, edit_inline=True):
self.manipulator, self.data = manipulator, data
self.error_dict = error_dict
+ self._inline_collections = None
+ self.edit_inline = edit_inline
def __repr__(self):
- return repr(self.data)
+ return repr(self.__dict__)
def __getitem__(self, key):
for field in self.manipulator.fields:
if field.field_name == key:
- if hasattr(field, 'requires_data_list') and hasattr(self.data, 'getlist'):
- data = self.data.getlist(field.field_name)
- else:
- data = self.data.get(field.field_name, None)
- if data is None:
- data = ''
+ data = field.extract_data(self.data)
return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
- raise KeyError
+ if self.edit_inline:
+ self.fill_inline_collections()
+ for inline_collection in self._inline_collections:
+ if inline_collection.name == key:
+ return inline_collection
+ raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key
+
+ def fill_inline_collections(self):
+ if not self._inline_collections:
+ ic = []
+ related_objects = self.manipulator.get_related_objects()
+ for rel_obj in related_objects:
+ data = rel_obj.extract_data(self.data)
+ inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
+ ic.append(inline_collection)
+ self._inline_collections = ic
def has_errors(self):
return self.error_dict != {}
@@ -166,6 +170,9 @@ class FormFieldWrapper:
else:
return ''
+ def get_id(self):
+ return self.formfield.get_id()
+
class FormFieldCollection(FormFieldWrapper):
"A utility class that gives the template access to a dict of FormFieldWrappers"
def __init__(self, formfield_dict):
@@ -185,9 +192,66 @@ class FormFieldCollection(FormFieldWrapper):
"Returns list of all errors in this collection's formfields"
errors = []
for field in self.formfield_dict.values():
- errors.extend(field.errors())
+ if hasattr(field, 'errors'):
+ errors.extend(field.errors())
return errors
+ def has_errors(self):
+ return bool(len(self.errors()))
+
+ def html_combined_error_list(self):
+ return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
+
+class InlineObjectCollection:
+ "An object that acts like a list of form field collections."
+ def __init__(self, parent_manipulator, rel_obj, data, errors):
+ self.parent_manipulator = parent_manipulator
+ self.rel_obj = rel_obj
+ self.data = data
+ self.errors = errors
+ self._collections = None
+ self.name = rel_obj.name
+
+ def __len__(self):
+ self.fill()
+ return self._collections.__len__()
+
+ def __getitem__(self, k):
+ self.fill()
+ return self._collections.__getitem__(k)
+
+ def __setitem__(self, k, v):
+ self.fill()
+ return self._collections.__setitem__(k,v)
+
+ def __delitem__(self, k):
+ self.fill()
+ return self._collections.__delitem__(k)
+
+ def __iter__(self):
+ self.fill()
+ return self._collections.__iter__()
+
+ def fill(self):
+ if self._collections:
+ return
+ else:
+ var_name = self.rel_obj.opts.object_name.lower()
+ wrapper = []
+ orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object or None
+ orig_list = self.rel_obj.get_list(orig)
+ for i, instance in enumerate(orig_list):
+ collection = {'original': instance}
+ for f in self.rel_obj.editable_fields():
+ for field_name in f.get_manipulator_field_names(''):
+ full_field_name = '%s.%d.%s' % (var_name, i, field_name)
+ field = self.parent_manipulator[full_field_name]
+ data = field.extract_data(self.data)
+ errors = self.errors.get(full_field_name, [])
+ collection[field_name] = FormFieldWrapper(field, data, errors)
+ wrapper.append(FormFieldCollection(collection))
+ self._collections = wrapper
+
class FormField:
"""Abstract class representing a form field.
@@ -220,6 +284,37 @@ class FormField:
def render(self, data):
raise NotImplementedError
+ def get_member_name(self):
+ if hasattr(self, 'member_name'):
+ return self.member_name
+ else:
+ return self.field_name
+
+ def extract_data(self, data_dict):
+ if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
+ data = data_dict.getlist(self.get_member_name())
+ else:
+ data = data_dict.get(self.get_member_name(), None)
+ if data is None:
+ data = ''
+ return data
+
+ def convert_post_data(self, new_data):
+ name = self.get_member_name()
+ if new_data.has_key(self.field_name):
+ d = new_data.getlist(self.field_name)
+ try:
+ converted_data = [self.__class__.html2python(data) for data in d]
+ except ValueError:
+ converted_data = d
+ new_data.setlist(name, converted_data)
+ else:
+ try:
+ # individual fields deal with None values themselves
+ new_data.setlist(name, [self.__class__.html2python(None)])
+ except EmptyValue:
+ new_data.setlist(name, [])
+
def get_id(self):
"Returns the HTML 'id' attribute for this form field."
return FORM_FIELD_ID_PREFIX + self.field_name
@@ -313,11 +408,13 @@ class CheckboxField(FormField):
html2python = staticmethod(html2python)
class SelectField(FormField):
- def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[]):
+ def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None):
self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.size, self.is_required = choices, size, is_required
self.validator_list = [self.isValidChoice] + validator_list
+ if member_name != None:
+ self.member_name = member_name
def render(self, data):
output = ['