Merged the newforms-admin branch into trunk.

This is a backward incompatible change. The admin contrib app has been
refactored. The newforms module has several improvements including FormSets
and Media definitions.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@7967 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Brian Rosner 2008-07-18 23:54:34 +00:00
parent dc375fb0f3
commit a19ed8aea3
121 changed files with 8050 additions and 2680 deletions

10
AUTHORS
View File

@ -74,6 +74,7 @@ answer newbie questions, and generally made Django that much better:
Arvis Bickovskis <viestards.lists@gmail.com>
Paul Bissex <http://e-scribe.com/>
Simon Blanchard
David Blewett <david@dawninglight.net>
Matt Boersma <ogghead@gmail.com>
boobsd@gmail.com
Andrew Brehaut <http://brehaut.net/blog>
@ -172,6 +173,7 @@ answer newbie questions, and generally made Django that much better:
Espen Grindhaug <http://grindhaug.org/>
Thomas Güttler <hv@tbz-pariv.de>
dAniel hAhler
hambaloney
Brian Harring <ferringb@gmail.com>
Brant Harris
Hawkeye
@ -194,6 +196,7 @@ answer newbie questions, and generally made Django that much better:
Baurzhan Ismagulov <ibr@radix50.net>
james_027@yahoo.com
jcrasta@gmail.com
jdetaeye
Zak Johnson <zakj@nox.cx>
Nis Jørgensen <nis@superlativ.dk>
Michael Josephson <http://www.sdjournal.com/>
@ -241,11 +244,13 @@ answer newbie questions, and generally made Django that much better:
Waylan Limberg <waylan@gmail.com>
limodou
Philip Lindborg <philip.lindborg@gmail.com>
Simon Litchfield <simon@quo.com.au>
Daniel Lindsley <polarcowz@gmail.com>
Trey Long <trey@ktrl.com>
msaelices <msaelices@gmail.com>
Matt McClanahan <http://mmcc.cx/>
Martin Maney <http://www.chipy.org/Martin_Maney>
Petr Marhoun <petr.marhoun@gmail.com>
masonsimon+django@gmail.com
Manuzhai
Petr Marhoun <petr.marhoun@gmail.com>
@ -258,6 +263,7 @@ answer newbie questions, and generally made Django that much better:
mattycakes@gmail.com
Jason McBrayer <http://www.carcosa.net/jason/>
mccutchen@gmail.com
Christian Metts
michael.mcewan@gmail.com
michal@plovarna.cz
Slawek Mikula <slawek dot mikula at gmail dot com>
@ -270,6 +276,7 @@ answer newbie questions, and generally made Django that much better:
Eric Moritz <http://eric.themoritzfamily.com/>
mrmachine <real.human@mrmachine.net>
Robin Munn <http://www.geekforgod.com/>
msundstr
Robert Myers <myer0052@gmail.com>
Nebojša Dorđević
Doug Napoleone <doug@dougma.com>
@ -290,6 +297,7 @@ answer newbie questions, and generally made Django that much better:
peter@mymart.com
pgross@thoughtworks.com
phaedo <http://phaedo.cx/>
Julien Phalip <http://www.julienphalip.com>
phil@produxion.net
phil.h.smith@gmail.com
Gustavo Picon
@ -298,6 +306,7 @@ answer newbie questions, and generally made Django that much better:
Mihai Preda <mihai_preda@yahoo.com>
Daniel Poelzleithner <http://poelzi.org/>
polpak@yahoo.com
Matthias Pronk <django@masida.nl>
Jyrki Pulliainen <jyrki.pulliainen@gmail.com>
Johann Queuniet <johann.queuniet@adh.naellia.eu>
Jan Rademaker
@ -314,6 +323,7 @@ answer newbie questions, and generally made Django that much better:
Matt Riggott
Henrique Romano <onaiort@gmail.com>
Armin Ronacher
Daniel Roseman <http://roseman.org.uk/>
Brian Rosner <brosner@gmail.com>
Oliver Rutherfurd <http://rutherfurd.net/>
ryankanno

View File

@ -1,4 +1,4 @@
VERSION = (0, 97, 'pre')
VERSION = (0, 97, 'newforms-admin')
def get_version():
"Returns the version as a human-format string."

View File

@ -1,9 +1,15 @@
from django.conf.urls.defaults import *
# Uncomment this for admin:
#from django.contrib import admin
urlpatterns = patterns('',
# Example:
# (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
# Uncomment this for admin docs:
#(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment this for admin:
# (r'^admin/', include('django.contrib.admin.urls')),
#('^admin/(.*)', admin.site.root),
)

View File

@ -0,0 +1,16 @@
from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
from django.contrib.admin.options import StackedInline, TabularInline
from django.contrib.admin.sites import AdminSite, site
def autodiscover():
"""
Auto-discover INSTALLED_APPS admin.py modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
"""
from django.conf import settings
for app in settings.INSTALLED_APPS:
try:
__import__("%s.admin" % app)
except ImportError:
pass

View File

@ -15,7 +15,7 @@ import datetime
class FilterSpec(object):
filter_specs = []
def __init__(self, f, request, params, model):
def __init__(self, f, request, params, model, model_admin):
self.field = f
self.params = params
@ -23,10 +23,10 @@ class FilterSpec(object):
cls.filter_specs.append((test, factory))
register = classmethod(register)
def create(cls, f, request, params, model):
def create(cls, f, request, params, model, model_admin):
for test, factory in cls.filter_specs:
if test(f):
return factory(f, request, params, model)
return factory(f, request, params, model, model_admin)
create = classmethod(create)
def has_output(self):
@ -52,8 +52,8 @@ class FilterSpec(object):
return mark_safe("".join(t))
class RelatedFilterSpec(FilterSpec):
def __init__(self, f, request, params, model):
super(RelatedFilterSpec, self).__init__(f, request, params, model)
def __init__(self, f, request, params, model, model_admin):
super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin)
if isinstance(f, models.ManyToManyField):
self.lookup_title = f.rel.to._meta.verbose_name
else:
@ -81,8 +81,8 @@ class RelatedFilterSpec(FilterSpec):
FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec)
class ChoicesFilterSpec(FilterSpec):
def __init__(self, f, request, params, model):
super(ChoicesFilterSpec, self).__init__(f, request, params, model)
def __init__(self, f, request, params, model, model_admin):
super(ChoicesFilterSpec, self).__init__(f, request, params, model, model_admin)
self.lookup_kwarg = '%s__exact' % f.name
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
@ -98,8 +98,8 @@ class ChoicesFilterSpec(FilterSpec):
FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
class DateFieldFilterSpec(FilterSpec):
def __init__(self, f, request, params, model):
super(DateFieldFilterSpec, self).__init__(f, request, params, model)
def __init__(self, f, request, params, model, model_admin):
super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin)
self.field_generic = '%s__' % self.field.name
@ -133,8 +133,8 @@ class DateFieldFilterSpec(FilterSpec):
FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
class BooleanFieldFilterSpec(FilterSpec):
def __init__(self, f, request, params, model):
super(BooleanFieldFilterSpec, self).__init__(f, request, params, model)
def __init__(self, f, request, params, model, model_admin):
super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, model_admin)
self.lookup_kwarg = '%s__exact' % f.name
self.lookup_kwarg2 = '%s__isnull' % f.name
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
@ -159,10 +159,10 @@ FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f
# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much
# more appropriate, and the AllValuesFilterSpec won't get used for it.
class AllValuesFilterSpec(FilterSpec):
def __init__(self, f, request, params, model):
super(AllValuesFilterSpec, self).__init__(f, request, params, model)
def __init__(self, f, request, params, model, model_admin):
super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin)
self.lookup_val = request.GET.get(f.name, None)
self.lookup_choices = model._meta.admin.manager.distinct().order_by(f.name).values(f.name)
self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name)
def title(self):
return self.field.verbose_name

View File

@ -58,3 +58,24 @@ fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Cou
.vLargeTextField, .vXMLLargeTextField { width:48em; }
.flatpages-flatpage #id_content { height:40.2em; }
.module table .vPositiveSmallIntegerField { width:2.2em; }
/* x unsorted */
.inline-group {padding:10px; padding-bottom:5px; background:#eee; margin:10px 0;}
.inline-group h3.header {margin:-5px -10px 5px -10px; background:#bbb; color:#fff; padding:2px 5px 3px 5px; font-size:11px}
.inline-related {margin-bottom:15px; position:relative;}
.last-related {margin-bottom:0px;}
.inline-related h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; color:#888; }
.inline-related h2 b {font-weight:normal; color:#aaa;}
.inline-related h2 span.delete {padding-left:20px; position:absolute; top:0px; right:5px;}
.inline-related h2 span.delete label {margin-left:2px; padding-top:1px;}
.inline-related fieldset {background:#fbfbfb;}
.inline-related fieldset.module h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#bcd; color:#fff; }
.inline-related.tabular fieldset.module table {width:100%;}
.inline-group .tabular tr.has_original td {padding-top:2em;}
.inline-group .tabular tr td.original { padding:2px 0 0 0; width:0; _position:relative; }
.inline-group .tabular th.original {width:0px; padding:0;}
.inline-group .tabular td.original p {position:absolute; left:0; height:1.1em; padding:2px 7px; overflow:hidden; font-size:9px; font-weight:bold; color:#666; _width:700px; }
.inline-group ul.tools {padding:0; margin: 0; list-style:none;}
.inline-group ul.tools li {display:inline; padding:0 5px;}
.inline-group ul.tools a.add {background:url(../img/admin/icon_addlink.gif) 0 50% no-repeat; padding-left:14px;}

View File

@ -1,81 +0,0 @@
/*
SelectFilter - Turns a multiple-select box into a filter interface.
Requires SelectBox.js and addevent.js.
*/
function findForm(node) {
// returns the node of the form containing the given node
if (node.tagName.toLowerCase() != 'form') {
return findForm(node.parentNode);
}
return node;
}
var SelectFilter = {
init: function(field_id) {
var from_box = document.getElementById(field_id);
from_box.id += '_from'; // change its ID
// Create the INPUT input box
var input_box = document.createElement('input');
input_box.id = field_id + '_input';
input_box.setAttribute('type', 'text');
from_box.parentNode.insertBefore(input_box, from_box);
from_box.parentNode.insertBefore(document.createElement('br'), input_box.nextSibling);
// Create the TO box
var to_box = document.createElement('select');
to_box.id = field_id + '_to';
to_box.setAttribute('multiple', 'multiple');
to_box.setAttribute('size', from_box.size);
from_box.parentNode.insertBefore(to_box, from_box.nextSibling);
to_box.setAttribute('name', from_box.getAttribute('name'));
from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
// Give the filters a CSS hook
from_box.setAttribute('class', 'filtered');
to_box.setAttribute('class', 'filtered');
// Set up the JavaScript event handlers for the select box filter interface
addEvent(input_box, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
addEvent(input_box, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); });
addEvent(from_box, 'focus', function() { input_box.focus(); });
addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); });
addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
SelectBox.init(field_id + '_from');
SelectBox.init(field_id + '_to');
// Move selected from_box options to to_box
SelectBox.move(field_id + '_from', field_id + '_to');
},
filter_key_up: function(event, field_id) {
from = document.getElementById(field_id + '_from');
// don't submit form if user pressed Enter
if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
from.selectedIndex = 0;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = 0;
return false;
}
var temp = from.selectedIndex;
SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
from.selectedIndex = temp;
return true;
},
filter_key_down: function(event, field_id) {
from = document.getElementById(field_id + '_from');
// right arrow -- move across
if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
var old_index = from.selectedIndex;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
return false;
}
// down arrow -- wrap around
if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
}
// up arrow -- wrap around
if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
}
return true;
}
}

View File

@ -47,7 +47,7 @@ var CollapsedFieldsets = {
// Returns true if any fields in the fieldset have validation errors.
var divs = fs.getElementsByTagName('div');
for (var i=0; i<divs.length; i++) {
if (divs[i].className.match(/\berror\b/)) {
if (divs[i].className.match(/\berrors\b/)) {
return true;
}
}

View File

@ -1,4 +1,4 @@
// Handles related-objects functionality: lookup link for raw_id_admin=True
// Handles related-objects functionality: lookup link for raw_id_fields
// and Add Another links.
function html_unescape(text) {
@ -29,7 +29,7 @@ function showRelatedObjectLookupPopup(triggeringLink) {
function dismissRelatedLookupPopup(win, chosenId) {
var name = win.name.replace(/___/g, '.');
var elem = document.getElementById(name);
if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) {
if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
elem.value += ',' + chosenId;
} else {
document.getElementById(name).value = chosenId;

View File

@ -1,6 +1,7 @@
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
from django.utils.safestring import mark_safe
@ -50,4 +51,4 @@ class LogEntry(models.Model):
Returns the admin URL to edit the object represented by this log entry.
This is relative to the Django admin index page.
"""
return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id))
return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id)))

View File

@ -0,0 +1,795 @@
from django import oldforms, template
from django import newforms as forms
from django.newforms.formsets import all_valid
from django.newforms.models import modelform_factory, inlineformset_factory
from django.newforms.models import BaseInlineFormset
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets
from django.contrib.admin.util import quote, unquote, get_deleted_objects
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.db import models, transaction
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.text import capfirst, get_text_list
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
import sets
HORIZONTAL, VERTICAL = 1, 2
# returns the <ul> class for a given radio_admin field
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
class IncorrectLookupParameters(Exception):
pass
def flatten_fieldsets(fieldsets):
"""Returns a list of field names from an admin fieldsets structure."""
field_names = []
for name, opts in fieldsets:
for field in opts['fields']:
# type checking feels dirty, but it seems like the best way here
if type(field) == tuple:
field_names.extend(field)
else:
field_names.append(field)
return field_names
class AdminForm(object):
def __init__(self, form, fieldsets, prepopulated_fields):
self.form, self.fieldsets = form, fieldsets
self.prepopulated_fields = [{
'field': form[field_name],
'dependencies': [form[f] for f in dependencies]
} for field_name, dependencies in prepopulated_fields.items()]
def __iter__(self):
for name, options in self.fieldsets:
yield Fieldset(self.form, name, **options)
def first_field(self):
for bf in self.form:
return bf
def _media(self):
media = self.form.media
for fs in self:
media = media + fs.media
return media
media = property(_media)
class Fieldset(object):
def __init__(self, form, name=None, fields=(), classes=(), description=None):
self.form = form
self.name, self.fields = name, fields
self.classes = u' '.join(classes)
self.description = description
def _media(self):
from django.conf import settings
if 'collapse' in self.classes:
return forms.Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX])
return forms.Media()
media = property(_media)
def __iter__(self):
for field in self.fields:
yield Fieldline(self.form, field)
class Fieldline(object):
def __init__(self, form, field):
self.form = form # A django.forms.Form instance
if isinstance(field, basestring):
self.fields = [field]
else:
self.fields = field
def __iter__(self):
for i, field in enumerate(self.fields):
yield AdminField(self.form, field, is_first=(i == 0))
def errors(self):
return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]))
class AdminField(object):
def __init__(self, form, field, is_first):
self.field = form[field] # A django.forms.BoundField instance
self.is_first = is_first # Whether this field is first on the line
self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput)
def label_tag(self):
classes = []
if self.is_checkbox:
classes.append(u'vCheckboxLabel')
contents = escape(self.field.label)
else:
contents = force_unicode(escape(self.field.label)) + u':'
if self.field.field.required:
classes.append(u'required')
if not self.is_first:
classes.append(u'inline')
attrs = classes and {'class': u' '.join(classes)} or {}
return self.field.label_tag(contents=contents, attrs=attrs)
class BaseModelAdmin(object):
"""Functionality common to both ModelAdmin and InlineAdmin."""
raw_id_fields = ()
fields = None
fieldsets = None
form = forms.ModelForm
filter_vertical = ()
filter_horizontal = ()
radio_fields = {}
prepopulated_fields = {}
def formfield_for_dbfield(self, db_field, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
"""
# For DateTimeFields, use a special field and widget.
if isinstance(db_field, models.DateTimeField):
kwargs['form_class'] = forms.SplitDateTimeField
kwargs['widget'] = widgets.AdminSplitDateTime()
return db_field.formfield(**kwargs)
# For DateFields, add a custom CSS class.
if isinstance(db_field, models.DateField):
kwargs['widget'] = widgets.AdminDateWidget
return db_field.formfield(**kwargs)
# For TimeFields, add a custom CSS class.
if isinstance(db_field, models.TimeField):
kwargs['widget'] = widgets.AdminTimeWidget
return db_field.formfield(**kwargs)
# For FileFields and ImageFields add a link to the current file.
if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
kwargs['widget'] = widgets.AdminFileWidget
return db_field.formfield(**kwargs)
# For ForeignKey or ManyToManyFields, use a special widget.
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields:
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
'class': get_ul_class(self.radio_fields[db_field.name]),
})
kwargs['empty_label'] = db_field.blank and _('None') or None
else:
if isinstance(db_field, models.ManyToManyField):
if db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
kwargs['help_text'] = ''
elif db_field.name in (self.filter_vertical + self.filter_horizontal):
kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
# Wrap the widget's render() method with a method that adds
# extra HTML to the end of the rendered output.
formfield = db_field.formfield(**kwargs)
# Don't wrap raw_id fields. Their add function is in the popup window.
if not db_field.name in self.raw_id_fields:
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
return formfield
if db_field.choices and db_field.name in self.radio_fields:
kwargs['widget'] = widgets.AdminRadioSelect(
choices=db_field.get_choices(include_blank=db_field.blank,
blank_choice=[('', _('None'))]),
attrs={
'class': get_ul_class(self.radio_fields[db_field.name]),
}
)
# For any other type of field, just call its formfield() method.
return db_field.formfield(**kwargs)
def _declared_fieldsets(self):
if self.fieldsets:
return self.fieldsets
elif self.fields:
return [(None, {'fields': self.fields})]
return None
declared_fieldsets = property(_declared_fieldsets)
class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model."
__metaclass__ = forms.MediaDefiningClass
list_display = ('__str__',)
list_display_links = ()
list_filter = ()
list_select_related = False
list_per_page = 100
search_fields = ()
date_hierarchy = None
save_as = False
save_on_top = False
ordering = None
inlines = []
# Custom templates (designed to be over-ridden in subclasses)
change_form_template = None
change_list_template = None
delete_confirmation_template = None
object_history_template = None
def __init__(self, model, admin_site):
self.model = model
self.opts = model._meta
self.admin_site = admin_site
self.inline_instances = []
for inline_class in self.inlines:
inline_instance = inline_class(self.model, self.admin_site)
self.inline_instances.append(inline_instance)
super(ModelAdmin, self).__init__()
def __call__(self, request, url):
# Check that LogEntry, ContentType and the auth context processor are installed.
from django.conf import settings
if settings.DEBUG:
from django.contrib.admin.models import LogEntry
if not LogEntry._meta.installed:
raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
if not ContentType._meta.installed:
raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
# Delegate to the appropriate method, based on the URL.
if url is None:
return self.changelist_view(request)
elif url.endswith('add'):
return self.add_view(request)
elif url.endswith('history'):
return self.history_view(request, unquote(url[:-8]))
elif url.endswith('delete'):
return self.delete_view(request, unquote(url[:-7]))
else:
return self.change_view(request, unquote(url))
def _media(self):
from django.conf import settings
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
if self.prepopulated_fields:
js.append('js/urlify.js')
if self.opts.get_ordered_objects():
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
if self.filter_vertical or self.filter_horizontal:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
media = property(_media)
def has_add_permission(self, request):
"Returns True if the given request has permission to add an object."
opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
def has_change_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change the given
Django model instance.
If `obj` is None, this should return True if the given request has
permission to change *any* object of the given type.
"""
opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
def has_delete_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change the given
Django model instance.
If `obj` is None, this should return True if the given request has
permission to delete *any* object of the given type.
"""
opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
def queryset(self, request):
"""
Returns a QuerySet of all model instances that can be edited by the
admin site. This is used by changelist_view.
"""
qs = self.model._default_manager.get_query_set()
# TODO: this should be handled by some parameter to the ChangeList.
ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
if ordering:
qs = qs.order_by(*ordering)
return qs
def get_fieldsets(self, request, obj=None):
"Hook for specifying fieldsets for the add form."
if self.declared_fieldsets:
return self.declared_fieldsets
form = self.get_form(request)
return [(None, {'fields': form.base_fields.keys()})]
def get_form(self, request, obj=None):
"""
Returns a Form class for use in the admin add view. This is used by
add_view and change_view.
"""
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
fields = None
return modelform_factory(self.model, form=self.form, fields=fields, formfield_callback=self.formfield_for_dbfield)
def get_formsets(self, request, obj=None):
for inline in self.inline_instances:
yield inline.get_formset(request, obj)
def save_add(self, request, form, formsets, post_url_continue):
"""
Saves the object in the "add" stage and returns an HttpResponseRedirect.
`form` is a bound Form instance that's verified to be valid.
"""
from django.contrib.admin.models import LogEntry, ADDITION
opts = self.model._meta
new_object = form.save(commit=True)
if formsets:
for formset in formsets:
# HACK: it seems like the parent obejct should be passed into
# a method of something, not just set as an attribute
formset.instance = new_object
formset.save()
pk_value = new_object._get_pk_val()
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), ADDITION)
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
# 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.message_set.create(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)
if request.POST.has_key("_popup"):
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
# escape() calls force_unicode.
(escape(pk_value), escape(new_object)))
elif request.POST.has_key("_addanother"):
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect(request.path)
else:
request.user.message_set.create(message=msg)
# Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise,
# redirect to the admin index.
if self.has_change_permission(request, None):
post_url = '../'
else:
post_url = '../../../'
return HttpResponseRedirect(post_url)
save_add = transaction.commit_on_success(save_add)
def save_change(self, request, form, formsets=None):
"""
Saves the object in the "change" stage and returns an HttpResponseRedirect.
`form` is a bound Form instance that's verified to be valid.
`formsets` is a sequence of InlineFormSet instances that are verified to be valid.
"""
from django.contrib.admin.models import LogEntry, CHANGE
opts = self.model._meta
new_object = form.save(commit=True)
pk_value = new_object._get_pk_val()
if formsets:
for formset in formsets:
formset.save()
# Construct the change message.
change_message = []
if form.changed_data:
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
if formsets:
for formset in formsets:
for added_object in formset.new_objects:
change_message.append(_('Added %(name)s "%(object)s".')
% {'name': added_object._meta.verbose_name,
'object': added_object})
for changed_object, changed_fields in formset.changed_objects:
change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
% {'list': get_text_list(changed_fields, _('and')),
'name': changed_object._meta.verbose_name,
'object': changed_object})
for deleted_object in formset.deleted_objects:
change_message.append(_('Deleted %(name)s "%(object)s".')
% {'name': deleted_object._meta.verbose_name,
'object': deleted_object})
change_message = ' '.join(change_message)
if not change_message:
change_message = _('No fields changed.')
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), CHANGE, change_message)
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
if request.POST.has_key("_continue"):
request.user.message_set.create(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.message_set.create(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.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect("../add/")
else:
request.user.message_set.create(message=msg)
return HttpResponseRedirect("../")
save_change = transaction.commit_on_success(save_change)
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
opts = self.model._meta
app_label = opts.app_label
ordered_objects = opts.get_ordered_objects()
context.update({
'add': add,
'change': change,
'has_add_permission': self.has_add_permission(request),
'has_change_permission': self.has_change_permission(request, obj),
'has_delete_permission': self.has_delete_permission(request, obj),
'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
'ordered_objects': ordered_objects,
'form_url': mark_safe(form_url),
'opts': opts,
'content_type_id': ContentType.objects.get_for_model(self.model).id,
'save_as': self.save_as,
'save_on_top': self.save_on_top,
'root_path': self.admin_site.root_path,
})
return render_to_response(self.change_form_template or [
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_form.html" % app_label,
"admin/change_form.html"
], context, context_instance=template.RequestContext(request))
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
model = self.model
opts = model._meta
app_label = opts.app_label
if not self.has_add_permission(request):
raise PermissionDenied
if self.has_change_permission(request, None):
# redirect to list view
post_url = '../'
else:
# Object list will give 'Permission Denied', so go back to admin home
post_url = '../../../'
ModelForm = self.get_form(request)
inline_formsets = []
obj = self.model()
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
for FormSet in self.get_formsets(request):
inline_formset = FormSet(data=request.POST, files=request.FILES,
instance=obj, save_as_new=request.POST.has_key("_saveasnew"))
inline_formsets.append(inline_formset)
if all_valid(inline_formsets) and form.is_valid():
return self.save_add(request, form, inline_formsets, '../%s/')
else:
form = ModelForm(initial=dict(request.GET.items()))
for FormSet in self.get_formsets(request):
inline_formset = FormSet(instance=obj)
inline_formsets.append(inline_formset)
adminForm = AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
media = self.media + adminForm.media
for fs in inline_formsets:
media = media + fs.media
inline_admin_formsets = []
for inline, formset in zip(self.inline_instances, inline_formsets):
fieldsets = list(inline.get_fieldsets(request))
inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
inline_admin_formsets.append(inline_admin_formset)
context = {
'title': _('Add %s') % opts.verbose_name,
'adminform': adminForm,
'is_popup': request.REQUEST.has_key('_popup'),
'show_delete': False,
'media': mark_safe(media),
'inline_admin_formsets': inline_admin_formsets,
'errors': AdminErrorList(form, inline_formsets),
'root_path': self.admin_site.root_path,
}
context.update(extra_context or {})
return self.render_change_form(request, context, add=True)
def change_view(self, request, object_id, extra_context=None):
"The 'change' admin view for this model."
model = self.model
opts = model._meta
app_label = opts.app_label
try:
obj = model._default_manager.get(pk=object_id)
except model.DoesNotExist:
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
# to determine whether a given object exists.
obj = None
if not self.has_change_permission(request, obj):
raise PermissionDenied
if obj is None:
raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
if request.POST and request.POST.has_key("_saveasnew"):
return self.add_view(request, form_url='../../add/')
ModelForm = self.get_form(request, obj)
inline_formsets = []
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES, instance=obj)
for FormSet in self.get_formsets(request, obj):
inline_formset = FormSet(request.POST, request.FILES, instance=obj)
inline_formsets.append(inline_formset)
if all_valid(inline_formsets) and form.is_valid():
return self.save_change(request, form, inline_formsets)
else:
form = ModelForm(instance=obj)
for FormSet in self.get_formsets(request, obj):
inline_formset = FormSet(instance=obj)
inline_formsets.append(inline_formset)
adminForm = AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
media = self.media + adminForm.media
for fs in inline_formsets:
media = media + fs.media
inline_admin_formsets = []
for inline, formset in zip(self.inline_instances, inline_formsets):
fieldsets = list(inline.get_fieldsets(request, obj))
inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
inline_admin_formsets.append(inline_admin_formset)
context = {
'title': _('Change %s') % opts.verbose_name,
'adminform': adminForm,
'object_id': object_id,
'original': obj,
'is_popup': request.REQUEST.has_key('_popup'),
'media': mark_safe(media),
'inline_admin_formsets': inline_admin_formsets,
'errors': AdminErrorList(form, inline_formsets),
'root_path': self.admin_site.root_path,
}
context.update(extra_context or {})
return self.render_change_form(request, context, change=True, obj=obj)
def changelist_view(self, request, extra_context=None):
"The 'change list' admin view for this model."
from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
opts = self.model._meta
app_label = opts.app_label
if not self.has_change_permission(request, None):
raise PermissionDenied
try:
cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter,
self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self)
except IncorrectLookupParameters:
# Wacky lookup parameters were given, so redirect to the main
# changelist page, without parameters, and pass an 'invalid=1'
# parameter via the query string. If wacky parameters were given and
# the 'invalid=1' parameter was already in the query string, something
# is screwed up with the database, so display an error page.
if ERROR_FLAG in request.GET.keys():
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
context = {
'title': cl.title,
'is_popup': cl.is_popup,
'cl': cl,
'has_add_permission': self.has_add_permission(request),
'root_path': self.admin_site.root_path,
}
context.update(extra_context or {})
return render_to_response(self.change_list_template or [
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
'admin/%s/change_list.html' % app_label,
'admin/change_list.html'
], context, context_instance=template.RequestContext(request))
def delete_view(self, request, object_id, extra_context=None):
"The 'delete' admin view for this model."
from django.contrib.admin.models import LogEntry, DELETION
opts = self.model._meta
app_label = opts.app_label
try:
obj = self.model._default_manager.get(pk=object_id)
except self.model.DoesNotExist:
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
# to determine whether a given object exists.
obj = None
if not self.has_delete_permission(request, obj):
raise PermissionDenied
if obj is None:
raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
perms_needed = sets.Set()
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
if request.POST: # The user has already confirmed the deletion.
if perms_needed:
raise PermissionDenied
obj_display = str(obj)
obj.delete()
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, object_id, obj_display, DELETION)
request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
if not self.has_change_permission(request, None):
return HttpResponseRedirect("../../../../")
return HttpResponseRedirect("../../")
context = {
"title": _("Are you sure?"),
"object_name": opts.verbose_name,
"object": obj,
"deleted_objects": deleted_objects,
"perms_lacking": perms_needed,
"opts": opts,
"root_path": self.admin_site.root_path,
}
context.update(extra_context or {})
return render_to_response(self.delete_confirmation_template or [
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
"admin/%s/delete_confirmation.html" % app_label,
"admin/delete_confirmation.html"
], context, context_instance=template.RequestContext(request))
def history_view(self, request, object_id, extra_context=None):
"The 'history' admin view for this model."
from django.contrib.admin.models import LogEntry
model = self.model
opts = model._meta
action_list = LogEntry.objects.filter(
object_id = object_id,
content_type__id__exact = ContentType.objects.get_for_model(model).id
).select_related().order_by('action_time')
# If no history was found, see whether this object even exists.
obj = get_object_or_404(model, pk=object_id)
context = {
'title': _('Change history: %s') % force_unicode(obj),
'action_list': action_list,
'module_name': capfirst(opts.verbose_name_plural),
'object': obj,
'root_path': self.admin_site.root_path,
}
context.update(extra_context or {})
return render_to_response(self.object_history_template or [
"admin/%s/%s/object_history.html" % (opts.app_label, opts.object_name.lower()),
"admin/%s/object_history.html" % opts.app_label,
"admin/object_history.html"
], context, context_instance=template.RequestContext(request))
class InlineModelAdmin(BaseModelAdmin):
"""
Options for inline editing of ``model`` instances.
Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
``model`` to its parent. This is required if ``model`` has more than one
``ForeignKey`` to its parent.
"""
model = None
fk_name = None
formset = BaseInlineFormset
extra = 3
max_num = 0
template = None
verbose_name = None
verbose_name_plural = None
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
self.opts = self.model._meta
super(InlineModelAdmin, self).__init__()
if self.verbose_name is None:
self.verbose_name = self.model._meta.verbose_name
if self.verbose_name_plural is None:
self.verbose_name_plural = self.model._meta.verbose_name_plural
def get_formset(self, request, obj=None):
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
fields = None
return inlineformset_factory(self.parent_model, self.model,
form=self.form, formset=self.formset, fk_name=self.fk_name,
fields=fields, formfield_callback=self.formfield_for_dbfield,
extra=self.extra, max_num=self.max_num)
def get_fieldsets(self, request, obj=None):
if self.declared_fieldsets:
return self.declared_fieldsets
form = self.get_formset(request).form
return [(None, {'fields': form.base_fields.keys()})]
class StackedInline(InlineModelAdmin):
template = 'admin/edit_inline/stacked.html'
class TabularInline(InlineModelAdmin):
template = 'admin/edit_inline/tabular.html'
class InlineAdminFormSet(object):
"""
A wrapper around an inline formset for use in the admin system.
"""
def __init__(self, inline, formset, fieldsets):
self.opts = inline
self.formset = formset
self.fieldsets = fieldsets
def __iter__(self):
for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
for form in self.formset.extra_forms:
yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
def fields(self):
for field_name in flatten_fieldsets(self.fieldsets):
yield self.formset.form.base_fields[field_name]
class InlineAdminForm(AdminForm):
"""
A wrapper around an inline form for use in the admin system.
"""
def __init__(self, formset, form, fieldsets, prepopulated_fields, original):
self.formset = formset
self.original = original
self.show_url = original and hasattr(original, 'get_absolute_url')
super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
def pk_field(self):
return AdminField(self.form, self.formset._pk_field_name, False)
def deletion_field(self):
from django.newforms.formsets import DELETION_FIELD_NAME
return AdminField(self.form, DELETION_FIELD_NAME, False)
def ordering_field(self):
from django.newforms.formsets import ORDERING_FIELD_NAME
return AdminField(self.form, ORDERING_FIELD_NAME, False)
class AdminErrorList(forms.util.ErrorList):
"""
Stores all errors for the form/formsets in an add/change stage view.
"""
def __init__(self, form, inline_formsets):
if form.is_bound:
self.extend(form.errors.values())
for inline_formset in inline_formsets:
self.extend(inline_formset.non_form_errors())
for errors_in_inline_form in inline_formset.errors:
self.extend(errors_in_inline_form.values())

View File

@ -0,0 +1,349 @@
from django import http, template
from django.contrib.admin import ModelAdmin
from django.contrib.auth import authenticate, login
from django.db.models.base import ModelBase
from django.shortcuts import render_to_response
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, ugettext as _
from django.views.decorators.cache import never_cache
from django.conf import settings
import base64
import cPickle as pickle
import datetime
import md5
import re
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
USER_CHANGE_PASSWORD_URL_RE = re.compile('auth/user/(\d+)/password')
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
def _encode_post_data(post_data):
from django.conf import settings
pickled = pickle.dumps(post_data)
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def _decode_post_data(encoded_data):
from django.conf import settings
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)
class AdminSite(object):
"""
An AdminSite object encapsulates an instance of the Django admin application, ready
to be hooked in to your URLConf. Models are registered with the AdminSite using the
register() method, and the root() method can then be used as a Django view function
that presents a full admin interface for the collection of registered models.
"""
index_template = None
login_template = None
def __init__(self):
self._registry = {} # model_class class -> admin_class instance
def register(self, model_or_iterable, admin_class=None, **options):
"""
Registers the given model(s) with the given admin class.
The model(s) should be Model classes, not instances.
If an admin class isn't given, it will use ModelAdmin (the default
admin options). If keyword arguments are given -- e.g., list_display --
they'll be applied as options to the admin class.
If a model is already registered, this will raise AlreadyRegistered.
"""
do_validate = admin_class and settings.DEBUG
if do_validate:
# don't import the humongous validation code unless required
from django.contrib.admin.validation import validate
admin_class = admin_class or ModelAdmin
# TODO: Handle options
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
if do_validate:
validate(admin_class, model)
self._registry[model] = admin_class(model, self)
def unregister(self, model_or_iterable):
"""
Unregisters the given model(s).
If a model isn't already registered, this will raise NotRegistered.
"""
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model not in self._registry:
raise NotRegistered('The model %s is not registered' % model.__name__)
del self._registry[model]
def has_permission(self, request):
"""
Returns True if the given HttpRequest has permission to view
*at least one* page in the admin site.
"""
return request.user.is_authenticated() and request.user.is_staff
def root(self, request, url):
"""
Handles main URL routing for the admin app.
`url` is the remainder of the URL -- e.g. 'comments/comment/'.
"""
if request.method == 'GET' and not request.path.endswith('/'):
return http.HttpResponseRedirect(request.path + '/')
# Figure out the admin base URL path and stash it for later use
self.root_path = re.sub(re.escape(url) + '$', '', request.path)
url = url.rstrip('/') # Trim trailing slash, if it exists.
# The 'logout' view doesn't require that the person is logged in.
if url == 'logout':
return self.logout(request)
# Check permission to continue or display login form.
if not self.has_permission(request):
return self.login(request)
if url == '':
return self.index(request)
elif url == 'password_change':
return self.password_change(request)
elif url == 'password_change/done':
return self.password_change_done(request)
elif url == 'jsi18n':
return self.i18n_javascript(request)
# urls starting with 'r/' are for the "show in web" links
elif url.startswith('r/'):
from django.views.defaults import shortcut
return shortcut(request, *url.split('/')[1:])
else:
match = USER_CHANGE_PASSWORD_URL_RE.match(url)
if match:
return self.user_change_password(request, match.group(1))
if '/' in url:
return self.model_page(request, *url.split('/', 2))
raise http.Http404('The requested admin page does not exist.')
def model_page(self, request, app_label, model_name, rest_of_url=None):
"""
Handles the model-specific functionality of the admin site, delegating
to the appropriate ModelAdmin class.
"""
from django.db import models
model = models.get_model(app_label, model_name)
if model is None:
raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
try:
admin_obj = self._registry[model]
except KeyError:
raise http.Http404("This model exists but has not been registered with the admin site.")
return admin_obj(request, rest_of_url)
model_page = never_cache(model_page)
def password_change(self, request):
"""
Handles the "change password" task -- both form display and validation.
"""
from django.contrib.auth.views import password_change
return password_change(request)
def password_change_done(self, request):
"""
Displays the "success" page after a password change.
"""
from django.contrib.auth.views import password_change_done
return password_change_done(request)
def user_change_password(self, request, id):
"""
Handles the "user change password" task
"""
from django.contrib.auth.views import user_change_password
return user_change_password(request, id)
def i18n_javascript(self, request):
"""
Displays the i18n JavaScript that the Django admin requires.
This takes into account the USE_I18N setting. If it's set to False, the
generated JavaScript will be leaner and faster.
"""
from django.conf import settings
if settings.USE_I18N:
from django.views.i18n import javascript_catalog
else:
from django.views.i18n import null_javascript_catalog as javascript_catalog
return javascript_catalog(request, packages='django.conf')
def logout(self, request):
"""
Logs out the user for the given HttpRequest.
This should *not* assume the user is already logged in.
"""
from django.contrib.auth.views import logout
return logout(request)
logout = never_cache(logout)
def login(self, request):
"""
Displays the login form for the given HttpRequest.
"""
from django.contrib.auth.models import User
# If this isn't already the login page, display it.
if not request.POST.has_key(LOGIN_FORM_KEY):
if request.POST:
message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.")
else:
message = ""
return self.display_login_form(request, message)
# Check that the user accepts cookies.
if not request.session.test_cookie_worked():
message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
return self.display_login_form(request, message)
# Check the password.
username = request.POST.get('username', None)
password = request.POST.get('password', None)
user = authenticate(username=username, password=password)
if user is None:
message = ERROR_MESSAGE
if u'@' in username:
# Mistakenly entered e-mail address instead of username? Look it up.
try:
user = User.objects.get(email=username)
except (User.DoesNotExist, User.MultipleObjectsReturned):
message = _("Usernames cannot contain the '@' character.")
else:
if user.check_password(password):
message = _("Your e-mail address is not your username."
" Try '%s' instead.") % user.username
else:
message = _("Usernames cannot contain the '@' character.")
return self.display_login_form(request, message)
# The user data is correct; log in the user in and continue.
else:
if user.is_active and user.is_staff:
login(request, user)
# TODO: set last_login with an event.
user.last_login = datetime.datetime.now()
user.save()
if request.POST.has_key('post_data'):
post_data = _decode_post_data(request.POST['post_data'])
if post_data and not post_data.has_key(LOGIN_FORM_KEY):
# overwrite request.POST with the saved post_data, and continue
request.POST = post_data
request.user = user
return self.root(request, request.path.split(self.root_path)[-1])
else:
request.session.delete_test_cookie()
return http.HttpResponseRedirect(request.path)
else:
return self.display_login_form(request, ERROR_MESSAGE)
login = never_cache(login)
def index(self, request, extra_context=None):
"""
Displays the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
app_dict = {}
user = request.user
for model, model_admin in self._registry.items():
app_label = model._meta.app_label
has_module_perms = user.has_module_perms(app_label)
if has_module_perms:
perms = {
'add': model_admin.has_add_permission(request),
'change': model_admin.has_change_permission(request),
'delete': model_admin.has_delete_permission(request),
}
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms.values():
model_dict = {
'name': capfirst(model._meta.verbose_name_plural),
'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
'perms': perms,
}
if app_label in app_dict:
app_dict[app_label]['models'].append(model_dict)
else:
app_dict[app_label] = {
'name': app_label.title(),
'has_module_perms': has_module_perms,
'models': [model_dict],
}
# Sort the apps alphabetically.
app_list = app_dict.values()
app_list.sort(lambda x, y: cmp(x['name'], y['name']))
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
context = {
'title': _('Site administration'),
'app_list': app_list,
'root_path': self.root_path,
}
context.update(extra_context or {})
return render_to_response(self.index_template or 'admin/index.html', context,
context_instance=template.RequestContext(request)
)
index = never_cache(index)
def display_login_form(self, request, error_message='', extra_context=None):
request.session.set_test_cookie()
if request.POST and request.POST.has_key('post_data'):
# User has failed login BUT has previously saved post data.
post_data = request.POST['post_data']
elif request.POST:
# User's session must have expired; save their post data.
post_data = _encode_post_data(request.POST)
else:
post_data = _encode_post_data({})
context = {
'title': _('Log in'),
'app_path': request.path,
'post_data': post_data,
'error_message': error_message,
'root_path': self.root_path,
}
context.update(extra_context or {})
return render_to_response(self.login_template or 'admin/login.html', context,
context_instance=template.RequestContext(request)
)
# This global object represents the default admin site, for the common case.
# You can instantiate AdminSite in your own code to create a custom admin site.
site = AdminSite()

View File

@ -8,21 +8,26 @@
<fieldset class="module aligned">
<div class="form-row">
{{ form.username.html_error_list }}
{{ form.username.errors }}
{# TODO: get required class on label_tag #}
<label for="id_username" class="required">{% trans 'Username' %}:</label> {{ form.username }}
<p class="help">{{ username_help_text }}</p>
<p class="help">{{ form.username.help_text }}</p>
</div>
<div class="form-row">
{{ form.password1.html_error_list }}
{{ form.password1.errors }}
{# TODO: get required class on label_tag #}
<label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
</div>
<div class="form-row">
{{ form.password2.html_error_list }}
{{ form.password2.errors }}
{# TODO: get required class on label_tag #}
<label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
<p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
</div>
<script type="text/javascript">document.getElementById("id_username").focus();</script>
</fieldset>
{% endblock %}

View File

@ -2,7 +2,6 @@
{% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../../jsi18n/"></script>
{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
@ -18,9 +17,9 @@
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if form.error_dict %}
{% if form.errors %}
<p class="errornote">
{% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
{% blocktrans count form.errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{% endif %}
@ -29,12 +28,14 @@
<fieldset class="module aligned">
<div class="form-row">
{{ form.password1.html_error_list }}
{{ form.password1.errors }}
{# TODO: get required class on label_tag #}
<label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
</div>
<div class="form-row">
{{ form.password2.html_error_list }}
{{ form.password2.errors }}
{# TODO: get required class on label_tag #}
<label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
<p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
</div>
@ -45,7 +46,7 @@
<input type="submit" value="{% trans 'Change password' %}" class="default" />
</div>
<script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
<script type="text/javascript">document.getElementById("id_password1").focus();</script>
</div>
</form></div>
{% endblock %}

View File

@ -22,14 +22,7 @@
{% block branding %}{% endblock %}
</div>
{% if user.is_authenticated and user.is_staff %}
<div id="user-tools">
{% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>.
{% block userlinks %}
<a href="{% url django.contrib.admin.views.doc.doc_index %}">{% trans 'Documentation' %}</a>
/ <a href="{% url django.contrib.auth.views.password_change %}">{% trans 'Change password' %}</a>
/ <a href="{% url django.contrib.auth.views.logout %}">{% trans 'Log out' %}</a>
{% endblock %}
</div>
<div id="user-tools">{% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>. {% block userlinks %}<a href="{{ root_path }}doc/">{% trans 'Documentation' %}</a> / <a href="{{ root_path }}password_change/">{% trans 'Change password' %}</a> / <a href="{{ root_path }}logout/">{% trans 'Log out' %}</a>{% endblock %}</div>
{% endif %}
{% block nav-global %}{% endblock %}
</div>

View File

@ -1,12 +1,17 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../jsi18n/"></script>
{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{{ media }}
{% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../../">{% trans "Home" %}</a> &rsaquo;
@ -14,6 +19,7 @@
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
{% block object-tools %}
{% if change %}{% if not is_popup %}
@ -25,45 +31,48 @@
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if opts.admin.save_on_top %}{% submit_row %}{% endif %}
{% if form.error_dict %}
{% if save_on_top %}{% submit_row %}{% endif %}
{% if errors %}
<p class="errornote">
{% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
{% blocktrans count errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
<ul class="errorlist">{% for error in adminform.form.non_field_errors %}<li>{{ error }}</li>{% endfor %}</ul>
{% endif %}
{% for bound_field_set in bound_field_sets %}
<fieldset class="module aligned {{ bound_field_set.classes }}">
{% if bound_field_set.name %}<h2>{{ bound_field_set.name }}</h2>{% endif %}
{% if bound_field_set.description %}<div class="description">{{ bound_field_set.description|safe }}</div>{% endif %}
{% for bound_field_line in bound_field_set %}
{% admin_field_line bound_field_line %}
{% for bound_field in bound_field_line %}
{% filter_interface_script_maybe bound_field %}
{% endfor %}
{% endfor %}
</fieldset>
{% for fieldset in adminform %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
{% block after_field_sets %}{% endblock %}
{% if change %}
{% if ordered_objects %}
<fieldset class="module"><h2>{% trans "Ordering" %}</h2>
<div class="form-row{% if form.order_.errors %} error{% endif %} ">
{% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
<p><label for="id_order_">{% trans "Order:" %}</label> {{ form.order_ }}</p>
</div></fieldset>
{% endif %}
{% endif %}
{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% for inline_admin_formset in inline_admin_formsets %}
{% include inline_admin_formset.opts.template %}
{% endfor %}
{% block after_related_objects %}{% endblock %}
{% submit_row %}
{% if add %}
<script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
<script type="text/javascript">document.getElementById("{{ adminform.first_field.auto_id }}").focus();</script>
{% endif %}
{% if auto_populated_fields %}
<script type="text/javascript">
{% auto_populated_field_script auto_populated_fields change %}
</script>
{# JavaScript for prepopulated fields #}
{% if add %}
<script type="text/javascript">
{% for field in adminform.prepopulated_fields %}
document.getElementById("{{ field.field.auto_id }}").onchange = function() { this._changed = true; };
{% for dependency in field.dependencies %}
document.getElementById("{{ dependency.auto_id }}").onkeyup = function() {
var e = document.getElementById("{{ field.field.auto_id }}");
if (!e._changed) { e.value = URLify({% for innerdep in field.dependencies %}document.getElementById("{{ innerdep.auto_id }}").value{% if not forloop.last %} + ' ' + {% endif %}{% endfor %}, {{ field.field.field.max_length }}); }
}
{% endfor %}
{% endfor %}
</script>
{% endif %}
</div>
</form></div>
{% endblock %}

View File

@ -1,9 +1,14 @@
{% extends "admin/base_site.html" %}
{% load adminmedia admin_list i18n %}
{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst|escape }}</div>{% endblock %}{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
{% block object-tools %}
@ -14,7 +19,18 @@
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
{% block search %}{% search_form cl %}{% endblock %}
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
{% block filters %}{% filters cl %}{% endblock %}
{% block filters %}
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% trans 'Filter' %}</h2>
{% for spec in cl.filter_specs %}
{% admin_list_filter cl spec %}
{% endfor %}
</div>
{% endif %}
{% endblock %}
{% block result_list %}{% result_list cl %}{% endblock %}
{% block pagination %}{% pagination cl %}{% endblock %}
</div>

View File

@ -1,5 +1,6 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../../../">{% trans "Home" %}</a> &rsaquo;
@ -8,6 +9,7 @@
{% trans 'Delete' %}
</div>
{% endblock %}
{% block content %}
{% if perms_lacking %}
<p>{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>

View File

@ -0,0 +1,26 @@
{% load i18n %}
<div class="inline-group">
{{ inline_admin_formset.formset.management_form }}
{# <h3 class="header">{{ inline_admin_formset.opts.verbose_name_plural|title }}</h3> #}
{{ inline_admin_formset.formset.non_form_errors }}
{% for inline_admin_form in inline_admin_formset %}
<div class="inline-related {% if forloop.last %}last-related{% endif %}">
<h2><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %}
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
</h2>
{% if inline_admin_form.show_url %}
<p><a href="/r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">View on site</a></p>
{% endif %}
{% for fieldset in inline_admin_form %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
{{ inline_admin_form.pk_field.field }}
</div>
{% endfor %}
{# <ul class="tools"> #}
{# <li><a class="add" href="">Add another {{ inline_admin_formset.opts.verbose_name|title }}</a></li> #}
{# </ul> #}
</div>

View File

@ -0,0 +1,64 @@
{% load i18n %}
<div class="inline-group">
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
{{ inline_admin_formset.formset.management_form }}
<fieldset class="module">
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst|escape }}</h2>
{{ inline_admin_formset.formset.non_form_errors }}
<table>
<thead><tr>
{% for field in inline_admin_formset.fields %}
{% if not field.is_hidden %}
<th {% if forloop.first %}colspan="2"{% endif %}>{{ field.label|capfirst|escape }}</th>
{% endif %}
{% endfor %}
{% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete" %}?</th>{% endif %}
</tr></thead>
{% for inline_admin_form in inline_admin_formset %}
<tr class="{% cycle row1,row2 %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}">
<td class="original">{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
{% if inline_admin_form.show_url %}<a href="/r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">View on site</a>{% endif %}
</p>{% endif %}
{{ inline_admin_form.pk_field.field }}
{% spaceless %}
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if field.is_hidden %} {{ field.field }} {% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endspaceless %}
</td>
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
<td class="{{ field.field.name }}">
{{ field.field.errors.as_ul }}
{{ field.field }}
</td>
{% endfor %}
{% endfor %}
{% endfor %}
{% if inline_admin_formset.formset.can_delete %}<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>{% endif %}
</tr>
{% endfor %}
</table>
</fieldset>
</div>
{# <ul class="tools"> #}
{# <li><a class="add" href="">Add another {{ inline_admin_formset.opts.verbose_name|title }}</a></li> #}
{# </ul> #}
</div>

View File

@ -1,16 +0,0 @@
{% load admin_modify %}
<fieldset class="module aligned">
{% for fcw in bound_related_object.form_field_collection_wrappers %}
<h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }}&nbsp;#{{ forloop.counter }}</h2>
{% if bound_related_object.show_url %}{% if fcw.obj.original %}
<p><a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a></p>
{% endif %}{% endif %}
{% for bound_field in fcw.bound_fields %}
{% if bound_field.hidden %}
{% field_widget bound_field %}
{% else %}
{% admin_field_line bound_field %}
{% endif %}
{% endfor %}
{% endfor %}
</fieldset>

View File

@ -1,44 +0,0 @@
{% load admin_modify %}
<fieldset class="module">
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table>
<thead><tr>
{% for fw in bound_related_object.field_wrapper_list %}
{% if fw.needs_header %}
<th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
{% endif %}
{% endfor %}
</tr></thead>
{% for fcw in bound_related_object.form_field_collection_wrappers %}
{% if change %}{% if original_row_needed %}
{% if fcw.obj.original %}
<tr class="row-label {% cycle row1,row2 %}"><td colspan="{{ num_headers }}"><strong>{{ fcw.obj.original }}</strong></tr>
{% endif %}
{% endif %}{% endif %}
{% if fcw.obj.errors %}
<tr class="errorlist"><td colspan="{{ num_headers }}">
{{ fcw.obj.html_combined_error_list }}
</tr>
{% endif %}
<tr class="{% cycle row1,row2 %}">
{% for bound_field in fcw.bound_fields %}
{% if not bound_field.hidden %}
<td {{ bound_field.cell_class_attribute }}>
{% field_widget bound_field %}
</td>
{% endif %}
{% endfor %}
{% if bound_related_object.show_url %}<td>
{% if fcw.obj.original %}<a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a>{% endif %}
</td>{% endif %}
</tr>
{% endfor %} </table>
{% for fcw in bound_related_object.form_field_collection_wrappers %}
{% for bound_field in fcw.bound_fields %}
{% if bound_field.hidden %}
{% field_widget bound_field %}
{% endif %}
{% endfor %}
{% endfor %}
</fieldset>

View File

@ -1,10 +0,0 @@
{% load admin_modify %}
<div class="{{ class_names }}" >
{% for bound_field in bound_fields %}{{ bound_field.html_error_list }}{% endfor %}
{% for bound_field in bound_fields %}
{% if bound_field.has_label_first %}{% field_label bound_field %}{% endif %}
{% field_widget bound_field %}
{% if not bound_field.has_label_first %}{% field_label bound_field %}{% endif %}
{% if bound_field.field.help_text %}<p class="help">{{ bound_field.field.help_text|safe }}</p>{% endif %}
{% endfor %}
</div>

View File

@ -1,7 +0,0 @@
{% load admin_list %}
{% load i18n %}
{% if cl.has_filters %}<div id="changelist-filter">
<h2>{% trans 'Filter' %}</h2>
{% for spec in cl.filter_specs %}
{% filter cl spec %}
{% endfor %}</div>{% endif %}

View File

@ -0,0 +1,17 @@
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}<div class="description">{{ fieldset.description }}</div>{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
{{ line.errors }}
{% for field in line %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}{{ field.field }}
{% endif %}
{% if field.field.field.help_text %}<p class="help">{{ field.field.field.help_text|safe }}</p>{% endif %}
{% endfor %}
</div>
{% endfor %}
</fieldset>

View File

@ -2,15 +2,16 @@
{% load i18n %}
{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}dashboard{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content %}
<div id="content-main">
{% load adminapplist %}
{% get_admin_app_list as app_list %}
{% if app_list %}
{% for app in app_list %}
<div class="module">

View File

@ -4,7 +4,5 @@
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title }}</div>{% endblock %}
{% block content %}
<p>{% trans "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." %}</p>
{% endblock %}

View File

@ -2,12 +2,14 @@
{% load i18n %}
{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/login.css{% endblock %}
{% block bodyclass %}login{% endblock %}
{% block content_title %}{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content %}
{% if error_message %}
<p class="errornote">{{ error_message }}</p>
{% endif %}

View File

@ -1,16 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}
<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> &rsaquo; <a href="../../">{{ module_name }}</a> &rsaquo; <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo; {% trans 'History' %}</div>
{% endblock %}
{% block content %}
<div id="content-main">
<div class="module">
{% if action_list %}
<table id="change-history">
<thead>
<tr>
@ -29,14 +28,9 @@
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}</p>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% load adminmedia %}
{% load i18n %}
{% if cl.lookup_opts.admin.search_fields %}
{% if cl.search_fields %}
<div id="toolbar"><form id="changelist-search" action="" method="get">
<div><!-- DIV needed for valid HTML -->
<label for="searchbar"><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>

View File

@ -25,3 +25,4 @@
</div>
{% endblock %}

View File

@ -40,3 +40,4 @@
</div>
{% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / {% trans 'Change password' %} / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
{% block title %}{% trans 'Password change successful' %}{% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block userlinks %}<a href="../doc/">{% trans 'Documentation' %}</a> / {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
{% block title %}{% trans 'Password change' %}{% endblock %}

View File

@ -12,7 +12,7 @@
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." %}</p>
<form action="" method="post">
{% if form.email.errors %}{{ form.email.html_error_list }}{% endif %}
{% if form.email.errors %}{{ form.email.errors }}{% endif %}
<p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
</form>

View File

@ -1,5 +0,0 @@
{% load i18n %}
<p class="datetime">
{% trans "Date:" %} {{ bound_field.form_fields.0 }}<br />
{% trans "Time:" %} {{ bound_field.form_fields.1 }}
</p>

View File

@ -1 +0,0 @@
{% load admin_modify %}{% output_all bound_field.form_fields %}

View File

@ -1,4 +0,0 @@
{% load admin_modify i18n %}{% if bound_field.original_value %}
{% trans "Currently:" %} <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value|escape }} </a><br />
{% trans "Change:" %}{% output_all bound_field.form_fields %}
{% else %} {% output_all bound_field.form_fields %} {% endif %}

View File

@ -1,20 +0,0 @@
{% load admin_modify adminmedia %}
{% output_all bound_field.form_fields %}
{% if bound_field.raw_id_admin %}
{% if bound_field.field.rel.limit_choices_to %}
<a href="{{ bound_field.related_url }}?{% for limit_choice in bound_field.field.rel.limit_choices_to.items %}{% if not forloop.first %}&amp;{% endif %}{{ limit_choice|join:"=" }}{% endfor %}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
{% else %}
<a href="{{ bound_field.related_url }}" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
{% endif %}
{% else %}
{% if bound_field.needs_add_label %}
<a href="{{ bound_field.related_url }}add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
{% endif %}{% endif %}
{% if change %}
{% if bound_field.field.primary_key %}
{{ bound_field.original_value }}
{% endif %}
{% if bound_field.raw_id_admin %}
{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
{% endif %}
{% endif %}

View File

@ -1 +0,0 @@
{% include "widget/foreign.html" %}

View File

@ -1,2 +0,0 @@
{% if add %}{% include "widget/foreign.html" %}{% endif %}
{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}

View File

@ -71,7 +71,7 @@ pagination = register.inclusion_tag('admin/pagination.html')(pagination)
def result_headers(cl):
lookup_opts = cl.lookup_opts
for i, field_name in enumerate(lookup_opts.admin.list_display):
for i, field_name in enumerate(cl.list_display):
try:
f = lookup_opts.get_field(field_name)
admin_order_field = None
@ -123,7 +123,7 @@ def _boolean_icon(field_val):
def items_for_result(cl, result):
first = True
pk = cl.lookup_opts.pk.attname
for field_name in cl.lookup_opts.admin.list_display:
for field_name in cl.list_display:
row_class = ''
try:
f = cl.lookup_opts.get_field(field_name)
@ -189,7 +189,7 @@ def items_for_result(cl, result):
if force_unicode(result_repr) == '':
result_repr = mark_safe('&nbsp;')
# If list_display_links not defined, add the link tag to the first field
if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links:
if (first and not cl.list_display_links) or field_name in cl.list_display_links:
table_tag = {True:'th', False:'td'}[first]
first = False
url = cl.url_for_result(result)
@ -212,8 +212,8 @@ def result_list(cl):
result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
def date_hierarchy(cl):
if cl.lookup_opts.admin.date_hierarchy:
field_name = cl.lookup_opts.admin.date_hierarchy
if cl.date_hierarchy:
field_name = cl.date_hierarchy
year_field = '%s__year' % field_name
month_field = '%s__month' % field_name
day_field = '%s__day' % field_name
@ -280,10 +280,6 @@ def search_form(cl):
}
search_form = register.inclusion_tag('admin/search_form.html')(search_form)
def filter(cl, spec):
def admin_list_filter(cl, spec):
return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
filter = register.inclusion_tag('admin/filter.html')(filter)
def filters(cl):
return {'cl': cl}
filters = register.inclusion_tag('admin/filters.html')(filters)
admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter)

View File

@ -1,253 +1,21 @@
from django import template
from django.contrib.admin.views.main import AdminBoundField
from django.template import loader
from django.utils.text import capfirst
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.db import models
from django.db.models.fields import Field
from django.db.models.related import BoundRelatedObject
from django.conf import settings
import re
register = template.Library()
word_re = re.compile('[A-Z][a-z]+')
absolute_url_re = re.compile(r'^(?:http(?:s)?:/)?/', re.IGNORECASE)
def class_name_to_underscored(name):
return u'_'.join([s.lower() for s in word_re.findall(name)[:-1]])
def include_admin_script(script_path):
"""
Returns an HTML script element for including a script from the admin
media url (or other location if an absolute url is given).
Example usage::
{% include_admin_script "js/calendar.js" %}
could return::
<script type="text/javascript" src="/media/admin/js/calendar.js">
"""
if not absolute_url_re.match(script_path):
script_path = '%s%s' % (settings.ADMIN_MEDIA_PREFIX, script_path)
return mark_safe(u'<script type="text/javascript" src="%s"></script>'
% script_path)
include_admin_script = register.simple_tag(include_admin_script)
def submit_row(context):
opts = context['opts']
change = context['change']
is_popup = context['is_popup']
save_as = context['save_as']
return {
'onclick_attrib': (opts.get_ordered_objects() and change
and 'onclick="submitOrderForm();"' or ''),
'show_delete_link': (not is_popup and context['has_delete_permission']
and (change or context['show_delete'])),
'show_save_as_new': not is_popup and change and opts.admin.save_as,
'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']),
'show_save_as_new': not is_popup and change and save_as,
'show_save_and_add_another': context['has_add_permission'] and
not is_popup and (not save_as or context['add']),
'show_save_and_continue': not is_popup and context['has_change_permission'],
'show_save': True
}
submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row)
def field_label(bound_field):
class_names = []
if isinstance(bound_field.field, models.BooleanField):
class_names.append("vCheckboxLabel")
colon = ""
else:
if not bound_field.field.blank:
class_names.append('required')
if not bound_field.first:
class_names.append('inline')
colon = ":"
class_str = class_names and u' class="%s"' % u' '.join(class_names) or u''
return mark_safe(u'<label for="%s"%s>%s%s</label> ' %
(bound_field.element_id, class_str,
escape(force_unicode(capfirst(bound_field.field.verbose_name))),
colon))
field_label = register.simple_tag(field_label)
class FieldWidgetNode(template.Node):
nodelists = {}
default = None
def __init__(self, bound_field_var):
self.bound_field_var = template.Variable(bound_field_var)
def get_nodelist(cls, klass):
if klass not in cls.nodelists:
try:
field_class_name = klass.__name__
template_name = u"widget/%s.html" % class_name_to_underscored(field_class_name)
nodelist = loader.get_template(template_name).nodelist
except template.TemplateDoesNotExist:
super_klass = bool(klass.__bases__) and klass.__bases__[0] or None
if super_klass and super_klass != Field:
nodelist = cls.get_nodelist(super_klass)
else:
if not cls.default:
cls.default = loader.get_template("widget/default.html").nodelist
nodelist = cls.default
cls.nodelists[klass] = nodelist
return nodelist
else:
return cls.nodelists[klass]
get_nodelist = classmethod(get_nodelist)
def render(self, context):
bound_field = self.bound_field_var.resolve(context)
context.push()
context['bound_field'] = bound_field
output = self.get_nodelist(bound_field.field.__class__).render(context)
context.pop()
return output
class FieldWrapper(object):
def __init__(self, field ):
self.field = field
def needs_header(self):
return not isinstance(self.field, models.AutoField)
def header_class_attribute(self):
return self.field.blank and mark_safe(' class="optional"') or ''
def use_raw_id_admin(self):
return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \
and self.field.rel.raw_id_admin
class FormFieldCollectionWrapper(object):
def __init__(self, field_mapping, fields, index):
self.field_mapping = field_mapping
self.fields = fields
self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original'])
for field in self.fields]
self.index = index
class TabularBoundRelatedObject(BoundRelatedObject):
def __init__(self, related_object, field_mapping, original):
super(TabularBoundRelatedObject, self).__init__(related_object, field_mapping, original)
self.field_wrapper_list = [FieldWrapper(field) for field in self.relation.editable_fields()]
fields = self.relation.editable_fields()
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields, i)
for (i,field_mapping) in self.field_mappings.items() ]
self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list])
self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
def template_name(self):
return "admin/edit_inline_tabular.html"
class StackedBoundRelatedObject(BoundRelatedObject):
def __init__(self, related_object, field_mapping, original):
super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original)
fields = self.relation.editable_fields()
self.field_mappings.fill()
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields, i)
for (i,field_mapping) in self.field_mappings.items()]
self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
def template_name(self):
return "admin/edit_inline_stacked.html"
class EditInlineNode(template.Node):
def __init__(self, rel_var):
self.rel_var = template.Variable(rel_var)
def render(self, context):
relation = self.rel_var.resolve(context)
context.push()
if relation.field.rel.edit_inline == models.TABULAR:
bound_related_object_class = TabularBoundRelatedObject
elif relation.field.rel.edit_inline == models.STACKED:
bound_related_object_class = StackedBoundRelatedObject
else:
bound_related_object_class = relation.field.rel.edit_inline
original = context.get('original', None)
bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
context['bound_related_object'] = bound_related_object
t = loader.get_template(bound_related_object.template_name())
output = t.render(context)
context.pop()
return output
def output_all(form_fields):
return u''.join([force_unicode(f) for f in form_fields])
output_all = register.simple_tag(output_all)
def auto_populated_field_script(auto_pop_fields, change = False):
t = []
for field in auto_pop_fields:
if change:
t.append(u'document.getElementById("id_%s")._changed = true;' % field.name)
else:
t.append(u'document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
add_values = u' + " " + '.join([u'document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
for f in field.prepopulate_from:
t.append(u'document.getElementById("id_%s").onkeyup = function() {' \
' var e = document.getElementById("id_%s");' \
' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % (
f, field.name, add_values, field.max_length))
return mark_safe(u''.join(t))
auto_populated_field_script = register.simple_tag(auto_populated_field_script)
def filter_interface_script_maybe(bound_field):
f = bound_field.field
if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
return mark_safe(u'<script type="text/javascript">addEvent(window, "load", function(e) {' \
' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
f.name, escape(f.verbose_name.replace('"', '\\"')), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX))
else:
return ''
filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)
def field_widget(parser, token):
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
return FieldWidgetNode(bits[1])
field_widget = register.tag(field_widget)
def edit_inline(parser, token):
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0]
return EditInlineNode(bits[1])
edit_inline = register.tag(edit_inline)
def admin_field_line(context, argument_val):
if isinstance(argument_val, AdminBoundField):
bound_fields = [argument_val]
else:
bound_fields = [bf for bf in argument_val]
add = context['add']
change = context['change']
class_names = ['form-row']
for bound_field in bound_fields:
for f in bound_field.form_fields:
if f.errors():
class_names.append('errors')
break
# Assumes BooleanFields won't be stacked next to each other!
if isinstance(bound_fields[0].field, models.BooleanField):
class_names.append('checkbox-row')
return {
'add': context['add'],
'change': context['change'],
'bound_fields': bound_fields,
'class_names': " ".join(class_names),
}
admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line)

View File

@ -1,81 +0,0 @@
from django import template
from django.db.models import get_models
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
register = template.Library()
class AdminApplistNode(template.Node):
def __init__(self, varname):
self.varname = varname
def render(self, context):
from django.db import models
from django.utils.text import capfirst
app_list = []
user = context['user']
for app in models.get_apps():
# Determine the app_label.
app_models = get_models(app)
if not app_models:
continue
app_label = app_models[0]._meta.app_label
has_module_perms = user.has_module_perms(app_label)
if has_module_perms:
model_list = []
for m in app_models:
if m._meta.admin:
perms = {
'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())),
'change': user.has_perm("%s.%s" % (app_label, m._meta.get_change_permission())),
'delete': user.has_perm("%s.%s" % (app_label, m._meta.get_delete_permission())),
}
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms.values():
model_list.append({
'name': force_unicode(capfirst(m._meta.verbose_name_plural)),
'admin_url': mark_safe(u'%s/%s/' % (force_unicode(app_label), m.__name__.lower())),
'perms': perms,
})
if model_list:
# Sort using verbose decorate-sort-undecorate pattern
# instead of key argument to sort() for python 2.3 compatibility
decorated = [(x['name'], x) for x in model_list]
decorated.sort()
model_list = [x for key, x in decorated]
app_list.append({
'name': app_label.title(),
'has_module_perms': has_module_perms,
'models': model_list,
})
context[self.varname] = app_list
return ''
def get_admin_app_list(parser, token):
"""
Returns a list of installed applications and models for which the current user
has at least one permission.
Syntax::
{% get_admin_app_list as [context_var_containing_app_list] %}
Example usage::
{% get_admin_app_list as admin_app_list %}
"""
tokens = token.contents.split()
if len(tokens) < 3:
raise template.TemplateSyntaxError, "'%s' tag requires two arguments" % tokens[0]
if tokens[1] != 'as':
raise template.TemplateSyntaxError, "First argument to '%s' tag must be 'as'" % tokens[0]
return AdminApplistNode(tokens[2])
register.tag('get_admin_app_list', get_admin_app_list)

View File

@ -1,43 +0,0 @@
from django.conf import settings
from django.conf.urls.defaults import *
if settings.USE_I18N:
i18n_view = 'django.views.i18n.javascript_catalog'
else:
i18n_view = 'django.views.i18n.null_javascript_catalog'
urlpatterns = patterns('',
('^$', 'django.contrib.admin.views.main.index'),
('^r/', include('django.conf.urls.shortcut')),
('^jsi18n/$', i18n_view, {'packages': 'django.conf'}),
('^logout/$', 'django.contrib.auth.views.logout'),
('^password_change/$', 'django.contrib.auth.views.password_change'),
('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
('^template_validator/$', 'django.contrib.admin.views.template.template_validator'),
# Documentation
('^doc/$', 'django.contrib.admin.views.doc.doc_index'),
('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'),
('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'),
('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'),
('^doc/views/$', 'django.contrib.admin.views.doc.view_index'),
('^doc/views/(?P<view>[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'),
('^doc/models/$', 'django.contrib.admin.views.doc.model_index'),
('^doc/models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'),
# ('^doc/templates/$', 'django.views.admin.doc.template_index'),
('^doc/templates/(?P<template>.*)/$', 'django.contrib.admin.views.doc.template_detail'),
# "Add user" -- a special-case view
('^auth/user/add/$', 'django.contrib.admin.views.auth.user_add_stage'),
# "Change user password" -- another special-case view
('^auth/user/(\d+)/password/$', 'django.contrib.admin.views.auth.user_change_password'),
# Add/change/delete/history
('^([^/]+)/([^/]+)/$', 'django.contrib.admin.views.main.change_list'),
('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
('^([^/]+)/([^/]+)/(.+)/history/$', 'django.contrib.admin.views.main.history'),
('^([^/]+)/([^/]+)/(.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
('^([^/]+)/([^/]+)/(.+)/$', 'django.contrib.admin.views.main.change_stage'),
)
del i18n_view

View File

@ -0,0 +1,139 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext as _
def quote(s):
"""
Ensure that primary key values do not confuse the admin URLs by escaping
any '/', '_' and ':' characters. Similar to urllib.quote, except that the
quoting is slightly different so that it doesn't get automatically
unquoted by the Web browser.
"""
if not isinstance(s, basestring):
return s
res = list(s)
for i in range(len(res)):
c = res[i]
if c in """:/_#?;@&=+$,"<>%\\""":
res[i] = '_%02X' % ord(c)
return ''.join(res)
def unquote(s):
"""
Undo the effects of quote(). Based heavily on urllib.unquote().
"""
mychr = chr
myatoi = int
list = s.split('_')
res = [list[0]]
myappend = res.append
del list[0]
for item in list:
if item[1:2]:
try:
myappend(mychr(myatoi(item[:2], 16)) + item[2:])
except ValueError:
myappend('_' + item)
else:
myappend('_' + item)
return "".join(res)
def _nest_help(obj, depth, val):
current = obj
for i in range(depth):
current = current[-1]
current.append(val)
def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site):
"Helper function that recursively populates deleted_objects."
nh = _nest_help # Bind to local variable for performance
if current_depth > 16:
return # Avoid recursing too deep.
opts_seen = []
for related in opts.get_all_related_objects():
has_admin = related.model in admin_site._registry
if related.opts in opts_seen:
continue
opts_seen.append(related.opts)
rel_opts_name = related.get_accessor_name()
if isinstance(related.field.rel, models.OneToOneRel):
try:
sub_obj = getattr(obj, rel_opts_name)
except ObjectDoesNotExist:
pass
else:
if has_admin:
p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)
# We don't care about populating deleted_objects now.
continue
if related.field.rel.edit_inline or not has_admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' %
(escape(force_unicode(capfirst(related.opts.verbose_name))),
related.opts.app_label,
related.opts.object_name.lower(),
sub_obj._get_pk_val(), sub_obj)), []])
get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site)
else:
has_related_objs = False
for sub_obj in getattr(obj, rel_opts_name).all():
has_related_objs = True
if related.field.rel.edit_inline or not has_admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
(escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj))), []])
get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site)
# If there were related objects, and the user doesn't have
# permission to delete them, add the missing perm to perms_needed.
if has_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(related.opts.verbose_name)
for related in opts.get_all_related_many_to_many_objects():
has_admin = related.model in admin_site._registry
if related.opts in opts_seen:
continue
opts_seen.append(related.opts)
rel_opts_name = related.get_accessor_name()
has_related_objs = False
# related.get_accessor_name() could return None for symmetrical relationships
if rel_opts_name:
rel_objs = getattr(obj, rel_opts_name, None)
if rel_objs:
has_related_objs = True
if has_related_objs:
for sub_obj in rel_objs.all():
if related.field.rel.edit_inline or not has_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 %(fieldname)s in %(name)s: %(obj)s') % \
{'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [
mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \
(u' <a href="../../../../%s/%s/%s/">%s</a>' % \
(related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(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 has_admin and has_related_objs:
p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)

View File

@ -0,0 +1,280 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.newforms.models import BaseModelForm, BaseModelFormSet, fields_for_model
from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
from django.contrib.admin.options import HORIZONTAL, VERTICAL
def validate(cls, model):
"""
Does basic ModelAdmin option validation. Calls custom validation
classmethod in the end if it is provided in cls. The signature of the
custom validation classmethod should be: def validate(cls, model).
"""
opts = model._meta
_validate_base(cls, model)
# currying is expensive, use wrappers instead
def _check_istuplew(label, obj):
_check_istuple(cls, label, obj)
def _check_isdictw(label, obj):
_check_isdict(cls, label, obj)
def _check_field_existsw(label, field):
return _check_field_exists(cls, model, opts, label, field)
def _check_attr_existsw(label, field):
return _check_attr_exists(cls, model, opts, label, field)
# list_display
if hasattr(cls, 'list_display'):
_check_istuplew('list_display', cls.list_display)
for idx, field in enumerate(cls.list_display):
f = _check_attr_existsw("list_display[%d]" % idx, field)
if isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("`%s.list_display[%d]`, `%s` is a "
"ManyToManyField which is not supported."
% (cls.__name__, idx, field))
# list_display_links
if hasattr(cls, 'list_display_links'):
_check_istuplew('list_display_links', cls.list_display_links)
for idx, field in enumerate(cls.list_display_links):
_check_attr_existsw('list_display_links[%d]' % idx, field)
if field not in cls.list_display:
raise ImproperlyConfigured("`%s.list_display_links[%d]`"
"refers to `%s` which is not defined in `list_display`."
% (cls.__name__, idx, field))
# list_filter
if hasattr(cls, 'list_filter'):
_check_istuplew('list_filter', cls.list_filter)
for idx, field in enumerate(cls.list_filter):
_check_field_existsw('list_filter[%d]' % idx, field)
# list_per_page = 100
if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
raise ImproperlyConfigured("`%s.list_per_page` should be a integer."
% cls.__name__)
# search_fields = ()
if hasattr(cls, 'search_fields'):
_check_istuplew('search_fields', cls.search_fields)
# date_hierarchy = None
if cls.date_hierarchy:
f = _check_field_existsw('date_hierarchy', cls.date_hierarchy)
if not isinstance(f, (models.DateField, models.DateTimeField)):
raise ImproperlyConfigured("`%s.date_hierarchy is "
"neither an instance of DateField nor DateTimeField."
% cls.__name__)
# ordering = None
if cls.ordering:
_check_istuplew('ordering', cls.ordering)
for idx, field in enumerate(cls.ordering):
if field == '?' and len(cls.ordering) != 1:
raise ImproperlyConfigured("`%s.ordering` has the random "
"ordering marker `?`, but contains other fields as "
"well. Please either remove `?` or the other fields."
% cls.__name__)
if field == '?':
continue
if field.startswith('-'):
field = field[1:]
# Skip ordering in the format field1__field2 (FIXME: checking
# this format would be nice, but it's a little fiddly).
if '__' in field:
continue
_check_field_existsw('ordering[%d]' % idx, field)
# list_select_related = False
# save_as = False
# save_on_top = False
for attr in ('list_select_related', 'save_as', 'save_on_top'):
if not isinstance(getattr(cls, attr), bool):
raise ImproperlyConfigured("`%s.%s` should be a boolean."
% (cls.__name__, attr))
# inlines = []
if hasattr(cls, 'inlines'):
_check_istuplew('inlines', cls.inlines)
for idx, inline in enumerate(cls.inlines):
if not issubclass(inline, BaseModelAdmin):
raise ImproperlyConfigured("`%s.inlines[%d]` does not inherit "
"from BaseModelAdmin." % (cls.__name__, idx))
if not inline.model:
raise ImproperlyConfigured("`model` is a required attribute "
"of `%s.inlines[%d]`." % (cls.__name__, idx))
if not issubclass(inline.model, models.Model):
raise ImproperlyConfigured("`%s.inlines[%d].model` does not "
"inherit from models.Model." % (cls.__name__, idx))
_validate_base(inline, inline.model)
_validate_inline(inline)
def _validate_inline(cls):
# model is already verified to exist and be a Model
if cls.fk_name: # default value is None
f = _check_field_exists(cls, cls.model, cls.model._meta,
'fk_name', cls.fk_name)
if not isinstance(f, models.ForeignKey):
raise ImproperlyConfigured("`%s.fk_name is not an instance of "
"models.ForeignKey." % cls.__name__)
# extra = 3
# max_num = 0
for attr in ('extra', 'max_num'):
if not isinstance(getattr(cls, attr), int):
raise ImproperlyConfigured("`%s.%s` should be a integer."
% (cls.__name__, attr))
# formset
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
raise ImproperlyConfigured("`%s.formset` does not inherit from "
"BaseModelFormSet." % cls.__name__)
def _validate_base(cls, model):
opts = model._meta
# currying is expensive, use wrappers instead
def _check_istuplew(label, obj):
_check_istuple(cls, label, obj)
def _check_isdictw(label, obj):
_check_isdict(cls, label, obj)
def _check_field_existsw(label, field):
return _check_field_exists(cls, model, opts, label, field)
def _check_form_field_existsw(label, field):
return _check_form_field_exists(cls, model, opts, label, field)
# raw_id_fields
if hasattr(cls, 'raw_id_fields'):
_check_istuplew('raw_id_fields', cls.raw_id_fields)
for idx, field in enumerate(cls.raw_id_fields):
f = _check_field_existsw('raw_id_fields', field)
if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
raise ImproperlyConfigured("`%s.raw_id_fields[%d]`, `%s` must "
"be either a ForeignKey or ManyToManyField."
% (cls.__name__, idx, field))
# fields
if cls.fields: # default value is None
_check_istuplew('fields', cls.fields)
for field in cls.fields:
_check_form_field_existsw('fields', field)
if cls.fieldsets:
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
# fieldsets
if cls.fieldsets: # default value is None
_check_istuplew('fieldsets', cls.fieldsets)
for idx, fieldset in enumerate(cls.fieldsets):
_check_istuplew('fieldsets[%d]' % idx, fieldset)
if len(fieldset) != 2:
raise ImproperlyConfigured("`%s.fieldsets[%d]` does not "
"have exactly two elements." % (cls.__name__, idx))
_check_isdictw('fieldsets[%d][1]' % idx, fieldset[1])
if 'fields' not in fieldset[1]:
raise ImproperlyConfigured("`fields` key is required in "
"%s.fieldsets[%d][1] field options dict."
% (cls.__name__, idx))
for field in flatten_fieldsets(cls.fieldsets):
_check_form_field_existsw("fieldsets[%d][1]['fields']" % idx, field)
# form
if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
raise ImproperlyConfigured("%s.form does not inherit from "
"BaseModelForm." % cls.__name__)
# filter_vertical
if hasattr(cls, 'filter_vertical'):
_check_istuplew('filter_vertical', cls.filter_vertical)
for idx, field in enumerate(cls.filter_vertical):
f = _check_field_existsw('filter_vertical', field)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("`%s.filter_vertical[%d]` must be "
"a ManyToManyField." % (cls.__name__, idx))
# filter_horizontal
if hasattr(cls, 'filter_horizontal'):
_check_istuplew('filter_horizontal', cls.filter_horizontal)
for idx, field in enumerate(cls.filter_horizontal):
f = _check_field_existsw('filter_horizontal', field)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("`%s.filter_horizontal[%d]` must be "
"a ManyToManyField." % (cls.__name__, idx))
# radio_fields
if hasattr(cls, 'radio_fields'):
_check_isdictw('radio_fields', cls.radio_fields)
for field, val in cls.radio_fields.items():
f = _check_field_existsw('radio_fields', field)
if not (isinstance(f, models.ForeignKey) or f.choices):
raise ImproperlyConfigured("`%s.radio_fields['%s']` "
"is neither an instance of ForeignKey nor does "
"have choices set." % (cls.__name__, field))
if not val in (HORIZONTAL, VERTICAL):
raise ImproperlyConfigured("`%s.radio_fields['%s']` "
"is neither admin.HORIZONTAL nor admin.VERTICAL."
% (cls.__name__, field))
# prepopulated_fields
if hasattr(cls, 'prepopulated_fields'):
_check_isdictw('prepopulated_fields', cls.prepopulated_fields)
for field, val in cls.prepopulated_fields.items():
f = _check_field_existsw('prepopulated_fields', field)
if isinstance(f, (models.DateTimeField, models.ForeignKey,
models.ManyToManyField)):
raise ImproperlyConfigured("`%s.prepopulated_fields['%s']` "
"is either a DateTimeField, ForeignKey or "
"ManyToManyField. This isn't allowed."
% (cls.__name__, field))
_check_istuplew("prepopulated_fields['%s']" % field, val)
for idx, f in enumerate(val):
_check_field_existsw("prepopulated_fields['%s'][%d]"
% (f, idx), f)
def _check_istuple(cls, label, obj):
if not isinstance(obj, (list, tuple)):
raise ImproperlyConfigured("`%s.%s` must be a "
"list or tuple." % (cls.__name__, label))
def _check_isdict(cls, label, obj):
if not isinstance(obj, dict):
raise ImproperlyConfigured("`%s.%s` must be a dictionary."
% (cls.__name__, label))
def _check_field_exists(cls, model, opts, label, field):
try:
return opts.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("`%s.%s` refers to "
"field `%s` that is missing from model `%s`."
% (cls.__name__, label, field, model.__name__))
def _check_form_field_exists(cls, model, opts, label, field):
if hasattr(cls.form, 'base_fields'):
try:
cls.form.base_fields[field]
except KeyError:
raise ImproperlyConfigured("`%s.%s` refers to field `%s` that "
"is missing from the form." % (cls.__name__, label, field))
else:
fields = fields_for_model(model)
try:
fields[field]
except KeyError:
raise ImproperlyConfigured("`%s.%s` refers to field `%s` that "
"is missing from the form." % (cls.__name__, label, field))
def _check_attr_exists(cls, model, opts, label, field):
try:
return opts.get_field(field)
except models.FieldDoesNotExist:
if not hasattr(model, field):
raise ImproperlyConfigured("`%s.%s` refers to "
"`%s` that is neither a field, method or property "
"of model `%s`."
% (cls.__name__, label, field, model.__name__))
return getattr(model, field)

View File

@ -1,78 +0,0 @@
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.forms import UserCreationForm, AdminPasswordChangeForm
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django import oldforms, template
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django.utils.html import escape
from django.utils.translation import ugettext as _
def user_add_stage(request):
if not request.user.has_perm('auth.change_user'):
raise PermissionDenied
manipulator = UserCreationForm()
if request.method == 'POST':
new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data)
if not errors:
new_user = manipulator.save(new_data)
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
if "_addanother" in request.POST:
request.user.message_set.create(message=msg)
return HttpResponseRedirect(request.path)
else:
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
return HttpResponseRedirect('../%s/' % new_user.id)
else:
errors = new_data = {}
form = oldforms.FormWrapper(manipulator, new_data, errors)
return render_to_response('admin/auth/user/add_form.html', {
'title': _('Add user'),
'form': form,
'is_popup': '_popup' in request.REQUEST,
'add': True,
'change': False,
'has_delete_permission': False,
'has_change_permission': True,
'has_file_field': False,
'has_absolute_url': False,
'auto_populated_fields': (),
'bound_field_sets': (),
'first_form_field_id': 'id_username',
'opts': User._meta,
'username_help_text': User._meta.get_field('username').help_text,
}, context_instance=template.RequestContext(request))
user_add_stage = staff_member_required(user_add_stage)
def user_change_password(request, id):
if not request.user.has_perm('auth.change_user'):
raise PermissionDenied
user = get_object_or_404(User, pk=id)
manipulator = AdminPasswordChangeForm(user)
if request.method == 'POST':
new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data)
if not errors:
new_user = manipulator.save(new_data)
msg = _('Password changed successfully.')
request.user.message_set.create(message=msg)
return HttpResponseRedirect('..')
else:
errors = new_data = {}
form = oldforms.FormWrapper(manipulator, new_data, errors)
return render_to_response('admin/auth/user/change_password.html', {
'title': _('Change password: %s') % escape(user.username),
'form': form,
'is_popup': '_popup' in request.REQUEST,
'add': True,
'change': False,
'has_delete_permission': False,
'has_change_permission': True,
'has_absolute_url': False,
'first_form_field_id': 'id_password1',
'opts': User._meta,
'original': user,
'show_save': True,
}, context_instance=template.RequestContext(request))
user_change_password = staff_member_required(user_change_password)

View File

@ -12,7 +12,6 @@ from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from django.shortcuts import render_to_response
from django.utils.translation import ugettext_lazy, ugettext as _
from django.utils.safestring import mark_safe
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'

View File

@ -1,20 +1,13 @@
from django import oldforms, template
from django.conf import settings
from django.contrib.admin.filterspecs import FilterSpec
from django.contrib.admin.views.decorators import staff_member_required
from django.views.decorators.cache import never_cache
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.util import quote
from django.core.paginator import Paginator, InvalidPage
from django.shortcuts import get_object_or_404, render_to_response
from django.db import models
from django.db.models.query import QuerySet
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.utils.html import escape
from django.utils.text import capfirst, get_text_list
from django.utils.encoding import force_unicode, smart_str
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext
from django.utils.safestring import mark_safe
from django.utils.http import urlencode
import operator
try:
@ -22,13 +15,6 @@ try:
except NameError:
from sets import Set as set # Python 2.3 fallback
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
if not LogEntry._meta.installed:
raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application."
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
raise ImproperlyConfigured, "You'll need to put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting before you can use the admin application."
# The system will display a "Show all" link on the change list only if the
# total result count is less than or equal to this setting.
MAX_SHOW_ALL_ALLOWED = 200
@ -45,523 +31,20 @@ ERROR_FLAG = 'e'
# Text to display within change-list table cells if the value is blank.
EMPTY_CHANGELIST_VALUE = '(None)'
use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin
class IncorrectLookupParameters(Exception):
pass
def quote(s):
"""
Ensure that primary key values do not confuse the admin URLs by escaping
any '/', '_' and ':' characters. Similar to urllib.quote, except that the
quoting is slightly different so that it doesn't get automatically
unquoted by the Web browser.
"""
if type(s) != type(''):
return s
res = list(s)
for i in range(len(res)):
c = res[i]
if c in ':/_':
res[i] = '_%02X' % ord(c)
return ''.join(res)
def unquote(s):
"""
Undo the effects of quote(). Based heavily on urllib.unquote().
"""
mychr = chr
myatoi = int
list = s.split('_')
res = [list[0]]
myappend = res.append
del list[0]
for item in list:
if item[1:2]:
try:
myappend(mychr(myatoi(item[:2], 16)) + item[2:])
except ValueError:
myappend('_' + item)
else:
myappend('_' + item)
return "".join(res)
def get_javascript_imports(opts, auto_populated_fields, field_sets):
# Put in any necessary JavaScript imports.
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
if auto_populated_fields:
js.append('js/urlify.js')
if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField):
js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
if opts.get_ordered_objects():
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
if opts.admin.js:
js.extend(opts.admin.js)
seen_collapse = False
for field_set in field_sets:
if not seen_collapse and 'collapse' in field_set.classes:
seen_collapse = True
js.append('js/admin/CollapsedFieldsets.js')
for field_line in field_set:
try:
for f in field_line:
if f.rel and isinstance(f, models.ManyToManyField) and f.rel.filter_interface:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
raise StopIteration
except StopIteration:
break
return js
class AdminBoundField(object):
def __init__(self, field, field_mapping, original):
self.field = field
self.original = original
self.form_fields = [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
self.element_id = self.form_fields[0].get_id()
self.has_label_first = not isinstance(self.field, models.BooleanField)
self.raw_id_admin = use_raw_id_admin(field)
self.is_date_time = isinstance(field, models.DateTimeField)
self.is_file_field = isinstance(field, models.FileField)
self.needs_add_label = field.rel and (isinstance(field.rel, models.ManyToOneRel) or isinstance(field.rel, models.ManyToManyRel)) and field.rel.to._meta.admin
self.hidden = isinstance(self.field, models.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 = u' class="%s" ' % ' '.join(classes)
self._repr_filled = False
if field.rel:
self.related_url = mark_safe(u'../../../%s/%s/'
% (field.rel.to._meta.app_label,
field.rel.to._meta.object_name.lower()))
def original_value(self):
if self.original:
return self.original.__dict__[self.field.attname]
def existing_display(self):
try:
return self._display
except AttributeError:
if isinstance(self.field.rel, models.ManyToOneRel):
self._display = force_unicode(getattr(self.original, self.field.name), strings_only=True)
elif isinstance(self.field.rel, models.ManyToManyRel):
self._display = u", ".join([force_unicode(obj) for obj in getattr(self.original, self.field.name).all()])
return self._display
def __repr__(self):
return repr(self.__dict__)
def html_error_list(self):
return mark_safe(" ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors]))
def original_url(self):
if self.is_file_field and self.original and self.field.attname:
url_method = getattr(self.original, 'get_%s_url' % self.field.attname)
if callable(url_method):
return url_method()
return ''
class AdminBoundFieldLine(object):
def __init__(self, field_line, field_mapping, original):
self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line]
for bound_field in self:
bound_field.first = True
break
def __iter__(self):
for bound_field in self.bound_fields:
yield bound_field
def __len__(self):
return len(self.bound_fields)
class AdminBoundFieldSet(object):
def __init__(self, field_set, field_mapping, original):
self.name = field_set.name
self.classes = field_set.classes
self.description = field_set.description
self.bound_field_lines = [field_line.bind(field_mapping, original, AdminBoundFieldLine) for field_line in field_set]
def __iter__(self):
for bound_field_line in self.bound_field_lines:
yield bound_field_line
def __len__(self):
return len(self.bound_field_lines)
def render_change_form(model, manipulator, context, add=False, change=False, form_url=''):
opts = model._meta
app_label = opts.app_label
auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
field_sets = opts.admin.get_field_sets(opts)
original = getattr(manipulator, 'original_object', None)
bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets]
first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
ordered_objects = opts.get_ordered_objects()
inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
extra_context = {
'add': add,
'change': change,
'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()],
'has_change_permission': context['perms'][app_label][opts.get_change_permission()],
'has_file_field': opts.has_field_type(models.FileField),
'has_absolute_url': hasattr(model, 'get_absolute_url'),
'auto_populated_fields': auto_populated_fields,
'bound_field_sets': bound_field_sets,
'first_form_field_id': first_form_field_id,
'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets),
'ordered_objects': ordered_objects,
'inline_related_objects': inline_related_objects,
'form_url': mark_safe(form_url),
'opts': opts,
'content_type_id': ContentType.objects.get_for_model(model).id,
}
context.update(extra_context)
return render_to_response([
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_form.html" % app_label,
"admin/change_form.html"], context_instance=context)
def index(request):
return render_to_response('admin/index.html', {'title': _('Site administration')}, context_instance=template.RequestContext(request))
index = staff_member_required(never_cache(index))
def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
model = models.get_model(app_label, model_name)
if model is None:
raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
raise PermissionDenied
if post_url is None:
if request.user.has_perm(app_label + '.' + opts.get_change_permission()):
# redirect to list view
post_url = '../'
else:
# Object list will give 'Permission Denied', so go back to admin home
post_url = '../../../'
manipulator = model.AddManipulator()
if request.POST:
new_data = request.POST.copy()
if opts.has_field_type(models.FileField):
new_data.update(request.FILES)
errors = manipulator.get_validation_errors(new_data)
manipulator.do_html2python(new_data)
if not errors:
new_object = manipulator.save(new_data)
pk_value = new_object._get_pk_val()
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), ADDITION)
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if "_continue" in request.POST:
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
if "_popup" in request.POST:
post_url_continue += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value)
if "_popup" in request.POST:
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
# escape() calls force_unicode.
(escape(pk_value), escape(new_object)))
elif "_addanother" in request.POST:
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect(request.path)
else:
request.user.message_set.create(message=msg)
return HttpResponseRedirect(post_url)
else:
# Add default data.
new_data = manipulator.flatten_data()
# Override the defaults with GET params, if they exist.
new_data.update(dict(request.GET.items()))
errors = {}
# Populate the FormWrapper.
form = oldforms.FormWrapper(manipulator, new_data, errors)
c = template.RequestContext(request, {
'title': _('Add %s') % force_unicode(opts.verbose_name),
'form': form,
'is_popup': '_popup' in request.REQUEST,
'show_delete': show_delete,
})
if object_id_override is not None:
c['object_id'] = object_id_override
return render_change_form(model, manipulator, c, add=True)
add_stage = staff_member_required(never_cache(add_stage))
def change_stage(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
raise PermissionDenied
if request.POST and "_saveasnew" in request.POST:
return add_stage(request, app_label, model_name, form_url='../../add/')
try:
manipulator = model.ChangeManipulator(object_id)
except model.DoesNotExist:
raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id)))
if request.POST:
new_data = request.POST.copy()
if opts.has_field_type(models.FileField):
new_data.update(request.FILES)
errors = manipulator.get_validation_errors(new_data)
manipulator.do_html2python(new_data)
if not errors:
new_object = manipulator.save(new_data)
pk_value = new_object._get_pk_val()
# 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.')
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), CHANGE, change_message)
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)}
if "_continue" in request.POST:
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
if '_popup' in request.REQUEST:
return HttpResponseRedirect(request.path + "?_popup=1")
else:
return HttpResponseRedirect(request.path)
elif "_saveasnew" in request.POST:
request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)})
return HttpResponseRedirect("../%s/" % pk_value)
elif "_addanother" in request.POST:
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect("../add/")
else:
request.user.message_set.create(message=msg)
return HttpResponseRedirect("../")
else:
# Populate new_data with a "flattened" version of the current data.
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(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 = oldforms.FormWrapper(manipulator, new_data, errors)
form.original = manipulator.original_object
form.order_objects = []
#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_accessor_name())
orig_list = func()
form.order_objects.extend(orig_list)
c = template.RequestContext(request, {
'title': _('Change %s') % force_unicode(opts.verbose_name),
'form': form,
'object_id': object_id,
'original': manipulator.original_object,
'is_popup': '_popup' in request.REQUEST,
})
return render_change_form(model, manipulator, c, change=True)
change_stage = staff_member_required(never_cache(change_stage))
def _nest_help(obj, depth, val):
current = obj
for i in range(depth):
current = current[-1]
current.append(val)
def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth):
"Helper function that recursively populates deleted_objects."
nh = _nest_help # Bind to local variable for performance
if current_depth > 16:
return # Avoid recursing too deep.
opts_seen = []
for related in opts.get_all_related_objects():
if related.opts in opts_seen:
continue
opts_seen.append(related.opts)
rel_opts_name = related.get_accessor_name()
if isinstance(related.field.rel, models.OneToOneRel):
try:
sub_obj = getattr(obj, rel_opts_name)
except ObjectDoesNotExist:
pass
else:
if related.opts.admin:
p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)
# We don't care about populating deleted_objects now.
continue
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, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' %
(escape(force_unicode(capfirst(related.opts.verbose_name))),
related.opts.app_label,
related.opts.object_name.lower(),
sub_obj._get_pk_val(), 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, rel_opts_name).all():
has_related_objs = True
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, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
(escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(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 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(related.opts.verbose_name)
for related in opts.get_all_related_many_to_many_objects():
if related.opts in opts_seen:
continue
opts_seen.append(related.opts)
rel_opts_name = related.get_accessor_name()
has_related_objs = False
# related.get_accessor_name() could return None for symmetrical relationships
if rel_opts_name:
rel_objs = getattr(obj, rel_opts_name, None)
if rel_objs:
has_related_objs = True
if has_related_objs:
for sub_obj in rel_objs.all():
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 %(fieldname)s in %(name)s: %(obj)s') % \
{'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [
mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \
(u' <a href="../../../../%s/%s/%s/">%s</a>' % \
(related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(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 related.opts.admin and has_related_objs:
p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)
def delete_stage(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
raise PermissionDenied
obj = get_object_or_404(model, pk=object_id)
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), force_unicode(object_id), escape(obj))), []]
perms_needed = 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_display = force_unicode(obj)
obj.delete()
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION)
request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': obj_display})
return HttpResponseRedirect("../../")
extra_context = {
"title": _("Are you sure?"),
"object_name": force_unicode(opts.verbose_name),
"object": obj,
"deleted_objects": deleted_objects,
"perms_lacking": perms_needed,
"opts": model._meta,
}
return render_to_response(["admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower() ),
"admin/%s/delete_confirmation.html" % app_label ,
"admin/delete_confirmation.html"], extra_context, context_instance=template.RequestContext(request))
delete_stage = staff_member_required(never_cache(delete_stage))
def history(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
raise Http404("App %r, model %r, not found" % (app_label, model_name))
action_list = LogEntry.objects.filter(object_id=object_id,
content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
# If no history was found, see whether this object even exists.
obj = get_object_or_404(model, pk=object_id)
extra_context = {
'title': _('Change history: %s') % obj,
'action_list': action_list,
'module_name': force_unicode(capfirst(model._meta.verbose_name_plural)),
'object': obj,
}
return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()),
"admin/%s/object_history.html" % app_label ,
"admin/object_history.html"], extra_context, context_instance=template.RequestContext(request))
history = staff_member_required(never_cache(history))
class ChangeList(object):
def __init__(self, request, model):
def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, model_admin):
self.model = model
self.opts = model._meta
self.lookup_opts = self.opts
self.manager = self.opts.admin.manager
self.root_query_set = model_admin.queryset(request)
self.list_display = list_display
self.list_display_links = list_display_links
self.list_filter = list_filter
self.date_hierarchy = date_hierarchy
self.search_fields = search_fields
self.list_select_related = list_select_related
self.list_per_page = list_per_page
self.model_admin = model_admin
# Get search parameters from the query string.
try:
@ -580,17 +63,16 @@ class ChangeList(object):
self.query = request.GET.get(SEARCH_VAR, '')
self.query_set = self.get_query_set()
self.get_results(request)
self.title = (self.is_popup and _('Select %s') % force_unicode(self.opts.verbose_name) or _('Select %s to change') % force_unicode(self.opts.verbose_name))
self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
self.filter_specs, self.has_filters = self.get_filters(request)
self.pk_attname = self.lookup_opts.pk.attname
def get_filters(self, request):
filter_specs = []
if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field:
filter_fields = [self.lookup_opts.get_field(field_name) \
for field_name in self.lookup_opts.admin.list_filter]
if self.list_filter and not self.opts.one_to_one_field:
filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter]
for f in filter_fields:
spec = FilterSpec.create(f, request, self.params, self.model)
spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin)
if spec and spec.has_output():
filter_specs.append(spec)
return filter_specs, bool(filter_specs)
@ -604,15 +86,15 @@ class ChangeList(object):
if k.startswith(r):
del p[k]
for k, v in new_params.items():
if k in p and v is None:
del p[k]
elif v is not None:
if v is None:
if k in p:
del p[k]
else:
p[k] = v
return mark_safe('?' + '&amp;'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20'))
return '?%s' % urlencode(p)
def get_results(self, request):
paginator = Paginator(self.query_set, self.lookup_opts.admin.list_per_page)
paginator = Paginator(self.query_set, self.list_per_page)
# Get the number of objects, with admin filters applied.
try:
result_count = paginator.count
@ -630,10 +112,10 @@ class ChangeList(object):
if not self.query_set.query.where:
full_result_count = result_count
else:
full_result_count = self.manager.count()
full_result_count = self.root_query_set.count()
can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
multi_page = result_count > self.lookup_opts.admin.list_per_page
multi_page = result_count > self.list_per_page
# Get the list of objects to display on this page.
if (self.show_all and can_show_all) or not multi_page:
@ -657,7 +139,7 @@ class ChangeList(object):
# 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]
ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
if ordering[0].startswith('-'):
order_field, order_type = ordering[0][1:], 'desc'
@ -665,14 +147,14 @@ class ChangeList(object):
order_field, order_type = ordering[0], 'asc'
if ORDER_VAR in params:
try:
field_name = lookup_opts.admin.list_display[int(params[ORDER_VAR])]
field_name = self.list_display[int(params[ORDER_VAR])]
try:
f = lookup_opts.get_field(field_name)
except models.FieldDoesNotExist:
# see if field_name is a name of a non-field
# that allows sorting
# See whether field_name is a name of a non-field
# that allows sorting.
try:
attr = getattr(lookup_opts.admin.manager.model, field_name)
attr = getattr(self.model, field_name)
order_field = attr.admin_order_field
except AttributeError:
pass
@ -686,7 +168,7 @@ class ChangeList(object):
return order_field, order_type
def get_query_set(self):
qs = self.manager.get_query_set()
qs = self.root_query_set
lookup_params = self.params.copy() # a dictionary of the query string
for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
if i in lookup_params:
@ -703,10 +185,10 @@ class ChangeList(object):
# Use select_related() if one of the list_display options is a field
# with a relationship.
if self.lookup_opts.admin.list_select_related:
if self.list_select_related:
qs = qs.select_related()
else:
for field_name in self.lookup_opts.admin.list_display:
for field_name in self.list_display:
try:
f = self.lookup_opts.get_field(field_name)
except models.FieldDoesNotExist:
@ -731,13 +213,17 @@ class ChangeList(object):
else:
return "%s__icontains" % field_name
if self.lookup_opts.admin.search_fields and self.query:
if self.search_fields and self.query:
for bit in self.query.split():
or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.lookup_opts.admin.search_fields]
or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.search_fields]
other_qs = QuerySet(self.model)
other_qs.dup_select_related(qs)
other_qs = other_qs.filter(reduce(operator.or_, or_queries))
qs = qs & other_qs
for field_name in self.search_fields:
if '__' in field_name:
qs = qs.distinct()
break
if self.opts.one_to_one_field:
qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to)
@ -746,31 +232,3 @@ class ChangeList(object):
def url_for_result(self, result):
return "%s/" % quote(getattr(result, self.pk_attname))
def change_list(request, app_label, model_name):
model = models.get_model(app_label, model_name)
if model is None:
raise Http404("App %r, model %r, not found" % (app_label, model_name))
if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()):
raise PermissionDenied
try:
cl = ChangeList(request, model)
except IncorrectLookupParameters:
# Wacky lookup parameters were given, so redirect to the main
# changelist page, without parameters, and pass an 'invalid=1'
# parameter via the query string. If wacky parameters were given and
# the 'invalid=1' parameter was already in the query string, something
# is screwed up with the database, so display an error page.
if ERROR_FLAG in request.GET.keys():
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
c = template.RequestContext(request, {
'title': cl.title,
'is_popup': cl.is_popup,
'cl': cl,
})
c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}),
return render_to_response(['admin/%s/%s/change_list.html' % (app_label, cl.opts.object_name.lower()),
'admin/%s/change_list.html' % app_label,
'admin/change_list.html'], context_instance=c)
change_list = staff_member_required(never_cache(change_list))

View File

@ -0,0 +1,215 @@
"""
Form Widget classes specific to the Django admin site.
"""
import copy
from django import newforms as forms
from django.newforms.widgets import RadioFieldRenderer
from django.newforms.util import flatatt
from django.utils.datastructures import MultiValueDict
from django.utils.text import capfirst, truncate_words
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
class FilteredSelectMultiple(forms.SelectMultiple):
"""
A SelectMultiple with a JavaScript filter interface.
Note that the resulting JavaScript assumes that the SelectFilter2.js
library and its dependencies have been loaded in the HTML page.
"""
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=()):
from django.conf import settings
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:
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
def __init__(self, attrs={}):
super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
class AdminTimeWidget(forms.TextInput):
class Media:
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
def __init__(self, attrs={}):
super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
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)
def render(self, name, value, attrs=None):
from django.conf import settings
output = []
if value:
output.append('%s <a target="_blank" href="%s%s">%s</a> <br />%s ' % \
(_('Currently:'), settings.MEDIA_URL, value, value, _('Change:')))
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):
from django.conf import settings
related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
if self.rel.limit_choices_to:
url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in self.rel.limit_choices_to.items()])
else:
url = ''
if not attrs.has_key('class'):
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
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))
output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % settings.ADMIN_MEDIA_PREFIX)
if value:
output.append(self.label_for_value(value))
return mark_safe(u''.join(output))
def label_for_value(self, value):
return '&nbsp;<strong>%s</strong>' % \
truncate_words(self.rel.to.objects.get(pk=value), 14)
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)
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)
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
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
def render(self, name, value, *args, **kwargs):
from django.conf import settings
rel_to = self.rel.to
related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower())
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.
output.append(u'<a href="%sadd/" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
(related_url, name))
output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>' % settings.ADMIN_MEDIA_PREFIX)
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_)

View File

@ -0,0 +1,15 @@
from django.conf.urls.defaults import *
from django.contrib.admindocs import views
urlpatterns = patterns('',
('^$', views.doc_index),
('^bookmarklets/$', views.bookmarklets),
('^tags/$', views.template_tag_index),
('^filters/$', views.template_filter_index),
('^views/$', views.view_index),
('^views/(?P<view>[^/]+)/$', views.view_detail),
('^models/$', views.model_index),
('^models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', views.model_detail),
# ('^templates/$', views.template_index),
('^templates/(?P<template>.*)/$', views.template_detail),
)

View File

@ -5,9 +5,9 @@ from django.contrib.admin.views.decorators import staff_member_required
from django.db import models
from django.shortcuts import render_to_response
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.http import Http404
from django.http import Http404, get_host
from django.core import urlresolvers
from django.contrib.admin import utils
from django.contrib.admindocs import utils
from django.contrib.sites.models import Site
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
@ -23,13 +23,18 @@ class GenericSite(object):
def doc_index(request):
if not utils.docutils_is_available:
return missing_docutils_page(request)
return render_to_response('admin_doc/index.html', context_instance=RequestContext(request))
root_path = re.sub(re.escape('doc/') + '$', '', request.path)
return render_to_response('admin_doc/index.html', {
'root_path': root_path,
}, context_instance=RequestContext(request))
doc_index = staff_member_required(doc_index)
def bookmarklets(request):
# Hack! This couples this view to the URL it lives at.
admin_root = request.path[:-len('doc/bookmarklets/')]
root_path = re.sub(re.escape('doc/bookmarklets/') + '$', '', request.path)
return render_to_response('admin_doc/bookmarklets.html', {
'root_path': root_path,
'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)),
}, context_instance=RequestContext(request))
bookmarklets = staff_member_required(bookmarklets)
@ -61,8 +66,11 @@ def template_tag_index(request):
'meta': metadata,
'library': tag_library,
})
return render_to_response('admin_doc/template_tag_index.html', {'tags': tags}, context_instance=RequestContext(request))
root_path = re.sub(re.escape('doc/tags/') + '$', '', request.path)
return render_to_response('admin_doc/template_tag_index.html', {
'root_path': root_path,
'tags': tags
}, context_instance=RequestContext(request))
template_tag_index = staff_member_required(template_tag_index)
def template_filter_index(request):
@ -92,7 +100,11 @@ def template_filter_index(request):
'meta': metadata,
'library': tag_library,
})
return render_to_response('admin_doc/template_filter_index.html', {'filters': filters}, context_instance=RequestContext(request))
root_path = re.sub(re.escape('doc/filters/') + '$', '', request.path)
return render_to_response('admin_doc/template_filter_index.html', {
'root_path': root_path,
'filters': filters
}, context_instance=RequestContext(request))
template_filter_index = staff_member_required(template_filter_index)
def view_index(request):
@ -120,7 +132,11 @@ def view_index(request):
'site': site_obj,
'url': simplify_regex(regex),
})
return render_to_response('admin_doc/view_index.html', {'views': views}, context_instance=RequestContext(request))
root_path = re.sub(re.escape('doc/views/') + '$', '', request.path)
return render_to_response('admin_doc/view_index.html', {
'root_path': root_path,
'views': views
}, context_instance=RequestContext(request))
view_index = staff_member_required(view_index)
def view_detail(request, view):
@ -139,7 +155,9 @@ def view_detail(request, view):
body = utils.parse_rst(body, 'view', _('view:') + view)
for key in metadata:
metadata[key] = utils.parse_rst(metadata[key], 'model', _('view:') + view)
root_path = re.sub(re.escape('doc/views/%s/' % view) + '$', '', request.path)
return render_to_response('admin_doc/view_detail.html', {
'root_path': root_path,
'name': view,
'summary': title,
'body': body,
@ -150,15 +168,18 @@ view_detail = staff_member_required(view_detail)
def model_index(request):
if not utils.docutils_is_available:
return missing_docutils_page(request)
m_list = [m._meta for m in models.get_models()]
return render_to_response('admin_doc/model_index.html', {'models': m_list}, context_instance=RequestContext(request))
root_path = re.sub(re.escape('doc/models/') + '$', '', request.path)
return render_to_response('admin_doc/model_index.html', {
'root_path': root_path,
'models': m_list
}, context_instance=RequestContext(request))
model_index = staff_member_required(model_index)
def model_detail(request, app_label, model_name):
if not utils.docutils_is_available:
return missing_docutils_page(request)
# Get the model class.
try:
app_mod = models.get_app(app_label)
@ -170,7 +191,7 @@ def model_detail(request, app_label, model_name):
model = m
break
if model is None:
raise Http404, _("Model %(name)r not found in app %(label)r") % {'name': model_name, 'label': app_label}
raise Http404, _("Model %(model_name)r not found in app %(app_label)r") % {'model_name': model_name, 'app_label': app_label}
opts = model._meta
@ -182,7 +203,7 @@ def model_detail(request, app_label, model_name):
if isinstance(field, models.ForeignKey):
data_type = related_object_name = field.rel.to.__name__
app_label = field.rel.to._meta.app_label
verbose = utils.parse_rst((_("the related `%(label)s.%(type)s` object") % {'label': app_label, 'type': data_type}), 'model', _('model:') + data_type)
verbose = utils.parse_rst((_("the related `%(app_label)s.%(data_type)s` object") % {'app_label': app_label, 'data_type': data_type}), 'model', _('model:') + data_type)
else:
data_type = get_readable_field_data_type(field)
verbose = field.verbose_name
@ -213,7 +234,7 @@ def model_detail(request, app_label, model_name):
# Gather related objects
for rel in opts.get_all_related_objects():
verbose = _("related `%(label)s.%(name)s` objects") % {'label': rel.opts.app_label, 'name': rel.opts.object_name}
verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': rel.opts.app_label, 'object_name': rel.opts.object_name}
accessor = rel.get_accessor_name()
fields.append({
'name' : "%s.all" % accessor,
@ -225,8 +246,9 @@ def model_detail(request, app_label, model_name):
'data_type' : 'Integer',
'verbose' : utils.parse_rst(_("number of %s") % verbose , 'model', _('model:') + opts.module_name),
})
root_path = re.sub(re.escape('doc/models/%s.%s/' % (app_label, model_name)) + '$', '', request.path)
return render_to_response('admin_doc/model_detail.html', {
'root_path': root_path,
'name': '%s.%s' % (opts.app_label, opts.object_name),
'summary': _("Fields on %s objects") % opts.object_name,
'description': model.__doc__,
@ -252,7 +274,9 @@ def template_detail(request, template):
'site': site_obj,
'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
})
root_path = re.sub(re.escape('doc/templates/%s/' % template) + '$', '', request.path)
return render_to_response('admin_doc/template_detail.html', {
'root_path': root_path,
'name': template,
'templates': templates,
}, context_instance=RequestContext(request))

View File

@ -0,0 +1,66 @@
from django.contrib.auth.models import User, Group
from django.core.exceptions import PermissionDenied
from django import oldforms, template
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib import admin
class GroupAdmin(admin.ModelAdmin):
search_fields = ('name',)
ordering = ('name',)
filter_horizontal = ('permissions',)
class UserAdmin(admin.ModelAdmin):
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
search_fields = ('username', 'first_name', 'last_name', 'email')
ordering = ('username',)
filter_horizontal = ('user_permissions',)
def add_view(self, request):
# avoid a circular import. see #6718.
from django.contrib.auth.forms import UserCreationForm
if not self.has_change_permission(request):
raise PermissionDenied
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
new_user = form.save()
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
if "_addanother" in request.POST:
request.user.message_set.create(message=msg)
return HttpResponseRedirect(request.path)
else:
request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below."))
return HttpResponseRedirect('../%s/' % new_user.id)
else:
form = UserCreationForm()
return render_to_response('admin/auth/user/add_form.html', {
'title': _('Add user'),
'form': form,
'is_popup': '_popup' in request.REQUEST,
'add': True,
'change': False,
'has_add_permission': True,
'has_delete_permission': False,
'has_change_permission': True,
'has_file_field': False,
'has_absolute_url': False,
'auto_populated_fields': (),
'opts': User._meta,
'save_as': False,
'username_help_text': User._meta.get_field('username').help_text,
'root_path': self.admin_site.root_path,
}, context_instance=template.RequestContext(request))
admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin)

View File

@ -3,88 +3,106 @@ from django.contrib.auth import authenticate
from django.contrib.sites.models import Site
from django.template import Context, loader
from django.core import validators
from django import oldforms
from django.utils.translation import ugettext as _
from django import newforms as forms
from django.utils.translation import ugettext_lazy as _
class UserCreationForm(oldforms.Manipulator):
"A form that creates a user, with no privileges, from the given username and password."
def __init__(self):
self.fields = (
oldforms.TextField(field_name='username', length=30, max_length=30, is_required=True,
validator_list=[validators.isAlphaNumeric, self.isValidUsername]),
oldforms.PasswordField(field_name='password1', length=30, max_length=60, is_required=True),
oldforms.PasswordField(field_name='password2', length=30, max_length=60, is_required=True,
validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]),
)
def isValidUsername(self, field_data, all_data):
class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and password.
"""
username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$',
help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."),
error_message = _("This value must contain only letters, numbers and underscores."))
password1 = forms.CharField(label=_("Password"), max_length=60, widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), max_length=60, widget=forms.PasswordInput)
class Meta:
model = User
fields = ("username",)
def clean_username(self):
username = self.cleaned_data["username"]
try:
User.objects.get(username=field_data)
User.objects.get(username=username)
except User.DoesNotExist:
return
raise validators.ValidationError, _('A user with that username already exists.')
return username
raise forms.ValidationError(_("A user with that username already exists."))
def clean_password2(self):
password1 = self.cleaned_data["password1"]
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
def save(self, new_data):
"Creates the user."
return User.objects.create_user(new_data['username'], '', new_data['password1'])
class AuthenticationForm(oldforms.Manipulator):
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
def __init__(self, request=None):
username = forms.CharField(label=_("Username"), max_length=30)
password = forms.CharField(label=_("Password"), max_length=30, widget=forms.PasswordInput)
def __init__(self, request=None, *args, **kwargs):
"""
If request is passed in, the manipulator will validate that cookies are
If request is passed in, the form will validate that cookies are
enabled. Note that the request (a HttpRequest object) must have set a
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
running this validator.
running this validation.
"""
self.request = request
self.fields = [
oldforms.TextField(field_name="username", length=15, max_length=30, is_required=True,
validator_list=[self.isValidUser, self.hasCookiesEnabled]),
oldforms.PasswordField(field_name="password", length=15, max_length=30, is_required=True),
]
self.user_cache = None
def hasCookiesEnabled(self, field_data, all_data):
if self.request and not self.request.session.test_cookie_worked():
raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
def isValidUser(self, field_data, all_data):
username = field_data
password = all_data.get('password', None)
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
elif not self.user_cache.is_active:
raise validators.ValidationError, _("This account is inactive.")
super(AuthenticationForm, self).__init__(*args, **kwargs)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
elif not self.user_cache.is_active:
raise forms.ValidationError(_("This account is inactive."))
# TODO: determine whether this should move to its own method.
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."))
return self.cleaned_data
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class PasswordResetForm(oldforms.Manipulator):
"A form that lets a user request a password reset"
def __init__(self):
self.fields = (
oldforms.EmailField(field_name="email", length=40, is_required=True,
validator_list=[self.isValidUserEmail]),
)
def isValidUserEmail(self, new_data, all_data):
"Validates that a user exists with the given e-mail address"
self.users_cache = list(User.objects.filter(email__iexact=new_data))
class PasswordResetForm(forms.Form):
email = forms.EmailField(label=_("E-mail"), max_length=40)
def clean_email(self):
"""
Validates that a user exists with the given e-mail address.
"""
email = self.cleaned_data["email"]
self.users_cache = User.objects.filter(email__iexact=email)
if len(self.users_cache) == 0:
raise validators.ValidationError, _("That e-mail address doesn't have an associated user account. Are you sure you've registered?")
raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?"))
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'):
"Calculates a new password randomly and sends it to the user"
"""
Calculates a new password randomly and sends it to the user.
"""
from django.core.mail import send_mail
for user in self.users_cache:
new_pass = User.objects.make_random_password()
@ -103,42 +121,69 @@ class PasswordResetForm(oldforms.Manipulator):
'domain': domain,
'site_name': site_name,
'user': user,
}
send_mail(_('Password reset on %s') % site_name, t.render(Context(c)), None, [user.email])
}
send_mail(_("Password reset on %s") % site_name,
t.render(Context(c)), None, [user.email])
class PasswordChangeForm(oldforms.Manipulator):
"A form that lets a user change his password."
def __init__(self, user):
class PasswordChangeForm(forms.Form):
"""
A form that lets a user change his/her password.
"""
old_password = forms.CharField(label=_("Old password"), max_length=30, widget=forms.PasswordInput)
new_password1 = forms.CharField(label=_("New password"), max_length=30, widget=forms.PasswordInput)
new_password2 = forms.CharField(label=_("New password confirmation"), max_length=30, widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
self.user = user
self.fields = (
oldforms.PasswordField(field_name="old_password", length=30, max_length=30, is_required=True,
validator_list=[self.isValidOldPassword]),
oldforms.PasswordField(field_name="new_password1", length=30, max_length=30, is_required=True,
validator_list=[validators.AlwaysMatchesOtherField('new_password2', _("The two 'new password' fields didn't match."))]),
oldforms.PasswordField(field_name="new_password2", length=30, max_length=30, is_required=True),
)
super(PasswordChangeForm, self).__init__(*args, **kwargs)
def clean_old_password(self):
"""
Validates that the old_password field is correct.
"""
old_password = self.cleaned_data["old_password"]
if not self.user.check_password(old_password):
raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again."))
return old_password
def clean_new_password2(self):
password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2')
if password1 and password2:
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
def save(self, commit=True):
self.user.set_password(self.cleaned_data['new_password1'])
if commit:
self.user.save()
return self.user
def isValidOldPassword(self, new_data, all_data):
"Validates that the old_password field is correct."
if not self.user.check_password(new_data):
raise validators.ValidationError, _("Your old password was entered incorrectly. Please enter it again.")
def save(self, new_data):
"Saves the new password."
self.user.set_password(new_data['new_password1'])
self.user.save()
class AdminPasswordChangeForm(oldforms.Manipulator):
"A form used to change the password of a user in the admin interface."
def __init__(self, user):
class AdminPasswordChangeForm(forms.Form):
"""
A form used to change the password of a user in the admin interface.
"""
password1 = forms.CharField(label=_("Password"), max_length=60, widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password (again)"), max_length=60, widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
self.user = user
self.fields = (
oldforms.PasswordField(field_name='password1', length=30, max_length=60, is_required=True),
oldforms.PasswordField(field_name='password2', length=30, max_length=60, is_required=True,
validator_list=[validators.AlwaysMatchesOtherField('password1', _("The two password fields didn't match."))]),
)
def save(self, new_data):
"Saves the new password."
self.user.set_password(new_data['password1'])
self.user.save()
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if password1 and password2:
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
def save(self, commit=True):
"""
Saves the new password.
"""
self.user.set_password(self.cleaned_data["password1"])
if commit:
self.user.save()
return self.user

View File

@ -91,16 +91,12 @@ class Group(models.Model):
Beyond permissions, groups are a convenient way to categorize users to apply some label, or extended functionality, to them. For example, you could create a group 'Special users', and you could write code that would do special things to those users -- such as giving them access to a members-only portion of your site, or sending them members-only e-mail messages.
"""
name = models.CharField(_('name'), max_length=80, unique=True)
permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True, filter_interface=models.HORIZONTAL)
permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True)
class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')
class Admin:
search_fields = ('name',)
ordering = ('name',)
def __unicode__(self):
return self.name
@ -147,26 +143,13 @@ class User(models.Model):
date_joined = models.DateTimeField(_('date joined'), default=datetime.datetime.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True,
help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."))
user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True, filter_interface=models.HORIZONTAL)
user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True)
objects = UserManager()
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
class Admin:
fields = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
search_fields = ('username', 'first_name', 'last_name', 'email')
ordering = ('username',)
def __unicode__(self):
return self.username

View File

@ -0,0 +1,8 @@
from django.contrib.auth.tests.basic import BASIC_TESTS, PasswordResetTest
from django.contrib.auth.tests.forms import FORM_TESTS
__test__ = {
'BASIC_TESTS': BASIC_TESTS,
'PASSWORDRESET_TESTS': PasswordResetTest,
'FORM_TESTS': FORM_TESTS,
}

View File

@ -1,4 +1,5 @@
"""
BASIC_TESTS = """
>>> from django.contrib.auth.models import User, AnonymousUser
>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
>>> u.has_usable_password()
@ -60,14 +61,15 @@ from django.core import mail
class PasswordResetTest(TestCase):
fixtures = ['authtestdata.json']
urls = 'django.contrib.auth.urls'
def test_email_not_found(self):
"Error is raised if the provided email address isn't currently registered"
response = self.client.get('/password_reset/')
self.assertEquals(response.status_code, 200)
response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})
self.assertContains(response, "That e-mail address doesn&#39;t have an associated user account")
self.assertContains(response, "That e-mail address doesn't have an associated user account")
self.assertEquals(len(mail.outbox), 0)
def test_email_found(self):
"Email is sent if a valid email address is provided for password reset"
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})

View File

@ -0,0 +1,135 @@
FORM_TESTS = """
>>> from django.contrib.auth.models import User
>>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
>>> from django.contrib.auth.forms import PasswordChangeForm
The user already exists.
>>> user = User.objects.create_user("jsmith", "jsmith@example.com", "test123")
>>> data = {
... 'username': 'jsmith',
... 'password1': 'test123',
... 'password2': 'test123',
... }
>>> form = UserCreationForm(data)
>>> form.is_valid()
False
>>> form["username"].errors
[u'A user with that username already exists.']
The username contains invalid data.
>>> data = {
... 'username': 'jsmith@example.com',
... 'password1': 'test123',
... 'password2': 'test123',
... }
>>> form = UserCreationForm(data)
>>> form.is_valid()
False
>>> form["username"].errors
[u'This value must contain only letters, numbers and underscores.']
The verification password is incorrect.
>>> data = {
... 'username': 'jsmith2',
... 'password1': 'test123',
... 'password2': 'test',
... }
>>> form = UserCreationForm(data)
>>> form.is_valid()
False
>>> form["password2"].errors
[u"The two password fields didn't match."]
The success case.
>>> data = {
... 'username': 'jsmith2',
... 'password1': 'test123',
... 'password2': 'test123',
... }
>>> form = UserCreationForm(data)
>>> form.is_valid()
True
>>> form.save()
<User: jsmith2>
The user submits an invalid username.
>>> data = {
... 'username': 'jsmith_does_not_exist',
... 'password': 'test123',
... }
>>> form = AuthenticationForm(None, data)
>>> form.is_valid()
False
>>> form.non_field_errors()
[u'Please enter a correct username and password. Note that both fields are case-sensitive.']
The user is inactive.
>>> data = {
... 'username': 'jsmith',
... 'password': 'test123',
... }
>>> user.is_active = False
>>> user.save()
>>> form = AuthenticationForm(None, data)
>>> form.is_valid()
False
>>> form.non_field_errors()
[u'This account is inactive.']
>>> user.is_active = True
>>> user.save()
The success case
>>> form = AuthenticationForm(None, data)
>>> form.is_valid()
True
>>> form.non_field_errors()
[]
The old password is incorrect.
>>> data = {
... 'old_password': 'test',
... 'new_password1': 'abc123',
... 'new_password2': 'abc123',
... }
>>> form = PasswordChangeForm(user, data)
>>> form.is_valid()
False
>>> form["old_password"].errors
[u'Your old password was entered incorrectly. Please enter it again.']
The two new passwords do not match.
>>> data = {
... 'old_password': 'test123',
... 'new_password1': 'abc123',
... 'new_password2': 'abc',
... }
>>> form = PasswordChangeForm(user, data)
>>> form.is_valid()
False
>>> form["new_password2"].errors
[u"The two password fields didn't match."]
The success case.
>>> data = {
... 'old_password': 'test123',
... 'new_password1': 'abc123',
... 'new_password2': 'abc123',
... }
>>> form = PasswordChangeForm(user, data)
>>> form.is_valid()
True
"""

View File

@ -1,42 +1,42 @@
from django import oldforms
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm, AdminPasswordChangeForm
from django.core.exceptions import PermissionDenied
from django.shortcuts import render_to_response, get_object_or_404
from django.contrib.sites.models import Site, RequestSite
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.http import urlquote
from django.utils.html import escape
from django.utils.translation import ugettext as _
from django.contrib.auth.models import User
import re
def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
"Displays the login form and handles the login action."
manipulator = AuthenticationForm()
redirect_to = request.REQUEST.get(redirect_field_name, '')
if request.POST:
errors = manipulator.get_validation_errors(request.POST)
if not errors:
if request.method == "POST":
form = AuthenticationForm(data=request.POST)
if form.is_valid():
# Light security check -- make sure redirect_to isn't garbage.
if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
from django.conf import settings
redirect_to = settings.LOGIN_REDIRECT_URL
from django.contrib.auth import login
login(request, manipulator.get_user())
login(request, form.get_user())
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponseRedirect(redirect_to)
else:
errors = {}
form = AuthenticationForm(request)
request.session.set_test_cookie()
if Site._meta.installed:
current_site = Site.objects.get_current()
else:
current_site = RequestSite(request)
return render_to_response(template_name, {
'form': oldforms.FormWrapper(manipulator, request.POST, errors),
'form': form,
redirect_field_name: redirect_to,
'site_name': current_site.name,
}, context_instance=RequestContext(request))
@ -66,13 +66,11 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next)))
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html'):
new_data, errors = {}, {}
form = PasswordResetForm()
if request.POST:
new_data = request.POST.copy()
errors = form.get_validation_errors(new_data)
if not errors:
email_template_name='registration/password_reset_email.html',
password_reset_form=PasswordResetForm):
if request.method == "POST":
form = password_reset_form(request.POST)
if form.is_valid():
if is_admin_site:
form.save(domain_override=request.META['HTTP_HOST'])
else:
@ -81,24 +79,57 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas
else:
form.save(domain_override=RequestSite(request).domain, email_template_name=email_template_name)
return HttpResponseRedirect('%sdone/' % request.path)
return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
context_instance=RequestContext(request))
else:
form = password_reset_form()
return render_to_response(template_name, {
'form': form,
}, context_instance=RequestContext(request))
def password_reset_done(request, template_name='registration/password_reset_done.html'):
return render_to_response(template_name, context_instance=RequestContext(request))
def password_change(request, template_name='registration/password_change_form.html'):
new_data, errors = {}, {}
form = PasswordChangeForm(request.user)
if request.POST:
new_data = request.POST.copy()
errors = form.get_validation_errors(new_data)
if not errors:
form.save(new_data)
if request.method == "POST":
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('%sdone/' % request.path)
return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
context_instance=RequestContext(request))
else:
form = PasswordChangeForm(request.user)
return render_to_response(template_name, {
'form': form,
}, context_instance=RequestContext(request))
password_change = login_required(password_change)
def password_change_done(request, template_name='registration/password_change_done.html'):
return render_to_response(template_name, context_instance=RequestContext(request))
# TODO: move to admin.py in the ModelAdmin
def user_change_password(request, id):
if not request.user.has_perm('auth.change_user'):
raise PermissionDenied
user = get_object_or_404(User, pk=id)
if request.method == 'POST':
form = AdminPasswordChangeForm(user, request.POST)
if form.is_valid():
new_user = form.save()
msg = _('Password changed successfully.')
request.user.message_set.create(message=msg)
return HttpResponseRedirect('..')
else:
form = AdminPasswordChangeForm(user)
return render_to_response('admin/auth/user/change_password.html', {
'title': _('Change password: %s') % escape(user.username),
'form': form,
'is_popup': '_popup' in request.REQUEST,
'add': True,
'change': False,
'has_delete_permission': False,
'has_change_permission': True,
'has_absolute_url': False,
'opts': User._meta,
'original': user,
'save_as': False,
'show_save': True,
'root_path': re.sub('auth/user/(\d+)/password/$', '', request.path),
}, context_instance=RequestContext(request))

View File

@ -0,0 +1,30 @@
from django.contrib import admin
from django.contrib.comments.models import Comment, FreeComment
class CommentAdmin(admin.ModelAdmin):
fieldsets = (
(None, {'fields': ('content_type', 'object_id', 'site')}),
('Content', {'fields': ('user', 'headline', 'comment')}),
('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
)
list_display = ('user', 'submit_date', 'content_type', 'get_content_object')
list_filter = ('submit_date',)
date_hierarchy = 'submit_date'
search_fields = ('comment', 'user__username')
raw_id_fields = ('user',)
class FreeCommentAdmin(admin.ModelAdmin):
fieldsets = (
(None, {'fields': ('content_type', 'object_id', 'site')}),
('Content', {'fields': ('person_name', 'comment')}),
('Meta', {'fields': ('is_public', 'ip_address', 'approved')}),
)
list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object')
list_filter = ('submit_date',)
date_hierarchy = 'submit_date'
search_fields = ('comment', 'person_name')
admin.site.register(Comment, CommentAdmin)
admin.site.register(FreeComment, FreeCommentAdmin)

View File

@ -66,7 +66,7 @@ class CommentManager(models.Manager):
class Comment(models.Model):
"""A comment by a registered user."""
user = models.ForeignKey(User, raw_id_admin=True)
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.IntegerField(_('object ID'))
headline = models.CharField(_('headline'), max_length=255, blank=True)
@ -96,18 +96,6 @@ class Comment(models.Model):
verbose_name_plural = _('comments')
ordering = ('-submit_date',)
class Admin:
fields = (
(None, {'fields': ('content_type', 'object_id', 'site')}),
('Content', {'fields': ('user', 'headline', 'comment')}),
('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
)
list_display = ('user', 'submit_date', 'content_type', 'get_content_object')
list_filter = ('submit_date',)
date_hierarchy = 'submit_date'
search_fields = ('comment', 'user__username')
def __unicode__(self):
return "%s: %s..." % (self.user.username, self.comment[:100])
@ -188,17 +176,6 @@ class FreeComment(models.Model):
verbose_name_plural = _('free comments')
ordering = ('-submit_date',)
class Admin:
fields = (
(None, {'fields': ('content_type', 'object_id', 'site')}),
('Content', {'fields': ('person_name', 'comment')}),
('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
)
list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object')
list_filter = ('submit_date',)
date_hierarchy = 'submit_date'
search_fields = ('comment', 'person_name')
def __unicode__(self):
return "%s: %s..." % (self.person_name, self.comment[:100])
@ -306,3 +283,4 @@ class ModeratorDeletion(models.Model):
def __unicode__(self):
return _("Moderator deletion by %r") % self.user

View File

@ -1,3 +1,6 @@
import base64
import datetime
from django.core import validators
from django import oldforms
from django.core.mail import mail_admins, mail_managers
@ -7,16 +10,61 @@ from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.comments.models import Comment, FreeComment, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import authenticate
from django.http import HttpResponseRedirect
from django.utils.text import normalize_newlines
from django.conf import settings
from django.utils.translation import ungettext, ugettext as _
from django.utils.encoding import smart_unicode
import base64, datetime
COMMENTS_PER_PAGE = 20
# TODO: This is a copy of the manipulator-based form that used to live in
# contrib.auth.forms. It should be replaced with the newforms version that
# has now been added to contrib.auth.forms when the comments app gets updated
# for newforms.
class AuthenticationForm(oldforms.Manipulator):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
def __init__(self, request=None):
"""
If request is passed in, the manipulator will validate that cookies are
enabled. Note that the request (a HttpRequest object) must have set a
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
running this validator.
"""
self.request = request
self.fields = [
oldforms.TextField(field_name="username", length=15, max_length=30, is_required=True,
validator_list=[self.isValidUser, self.hasCookiesEnabled]),
oldforms.PasswordField(field_name="password", length=15, max_length=30, is_required=True),
]
self.user_cache = None
def hasCookiesEnabled(self, field_data, all_data):
if self.request and not self.request.session.test_cookie_worked():
raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
def isValidUser(self, field_data, all_data):
username = field_data
password = all_data.get('password', None)
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
elif not self.user_cache.is_active:
raise validators.ValidationError, _("This account is inactive.")
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class PublicCommentManipulator(AuthenticationForm):
"Manipulator that handles public registered comments"
def __init__(self, user, ratings_required, ratings_range, num_rating_choices):

View File

@ -0,0 +1,15 @@
from django.contrib import admin
from django.contrib.flatpages.models import FlatPage
from django.utils.translation import ugettext_lazy as _
class FlatPageAdmin(admin.ModelAdmin):
fieldsets = (
(None, {'fields': ('url', 'title', 'content', 'sites')}),
(_('Advanced options'), {'classes': ('collapse',), 'fields': ('enable_comments', 'registration_required', 'template_name')}),
)
list_display = ('url', 'title')
list_filter = ('sites', 'enable_comments', 'registration_required')
search_fields = ('url', 'title')
admin.site.register(FlatPage, FlatPageAdmin)

View File

@ -20,16 +20,7 @@ class FlatPage(models.Model):
verbose_name = _('flat page')
verbose_name_plural = _('flat pages')
ordering = ('url',)
class Admin:
fields = (
(None, {'fields': ('url', 'title', 'content', 'sites')}),
(_('Advanced options'), {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
)
list_display = ('url', 'title')
list_filter = ('sites', 'enable_comments', 'registration_required')
search_fields = ('url', 'title')
def __unicode__(self):
return u"%s -- %s" % (self.url, self.title)

View File

@ -3,7 +3,7 @@ from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
class Redirect(models.Model):
site = models.ForeignKey(Site, radio_admin=models.VERTICAL)
site = models.ForeignKey(Site)
old_path = models.CharField(_('redirect from'), max_length=200, db_index=True,
help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'."))
new_path = models.CharField(_('redirect to'), max_length=200, blank=True,
@ -15,11 +15,21 @@ class Redirect(models.Model):
db_table = 'django_redirect'
unique_together=(('site', 'old_path'),)
ordering = ('old_path',)
class Admin:
list_display = ('old_path', 'new_path')
list_filter = ('site',)
search_fields = ('old_path', 'new_path')
def __unicode__(self):
return u"%s ---> %s" % (self.old_path, self.new_path)
return "%s ---> %s" % (self.old_path, self.new_path)
# Register the admin options for these models.
# TODO: Maybe this should live in a separate module admin.py, but how would we
# ensure that module was loaded?
from django.contrib import admin
class RedirectAdmin(admin.ModelAdmin):
list_display = ('old_path', 'new_path')
list_filter = ('site',)
search_fields = ('old_path', 'new_path')
radio_fields = {'site': admin.VERTICAL}
admin.site.register(Redirect, RedirectAdmin)

View File

@ -0,0 +1,9 @@
from django.contrib import admin
from django.contrib.sites.models import Site
class SiteAdmin(admin.ModelAdmin):
list_display = ('domain', 'name')
search_fields = ('domain', 'name')
admin.site.register(Site, SiteAdmin)

View File

@ -32,18 +32,16 @@ class Site(models.Model):
domain = models.CharField(_('domain name'), max_length=100)
name = models.CharField(_('display name'), max_length=50)
objects = SiteManager()
class Meta:
db_table = 'django_site'
verbose_name = _('site')
verbose_name_plural = _('sites')
ordering = ('domain',)
class Admin:
list_display = ('domain', 'name')
search_fields = ('domain', 'name')
def __unicode__(self):
return self.domain
def delete(self):
pk = self.pk
super(Site, self).delete()
@ -51,7 +49,6 @@ class Site(models.Model):
del(SITE_CACHE[pk])
except KeyError:
pass
class RequestSite(object):
"""

View File

@ -51,8 +51,6 @@ def get_validation_errors(outfile, app=None):
from PIL import Image
except ImportError:
e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name)
if f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple):
e.add(opts, '"%s": prepopulate_from should be a list or tuple.' % f.name)
if f.choices:
if isinstance(f.choices, basestring) or not is_iterable(f.choices):
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
@ -145,54 +143,6 @@ def get_validation_errors(outfile, app=None):
if r.get_accessor_name() == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
# Check admin attribute.
if opts.admin is not None:
if not isinstance(opts.admin, models.AdminOptions):
e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.')
else:
# list_display
if not isinstance(opts.admin.list_display, (list, tuple)):
e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.')
else:
for fn in opts.admin.list_display:
try:
f = opts.get_field(fn)
except models.FieldDoesNotExist:
if not hasattr(cls, fn):
e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn)
else:
if isinstance(f, models.ManyToManyField):
e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn)
# list_display_links
if opts.admin.list_display_links and not opts.admin.list_display:
e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.')
if not isinstance(opts.admin.list_display_links, (list, tuple)):
e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.')
else:
for fn in opts.admin.list_display_links:
try:
f = opts.get_field(fn)
except models.FieldDoesNotExist:
if not hasattr(cls, fn):
e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn)
if fn not in opts.admin.list_display:
e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn)
# list_filter
if not isinstance(opts.admin.list_filter, (list, tuple)):
e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.')
else:
for fn in opts.admin.list_filter:
try:
f = opts.get_field(fn)
except models.FieldDoesNotExist:
e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn)
# date_hierarchy
if opts.admin.date_hierarchy:
try:
f = opts.get_field(opts.admin.date_hierarchy)
except models.FieldDoesNotExist:
e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy)
# Check ordering attribute.
if opts.ordering:
for field_name in opts.ordering:
@ -210,18 +160,6 @@ def get_validation_errors(outfile, app=None):
except models.FieldDoesNotExist:
e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
# Check core=True, if needed.
for related in opts.get_followed_related_objects():
if not related.edit_inline:
continue
try:
for f in related.opts.fields:
if f.core:
raise StopIteration
e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
except StopIteration:
pass
# Check unique_together.
for ut in opts.unique_together:
for field_name in ut:

View File

@ -5,7 +5,7 @@ from django.db import connection
from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
from django.db.models.query import Q
from django.db.models.manager import Manager
from django.db.models.base import Model, AdminOptions
from django.db.models.base import Model
from django.db.models.fields import *
from django.db.models.fields.subclassing import SubfieldBase
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED

View File

@ -15,7 +15,7 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned,
from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
from django.db.models.query import delete_objects, Q, CollectedObjects
from django.db.models.options import Options, AdminOptions
from django.db.models.options import Options
from django.db import connection, transaction
from django.db.models import signals
from django.db.models.loading import register_models, get_model
@ -137,9 +137,6 @@ class ModelBase(type):
return get_model(new_class._meta.app_label, name, False)
def add_to_class(cls, name, value):
if name == 'Admin':
assert type(value) == types.ClassType, "%r attribute of %s model must be a class, not a %s object" % (name, cls.__name__, type(value))
value = AdminOptions(**dict([(k, v) for k, v in value.__dict__.items() if not k.startswith('_')]))
if hasattr(value, 'contribute_to_class'):
value.contribute_to_class(cls, name)
else:

View File

@ -28,16 +28,10 @@ from django.utils import datetime_safe
class NOT_PROVIDED:
pass
# Values for filter_interface.
HORIZONTAL, VERTICAL = 1, 2
# The values to use for "blank" in SelectFields. Will be appended to the start of most "choices" lists.
BLANK_CHOICE_DASH = [("", "---------")]
BLANK_CHOICE_NONE = [("", "None")]
# returns the <ul> class for a given radio_admin value
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
class FieldDoesNotExist(Exception):
pass
@ -85,10 +79,10 @@ class Field(object):
def __init__(self, verbose_name=None, name=None, primary_key=False,
max_length=None, unique=False, blank=False, null=False,
db_index=False, core=False, rel=None, default=NOT_PROVIDED,
editable=True, serialize=True, prepopulate_from=None,
unique_for_date=None, unique_for_month=None, unique_for_year=None,
validator_list=None, choices=None, radio_admin=None, help_text='',
db_column=None, db_tablespace=None, auto_created=False):
editable=True, serialize=True, unique_for_date=None,
unique_for_month=None, unique_for_year=None, validator_list=None,
choices=None, help_text='', db_column=None, db_tablespace=None,
auto_created=False):
self.name = name
self.verbose_name = verbose_name
self.primary_key = primary_key
@ -102,11 +96,9 @@ class Field(object):
self.editable = editable
self.serialize = serialize
self.validator_list = validator_list or []
self.prepopulate_from = prepopulate_from
self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
self.unique_for_year = unique_for_year
self._choices = choices or []
self.radio_admin = radio_admin
self.help_text = help_text
self.db_column = db_column
self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE
@ -294,11 +286,7 @@ class Field(object):
params['max_length'] = self.max_length
if self.choices:
if self.radio_admin:
field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
field_objs = [oldforms.SelectField]
field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default()
else:
@ -386,10 +374,7 @@ class Field(object):
return first_choice + lst
def get_choices_default(self):
if self.radio_admin:
return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
else:
return self.get_choices()
return self.get_choices()
def _get_val_from_obj(self, obj):
if obj:
@ -1012,7 +997,11 @@ class NullBooleanField(Field):
return [oldforms.NullBooleanField]
def formfield(self, **kwargs):
defaults = {'form_class': forms.NullBooleanField}
defaults = {
'form_class': forms.NullBooleanField,
'required': not self.blank,
'label': capfirst(self.verbose_name),
'help_text': self.help_text}
defaults.update(kwargs)
return super(NullBooleanField, self).formfield(**defaults)

View File

@ -1,6 +1,6 @@
from django.db import connection, transaction
from django.db.models import signals, get_model
from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class, FieldDoesNotExist
from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist
from django.db.models.related import RelatedObject
from django.db.models.query_utils import QueryWrapper
from django.utils.text import capfirst
@ -541,7 +541,7 @@ class ManyToOneRel(object):
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None,
raw_id_admin=False, parent_link=False):
parent_link=False):
try:
to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
@ -554,7 +554,6 @@ class ManyToOneRel(object):
limit_choices_to = {}
self.limit_choices_to = limit_choices_to
self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin
self.multiple = True
self.parent_link = parent_link
@ -573,34 +572,29 @@ class OneToOneRel(ManyToOneRel):
def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=None, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None,
raw_id_admin=False, parent_link=False):
parent_link=False):
# NOTE: *_num_in_admin and num_extra_on_change are intentionally
# ignored here. We accept them as parameters only to match the calling
# signature of ManyToOneRel.__init__().
super(OneToOneRel, self).__init__(to, field_name, num_in_admin,
edit_inline=edit_inline, related_name=related_name,
limit_choices_to=limit_choices_to,
lookup_overrides=lookup_overrides, raw_id_admin=raw_id_admin,
parent_link=parent_link)
lookup_overrides=lookup_overrides, parent_link=parent_link)
self.multiple = False
class ManyToManyRel(object):
def __init__(self, to, num_in_admin=0, related_name=None,
filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
limit_choices_to=None, symmetrical=True):
self.to = to
self.num_in_admin = num_in_admin
self.related_name = related_name
self.filter_interface = filter_interface
if limit_choices_to is None:
limit_choices_to = {}
self.limit_choices_to = limit_choices_to
self.edit_inline = False
self.raw_id_admin = raw_id_admin
self.symmetrical = symmetrical
self.multiple = True
assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
@ -626,7 +620,6 @@ class ForeignKey(RelatedField, Field):
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
lookup_overrides=kwargs.pop('lookup_overrides', None),
raw_id_admin=kwargs.pop('raw_id_admin', False),
parent_link=kwargs.pop('parent_link', False))
Field.__init__(self, **kwargs)
@ -640,19 +633,11 @@ class ForeignKey(RelatedField, Field):
def prepare_field_objs_and_params(self, manipulator, name_prefix):
params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
if self.rel.raw_id_admin:
field_objs = self.get_manipulator_field_objs()
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
if self.null:
field_objs = [oldforms.NullSelectField]
else:
if self.radio_admin:
field_objs = [oldforms.RadioSelectField]
params['ul_class'] = get_ul_class(self.radio_admin)
else:
if self.null:
field_objs = [oldforms.NullSelectField]
else:
field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default()
field_objs = [oldforms.SelectField]
params['choices'] = self.get_choices_default()
return field_objs, params
def get_default(self):
@ -664,10 +649,7 @@ class ForeignKey(RelatedField, Field):
def get_manipulator_field_objs(self):
rel_field = self.rel.get_related_field()
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
return rel_field.get_manipulator_field_objs()
else:
return [oldforms.IntegerField]
return [oldforms.IntegerField]
def get_db_prep_save(self, value):
if value == '' or value == None:
@ -679,15 +661,11 @@ class ForeignKey(RelatedField, Field):
if not obj:
# In required many-to-one fields with only one available choice,
# select that one available choice. Note: For SelectFields
# (radio_admin=False), we have to check that the length of choices
# is *2*, not 1, because SelectFields always have an initial
# "blank" value. Otherwise (radio_admin=True), we check that the
# length is 1.
if not self.blank and (not self.rel.raw_id_admin or self.choices):
# we have to check that the length of choices is *2*, not 1,
# because SelectFields always have an initial "blank" value.
if not self.blank and self.choices:
choice_list = self.get_choices_default()
if self.radio_admin and len(choice_list) == 1:
return {self.attname: choice_list[0][0]}
if not self.radio_admin and len(choice_list) == 2:
if len(choice_list) == 2:
return {self.attname: choice_list[1][0]}
return Field.flatten_data(self, follow, obj)
@ -704,7 +682,7 @@ class ForeignKey(RelatedField, Field):
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
def formfield(self, **kwargs):
defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.all()}
defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)}
defaults.update(kwargs)
return super(ForeignKey, self).formfield(**defaults)
@ -743,27 +721,17 @@ class ManyToManyField(RelatedField, Field):
kwargs['rel'] = ManyToManyRel(to,
num_in_admin=kwargs.pop('num_in_admin', 0),
related_name=kwargs.pop('related_name', None),
filter_interface=kwargs.pop('filter_interface', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
raw_id_admin=kwargs.pop('raw_id_admin', False),
symmetrical=kwargs.pop('symmetrical', True))
self.db_table = kwargs.pop('db_table', None)
if kwargs["rel"].raw_id_admin:
kwargs.setdefault("validator_list", []).append(self.isValidIDList)
Field.__init__(self, **kwargs)
if self.rel.raw_id_admin:
msg = ugettext_lazy('Separate multiple IDs with commas.')
else:
msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat(self.help_text, ' ', msg)
def get_manipulator_field_objs(self):
if self.rel.raw_id_admin:
return [oldforms.RawIdAdminField]
else:
choices = self.get_choices_default()
return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
choices = self.get_choices_default()
return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
def get_choices_default(self):
return Field.get_choices(self, include_blank=False)
@ -812,14 +780,11 @@ class ManyToManyField(RelatedField, Field):
new_data = {}
if obj:
instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
if self.rel.raw_id_admin:
new_data[self.name] = u",".join([smart_unicode(id) for id in instance_ids])
else:
new_data[self.name] = instance_ids
new_data[self.name] = instance_ids
else:
# In required many-to-many fields with only one available choice,
# select that one available choice.
if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin:
if not self.blank and not self.rel.edit_inline:
choices_list = self.get_choices_default()
if len(choices_list) == 1:
new_data[self.name] = [choices_list[0][0]]
@ -861,7 +826,7 @@ class ManyToManyField(RelatedField, Field):
setattr(instance, self.attname, data)
def formfield(self, **kwargs):
defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()}
defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)}
defaults.update(kwargs)
# If initial is passed in, it's a list of related objects, but the
# MultipleChoiceField takes a list of IDs.

View File

@ -120,10 +120,7 @@ class AutomaticManipulator(oldforms.Manipulator):
for f in self.opts.many_to_many:
if self.follow.get(f.name, None):
if not f.rel.edit_inline:
if f.rel.raw_id_admin:
new_vals = new_data.get(f.name, ())
else:
new_vals = new_data.getlist(f.name)
new_vals = new_data.getlist(f.name)
# First, clear the existing values.
rel_manager = getattr(new_object, f.name)
rel_manager.clear()
@ -220,8 +217,6 @@ class AutomaticManipulator(oldforms.Manipulator):
for f in related.opts.many_to_many:
if child_follow.get(f.name, None) and not f.rel.edit_inline:
new_value = rel_new_data[f.attname]
if f.rel.raw_id_admin:
new_value = new_value[0]
setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=new_value))
if self.change:
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))

View File

@ -11,7 +11,6 @@ from django.db.models.fields.related import ManyToManyRel
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.proxy import OrderWrt
from django.db.models.loading import get_models, app_cache_ready
from django.db.models import Manager
from django.utils.translation import activate, deactivate_all, get_language, string_concat
from django.utils.encoding import force_unicode, smart_str
from django.utils.datastructures import SortedDict
@ -485,77 +484,3 @@ class Options(object):
else:
self._field_types[field_type] = False
return self._field_types[field_type]
class AdminOptions(object):
def __init__(self, fields=None, js=None, list_display=None, list_display_links=None, list_filter=None,
date_hierarchy=None, save_as=False, ordering=None, search_fields=None,
save_on_top=False, list_select_related=False, manager=None, list_per_page=100):
self.fields = fields
self.js = js or []
self.list_display = list_display or ['__str__']
self.list_display_links = list_display_links or []
self.list_filter = list_filter or []
self.date_hierarchy = date_hierarchy
self.save_as, self.ordering = save_as, ordering
self.search_fields = search_fields or []
self.save_on_top = save_on_top
self.list_select_related = list_select_related
self.list_per_page = list_per_page
self.manager = manager or Manager()
def get_field_sets(self, opts):
"Returns a list of AdminFieldSet objects for this AdminOptions object."
if self.fields is None:
field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),)
else:
field_struct = self.fields
new_fieldset_list = []
for fieldset in field_struct:
fs_options = fieldset[1]
classes = fs_options.get('classes', ())
description = fs_options.get('description', '')
new_fieldset_list.append(AdminFieldSet(fieldset[0], classes,
opts.get_field, fs_options['fields'], description))
return new_fieldset_list
def contribute_to_class(self, cls, name):
cls._meta.admin = self
# Make sure the admin manager has access to the model
self.manager.model = cls
class AdminFieldSet(object):
def __init__(self, name, classes, field_locator_func, line_specs, description):
self.name = name
self.field_lines = [AdminFieldLine(field_locator_func, line_spec) for line_spec in line_specs]
self.classes = classes
self.description = description
def __repr__(self):
return "FieldSet: (%s, %s)" % (self.name, self.field_lines)
def bind(self, field_mapping, original, bound_field_set_class):
return bound_field_set_class(self, field_mapping, original)
def __iter__(self):
for field_line in self.field_lines:
yield field_line
def __len__(self):
return len(self.field_lines)
class AdminFieldLine(object):
def __init__(self, field_locator_func, linespec):
if isinstance(linespec, basestring):
self.fields = [field_locator_func(linespec)]
else:
self.fields = [field_locator_func(field_name) for field_name in linespec]
def bind(self, field_mapping, original, bound_field_line_class):
return bound_field_line_class(self, field_mapping, original)
def __iter__(self):
for field in self.fields:
yield field
def __len__(self):
return len(self.fields)

View File

@ -15,3 +15,4 @@ from widgets import *
from fields import *
from forms import *
from models import *
from formsets import *

View File

@ -10,7 +10,7 @@ from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
from django.utils.safestring import mark_safe
from fields import Field, FileField
from widgets import TextInput, Textarea
from widgets import Media, media_property, TextInput, Textarea
from util import flatatt, ErrorDict, ErrorList, ValidationError
__all__ = ('BaseForm', 'Form')
@ -31,6 +31,7 @@ def get_declared_fields(bases, attrs, with_base_fields=True):
If 'with_base_fields' is True, all fields from the bases are used.
Otherwise, only fields in the 'declared_fields' attribute on the bases are
used. The distinction is useful in ModelForm subclassing.
Also integrates any additional media definitions
"""
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
@ -56,8 +57,11 @@ class DeclarativeFieldsMetaclass(type):
"""
def __new__(cls, name, bases, attrs):
attrs['base_fields'] = get_declared_fields(bases, attrs)
return super(DeclarativeFieldsMetaclass,
new_class = super(DeclarativeFieldsMetaclass,
cls).__new__(cls, name, bases, attrs)
if 'media' not in attrs:
new_class.media = media_property(new_class)
return new_class
class BaseForm(StrAndUnicode):
# This is the main implementation of all the Form logic. Note that this
@ -65,7 +69,8 @@ class BaseForm(StrAndUnicode):
# information. Any improvements to the form API should be made to *this*
# class, not to the Form class.
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':'):
initial=None, error_class=ErrorList, label_suffix=':',
empty_permitted=False):
self.is_bound = data is not None or files is not None
self.data = data or {}
self.files = files or {}
@ -74,7 +79,9 @@ class BaseForm(StrAndUnicode):
self.initial = initial or {}
self.error_class = error_class
self.label_suffix = label_suffix
self.empty_permitted = empty_permitted
self._errors = None # Stores the errors after clean() has been called.
self._changed_data = None
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
@ -194,6 +201,10 @@ class BaseForm(StrAndUnicode):
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
@ -229,6 +240,40 @@ class BaseForm(StrAndUnicode):
"""
return self.cleaned_data
def has_changed(self):
"""
Returns True if data differs from initial.
"""
return bool(self.changed_data)
def _get_changed_data(self):
if self._changed_data is None:
self._changed_data = []
# XXX: For now we're asking the individual widgets whether or not the
# data has changed. It would probably be more efficient to hash the
# initial data, store it in a hidden field, and compare a hash of the
# submitted data, but we'd need a way to easily get the string value
# for a given field. Right now, that logic is embedded in the render
# method of each widget.
for name, field in self.fields.items():
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
initial_value = self.initial.get(name, field.initial)
if field.widget._has_changed(initial_value, data_value):
self._changed_data.append(name)
return self._changed_data
changed_data = property(_get_changed_data)
def _get_media(self):
"""
Provide a description of all media required to render the widgets on this form
"""
media = Media()
for field in self.fields.values():
media = media + field.widget.media
return media
media = property(_get_media)
def is_multipart(self):
"""
Returns True if the form needs to be multipart-encrypted, i.e. it has

292
django/newforms/formsets.py Normal file
View File

@ -0,0 +1,292 @@
from forms import Form
from django.utils.encoding import StrAndUnicode
from django.utils.safestring import mark_safe
from fields import IntegerField, BooleanField
from widgets import Media, HiddenInput, TextInput
from util import ErrorList, ValidationError
__all__ = ('BaseFormSet', 'all_valid')
# special field names
TOTAL_FORM_COUNT = 'TOTAL_FORMS'
INITIAL_FORM_COUNT = 'INITIAL_FORMS'
MAX_FORM_COUNT = 'MAX_FORMS'
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'
class ManagementForm(Form):
"""
``ManagementForm`` is used to keep track of how many form instances
are displayed on the page. If adding new forms via javascript, you should
increment the count field of this form as well.
"""
def __init__(self, *args, **kwargs):
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
self.base_fields[MAX_FORM_COUNT] = IntegerField(widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
class BaseFormSet(StrAndUnicode):
"""
A collection of instances of the same Form class.
"""
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList):
self.is_bound = data is not None or files is not None
self.prefix = prefix or 'form'
self.auto_id = auto_id
self.data = data
self.files = files
self.initial = initial
self.error_class = error_class
self._errors = None
self._non_form_errors = None
# initialization is different depending on whether we recieved data, initial, or nothing
if data or files:
self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix)
if self.management_form.is_valid():
self._total_form_count = self.management_form.cleaned_data[TOTAL_FORM_COUNT]
self._initial_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT]
self._max_form_count = self.management_form.cleaned_data[MAX_FORM_COUNT]
else:
raise ValidationError('ManagementForm data is missing or has been tampered with')
else:
if initial:
self._initial_form_count = len(initial)
if self._initial_form_count > self._max_form_count and self._max_form_count > 0:
self._initial_form_count = self._max_form_count
self._total_form_count = self._initial_form_count + self.extra
else:
self._initial_form_count = 0
self._total_form_count = self.extra
if self._total_form_count > self._max_form_count and self._max_form_count > 0:
self._total_form_count = self._max_form_count
initial = {TOTAL_FORM_COUNT: self._total_form_count,
INITIAL_FORM_COUNT: self._initial_form_count,
MAX_FORM_COUNT: self._max_form_count}
self.management_form = ManagementForm(initial=initial, auto_id=self.auto_id, prefix=self.prefix)
# construct the forms in the formset
self._construct_forms()
def __unicode__(self):
return self.as_table()
def _construct_forms(self):
# instantiate all the forms and put them in self.forms
self.forms = []
for i in xrange(self._total_form_count):
self.forms.append(self._construct_form(i))
def _construct_form(self, i, **kwargs):
"""
Instantiates and returns the i-th form instance in a formset.
"""
defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
if self.data or self.files:
defaults['data'] = self.data
defaults['files'] = self.files
if self.initial:
try:
defaults['initial'] = self.initial[i]
except IndexError:
pass
# Allow extra forms to be empty.
if i >= self._initial_form_count:
defaults['empty_permitted'] = True
defaults.update(kwargs)
form = self.form(**defaults)
self.add_fields(form, i)
return form
def _get_initial_forms(self):
"""Return a list of all the intial forms in this formset."""
return self.forms[:self._initial_form_count]
initial_forms = property(_get_initial_forms)
def _get_extra_forms(self):
"""Return a list of all the extra forms in this formset."""
return self.forms[self._initial_form_count:]
extra_forms = property(_get_extra_forms)
# Maybe this should just go away?
def _get_cleaned_data(self):
"""
Returns a list of form.cleaned_data dicts for every form in self.forms.
"""
if not self.is_valid():
raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__)
return [form.cleaned_data for form in self.forms]
cleaned_data = property(_get_cleaned_data)
def _get_deleted_forms(self):
"""
Returns a list of forms that have been marked for deletion. Raises an
AttributeError is deletion is not allowed.
"""
if not self.is_valid() or not self.can_delete:
raise AttributeError("'%s' object has no attribute 'deleted_forms'" % self.__class__.__name__)
# construct _deleted_form_indexes which is just a list of form indexes
# that have had their deletion widget set to True
if not hasattr(self, '_deleted_form_indexes'):
self._deleted_form_indexes = []
for i in range(0, self._total_form_count):
form = self.forms[i]
# if this is an extra form and hasn't changed, don't consider it
if i >= self._initial_form_count and not form.has_changed():
continue
if form.cleaned_data[DELETION_FIELD_NAME]:
self._deleted_form_indexes.append(i)
return [self.forms[i] for i in self._deleted_form_indexes]
deleted_forms = property(_get_deleted_forms)
def _get_ordered_forms(self):
"""
Returns a list of form in the order specified by the incoming data.
Raises an AttributeError is deletion is not allowed.
"""
if not self.is_valid() or not self.can_order:
raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__)
# Construct _ordering, which is a list of (form_index, order_field_value)
# tuples. After constructing this list, we'll sort it by order_field_value
# so we have a way to get to the form indexes in the order specified
# by the form data.
if not hasattr(self, '_ordering'):
self._ordering = []
for i in range(0, self._total_form_count):
form = self.forms[i]
# if this is an extra form and hasn't changed, don't consider it
if i >= self._initial_form_count and not form.has_changed():
continue
# don't add data marked for deletion to self.ordered_data
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
continue
# A sort function to order things numerically ascending, but
# None should be sorted below anything else. Allowing None as
# a comparison value makes it so we can leave ordering fields
# blamk.
def compare_ordering_values(x, y):
if x[1] is None:
return 1
if y[1] is None:
return -1
return x[1] - y[1]
self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME]))
# After we're done populating self._ordering, sort it.
self._ordering.sort(compare_ordering_values)
# Return a list of form.cleaned_data dicts in the order spcified by
# the form data.
return [self.forms[i[0]] for i in self._ordering]
ordered_forms = property(_get_ordered_forms)
def non_form_errors(self):
"""
Returns an ErrorList of errors that aren't associated with a particular
form -- i.e., from formset.clean(). Returns an empty ErrorList if there
are none.
"""
if self._non_form_errors is not None:
return self._non_form_errors
return self.error_class()
def _get_errors(self):
"""
Returns a list of form.errors for every form in self.forms.
"""
if self._errors is None:
self.full_clean()
return self._errors
errors = property(_get_errors)
def is_valid(self):
"""
Returns True if form.errors is empty for every form in self.forms.
"""
if not self.is_bound:
return False
# We loop over every form.errors here rather than short circuiting on the
# first failure to make sure validation gets triggered for every form.
forms_valid = True
for errors in self.errors:
if bool(errors):
forms_valid = False
return forms_valid and not bool(self.non_form_errors())
def full_clean(self):
"""
Cleans all of self.data and populates self._errors.
"""
self._errors = []
if not self.is_bound: # Stop further processing.
return
for i in range(0, self._total_form_count):
form = self.forms[i]
self._errors.append(form.errors)
# Give self.clean() a chance to do cross-form validation.
try:
self.clean()
except ValidationError, e:
self._non_form_errors = e.messages
def clean(self):
"""
Hook for doing any extra formset-wide cleaning after Form.clean() has
been called on every form. Any ValidationError raised by this method
will not be associated with a particular form; it will be accesible
via formset.non_form_errors()
"""
pass
def add_fields(self, form, index):
"""A hook for adding extra fields on to each form instance."""
if self.can_order:
# Only pre-fill the ordering field for initial forms.
if index < self._initial_form_count:
form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1, required=False)
else:
form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', required=False)
if self.can_delete:
form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False)
def add_prefix(self, index):
return '%s-%s' % (self.prefix, index)
def is_multipart(self):
"""
Returns True if the formset needs to be multipart-encrypted, i.e. it
has FileInput. Otherwise, False.
"""
return self.forms[0].is_multipart()
def _get_media(self):
# All the forms on a FormSet are the same, so you only need to
# interrogate the first form for media.
if self.forms:
return self.forms[0].media
else:
return Media()
media = property(_get_media)
def as_table(self):
"Returns this formset rendered as HTML <tr>s -- excluding the <table></table>."
# XXX: there is no semantic division between forms here, there
# probably should be. It might make sense to render each form as a
# table row with each field as a td.
forms = u' '.join([form.as_table() for form in self.forms])
return mark_safe(u'\n'.join([unicode(self.management_form), forms]))
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
can_delete=False, max_num=0):
"""Return a FormSet for the given form class."""
attrs = {'form': form, 'extra': extra,
'can_order': can_order, 'can_delete': can_delete,
'_max_form_count': max_num}
return type(form.__name__ + 'FormSet', (formset,), attrs)
def all_valid(formsets):
"""Returns true if every formset in formsets is valid."""
valid = True
for formset in formsets:
if not formset.is_valid():
valid = False
return valid

View File

@ -12,13 +12,15 @@ from django.core.exceptions import ImproperlyConfigured
from util import ValidationError, ErrorList
from forms import BaseForm, get_declared_fields
from fields import Field, ChoiceField, EMPTY_VALUES
from widgets import Select, SelectMultiple, MultipleHiddenInput
from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
from widgets import media_property
from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
__all__ = (
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
'ModelChoiceField', 'ModelMultipleChoiceField'
'ModelChoiceField', 'ModelMultipleChoiceField',
)
def save_instance(form, instance, fields=None, fail_message='saved',
@ -30,7 +32,7 @@ def save_instance(form, instance, fields=None, fail_message='saved',
database. Returns ``instance``.
"""
from django.db import models
opts = instance.__class__._meta
opts = instance._meta
if form.errors:
raise ValueError("The %s could not be %s because the data didn't"
" validate." % (opts.object_name, fail_message))
@ -44,7 +46,7 @@ def save_instance(form, instance, fields=None, fail_message='saved',
f.save_form_data(instance, cleaned_data[f.name])
# Wrap up the saving of m2m data as a function.
def save_m2m():
opts = instance.__class__._meta
opts = instance._meta
cleaned_data = form.cleaned_data
for f in opts.many_to_many:
if fields and f.name not in fields:
@ -226,6 +228,8 @@ class ModelFormMetaclass(type):
if not parents:
return new_class
if 'media' not in attrs:
new_class.media = media_property(new_class)
declared_fields = get_declared_fields(bases, attrs, False)
opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
if opts.model:
@ -244,7 +248,7 @@ class ModelFormMetaclass(type):
class BaseModelForm(BaseForm):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':',
instance=None):
empty_permitted=False, instance=None):
opts = self._meta
if instance is None:
# if we didn't get an instance, instantiate a new one
@ -256,7 +260,8 @@ class BaseModelForm(BaseForm):
# if initial was provided, it should override the values from instance
if initial is not None:
object_data.update(initial)
BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
BaseForm.__init__(self, data, files, auto_id, prefix, object_data,
error_class, label_suffix, empty_permitted)
def save(self, commit=True):
"""
@ -275,6 +280,209 @@ class BaseModelForm(BaseForm):
class ModelForm(BaseModelForm):
__metaclass__ = ModelFormMetaclass
def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
formfield_callback=lambda f: f.formfield()):
# HACK: we should be able to construct a ModelForm without creating
# and passing in a temporary inner class
class Meta:
pass
setattr(Meta, 'model', model)
setattr(Meta, 'fields', fields)
setattr(Meta, 'exclude', exclude)
class_name = model.__name__ + 'Form'
return ModelFormMetaclass(class_name, (form,), {'Meta': Meta,
'formfield_callback': formfield_callback})
# ModelFormSets ##############################################################
class BaseModelFormSet(BaseFormSet):
"""
A ``FormSet`` for editing a queryset and/or adding new objects to it.
"""
model = None
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
queryset=None, **kwargs):
self.queryset = queryset
defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix}
if self._max_form_count > 0:
qs = self.get_queryset()[:self._max_form_count]
else:
qs = self.get_queryset()
defaults['initial'] = [model_to_dict(obj) for obj in qs]
defaults.update(kwargs)
super(BaseModelFormSet, self).__init__(**defaults)
def get_queryset(self):
if self.queryset is not None:
return self.queryset
return self.model._default_manager.get_query_set()
def save_new(self, form, commit=True):
"""Saves and returns a new model instance for the given form."""
return save_instance(form, self.model(), commit=commit)
def save_existing(self, form, instance, commit=True):
"""Saves and returns an existing model instance for the given form."""
return save_instance(form, instance, commit=commit)
def save(self, commit=True):
"""Saves model instances for every form, adding and changing instances
as necessary, and returns the list of instances.
"""
if not commit:
self.saved_forms = []
def save_m2m():
for form in self.saved_forms:
form.save_m2m()
self.save_m2m = save_m2m
return self.save_existing_objects(commit) + self.save_new_objects(commit)
def save_existing_objects(self, commit=True):
self.changed_objects = []
self.deleted_objects = []
if not self.get_queryset():
return []
# Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
existing_objects = {}
for obj in self.get_queryset():
existing_objects[obj.pk] = obj
saved_instances = []
for form in self.initial_forms:
obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
self.deleted_objects.append(obj)
obj.delete()
else:
if form.changed_data:
self.changed_objects.append((obj, form.changed_data))
saved_instances.append(self.save_existing(form, obj, commit=commit))
if not commit:
self.saved_forms.append(form)
return saved_instances
def save_new_objects(self, commit=True):
self.new_objects = []
for form in self.extra_forms:
if not form.has_changed():
continue
# If someone has marked an add form for deletion, don't save the
# object.
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
continue
self.new_objects.append(self.save_new(form, commit=commit))
if not commit:
self.saved_forms.append(form)
return self.new_objects
def add_fields(self, form, index):
"""Add a hidden field for the object's primary key."""
self._pk_field_name = self.model._meta.pk.attname
form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput)
super(BaseModelFormSet, self).add_fields(form, index)
def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
formset=BaseModelFormSet,
extra=1, can_delete=False, can_order=False,
max_num=0, fields=None, exclude=None):
"""
Returns a FormSet class for the given Django model class.
"""
form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
formfield_callback=formfield_callback)
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
can_order=can_order, can_delete=can_delete)
FormSet.model = model
return FormSet
# InlineFormSets #############################################################
class BaseInlineFormset(BaseModelFormSet):
"""A formset for child objects related to a parent."""
def __init__(self, data=None, files=None, instance=None, save_as_new=False):
from django.db.models.fields.related import RelatedObject
self.instance = instance
self.save_as_new = save_as_new
# is there a better way to get the object descriptor?
self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
super(BaseInlineFormset, self).__init__(data, files, prefix=self.rel_name)
def _construct_forms(self):
if self.save_as_new:
self._total_form_count = self._initial_form_count
self._initial_form_count = 0
super(BaseInlineFormset, self)._construct_forms()
def get_queryset(self):
"""
Returns this FormSet's queryset, but restricted to children of
self.instance
"""
kwargs = {self.fk.name: self.instance}
return self.model._default_manager.filter(**kwargs)
def save_new(self, form, commit=True):
kwargs = {self.fk.get_attname(): self.instance.pk}
new_obj = self.model(**kwargs)
return save_instance(form, new_obj, commit=commit)
def _get_foreign_key(parent_model, model, fk_name=None):
"""
Finds and returns the ForeignKey from model to parent if there is one.
If fk_name is provided, assume it is the name of the ForeignKey field.
"""
# avoid circular import
from django.db.models import ForeignKey
opts = model._meta
if fk_name:
fks_to_parent = [f for f in opts.fields if f.name == fk_name]
if len(fks_to_parent) == 1:
fk = fks_to_parent[0]
if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model:
raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model))
elif len(fks_to_parent) == 0:
raise Exception("%s has no field named '%s'" % (model, fk_name))
else:
# Try to discover what the ForeignKey from model to parent_model is
fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model]
if len(fks_to_parent) == 1:
fk = fks_to_parent[0]
elif len(fks_to_parent) == 0:
raise Exception("%s has no ForeignKey to %s" % (model, parent_model))
else:
raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model))
return fk
def inlineformset_factory(parent_model, model, form=ModelForm,
formset=BaseInlineFormset, fk_name=None,
fields=None, exclude=None,
extra=3, can_order=False, can_delete=True, max_num=0,
formfield_callback=lambda f: f.formfield()):
"""
Returns an ``InlineFormset`` for the given kwargs.
You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
to ``parent_model``.
"""
fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
# let the formset handle object deletion by default
if exclude is not None:
exclude.append(fk.name)
else:
exclude = [fk.name]
FormSet = modelformset_factory(model, form=form,
formfield_callback=formfield_callback,
formset=formset,
extra=extra, can_delete=can_delete, can_order=can_order,
fields=fields, exclude=exclude, max_num=max_num)
FormSet.fk = fk
return FormSet
# Fields #####################################################################

View File

@ -9,7 +9,7 @@ except NameError:
import copy
from itertools import chain
from django.conf import settings
from django.utils.datastructures import MultiValueDict
from django.utils.html import escape, conditional_escape
from django.utils.translation import ugettext
@ -17,16 +17,118 @@ from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
from django.utils import datetime_safe
from util import flatatt
from urlparse import urljoin
__all__ = (
'Widget', 'TextInput', 'PasswordInput',
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
'HiddenInput', 'MultipleHiddenInput',
'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
)
MEDIA_TYPES = ('css','js')
class Media(StrAndUnicode):
def __init__(self, media=None, **kwargs):
if media:
media_attrs = media.__dict__
else:
media_attrs = kwargs
self._css = {}
self._js = []
for name in MEDIA_TYPES:
getattr(self, 'add_' + name)(media_attrs.get(name, None))
# Any leftover attributes must be invalid.
# if media_attrs != {}:
# raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
def __unicode__(self):
return self.render()
def render(self):
return u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))
def render_js(self):
return [u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path) for path in self._js]
def render_css(self):
# To keep rendering order consistent, we can't just iterate over items().
# We need to sort the keys, and iterate over the sorted list.
media = self._css.keys()
media.sort()
return chain(*[
[u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium)
for path in self._css[medium]]
for medium in media])
def absolute_path(self, path):
if path.startswith(u'http://') or path.startswith(u'https://') or path.startswith(u'/'):
return path
return urljoin(settings.MEDIA_URL,path)
def __getitem__(self, name):
"Returns a Media object that only contains media of the given type"
if name in MEDIA_TYPES:
return Media(**{name: getattr(self, '_' + name)})
raise KeyError('Unknown media type "%s"' % name)
def add_js(self, data):
if data:
self._js.extend([path for path in data if path not in self._js])
def add_css(self, data):
if data:
for medium, paths in data.items():
self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]])
def __add__(self, other):
combined = Media()
for name in MEDIA_TYPES:
getattr(combined, 'add_' + name)(getattr(self, '_' + name, None))
getattr(combined, 'add_' + name)(getattr(other, '_' + name, None))
return combined
def media_property(cls):
def _media(self):
# Get the media property of the superclass, if it exists
if hasattr(super(cls, self), 'media'):
base = super(cls, self).media
else:
base = Media()
# Get the media definition for this class
definition = getattr(cls, 'Media', None)
if definition:
extend = getattr(definition, 'extend', True)
if extend:
if extend == True:
m = base
else:
m = Media()
for medium in extend:
m = m + base[medium]
return m + Media(definition)
else:
return Media(definition)
else:
return base
return property(_media)
class MediaDefiningClass(type):
"Metaclass for classes that can have media definitions"
def __new__(cls, name, bases, attrs):
new_class = super(MediaDefiningClass, cls).__new__(cls, name, bases,
attrs)
if 'media' not in attrs:
new_class.media = media_property(new_class)
return new_class
class Widget(object):
__metaclass__ = MediaDefiningClass
is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
needs_multipart_form = False # Determines does this widget need multipart-encrypted form
@ -65,6 +167,25 @@ class Widget(object):
"""
return data.get(name, None)
def _has_changed(self, initial, data):
"""
Return True if data differs from initial.
"""
# For purposes of seeing whether something has changed, None is
# the same as an empty string, if the data or inital value we get
# is None, replace it w/ u''.
if data is None:
data_value = u''
else:
data_value = data
if initial is None:
initial_value = u''
else:
initial_value = initial
if force_unicode(initial_value) != force_unicode(data_value):
return True
return False
def id_for_label(self, id_):
"""
Returns the HTML ID attribute of this Widget for use by a <label>,
@ -143,6 +264,11 @@ class FileInput(Input):
def value_from_datadict(self, data, files, name):
"File widgets take data from FILES, not POST"
return files.get(name, None)
def _has_changed(self, initial, data):
if data is None:
return False
return True
class Textarea(Widget):
def __init__(self, attrs=None):
@ -202,6 +328,11 @@ class CheckboxInput(Widget):
return False
return super(CheckboxInput, self).value_from_datadict(data, files, name)
def _has_changed(self, initial, data):
# Sometimes data or initial could be None or u'' which should be the
# same thing as False.
return bool(initial) != bool(data)
class Select(Widget):
def __init__(self, attrs=None, choices=()):
super(Select, self).__init__(attrs)
@ -244,6 +375,11 @@ class NullBooleanSelect(Select):
value = data.get(name, None)
return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
def _has_changed(self, initial, data):
# Sometimes data or initial could be None or u'' which should be the
# same thing as False.
return bool(initial) != bool(data)
class SelectMultiple(Widget):
def __init__(self, attrs=None, choices=()):
super(SelectMultiple, self).__init__(attrs)
@ -268,6 +404,18 @@ class SelectMultiple(Widget):
if isinstance(data, MultiValueDict):
return data.getlist(name)
return data.get(name, None)
def _has_changed(self, initial, data):
if initial is None:
initial = []
if data is None:
data = []
if len(initial) != len(data):
return True
for value1, value2 in zip(initial, data):
if force_unicode(value1) != force_unicode(value2):
return True
return False
class RadioInput(StrAndUnicode):
"""
@ -447,6 +595,16 @@ class MultiWidget(Widget):
def value_from_datadict(self, data, files, name):
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
def _has_changed(self, initial, data):
if initial is None:
initial = [u'' for x in range(0, len(data))]
else:
initial = self.decompress(initial)
for widget, initial, data in zip(self.widgets, initial, data):
if widget._has_changed(initial, data):
return True
return False
def format_output(self, rendered_widgets):
"""
@ -466,6 +624,14 @@ class MultiWidget(Widget):
"""
raise NotImplementedError('Subclasses must implement this method.')
def _get_media(self):
"Media for a multiwidget is the combination of all media of the subwidgets"
media = Media()
for w in self.widgets:
media = media + w.media
return media
media = property(_get_media)
class SplitDateTimeWidget(MultiWidget):
"""
A Widget that splits datetime input into two <input type="text"> boxes.

678
docs/admin.txt Normal file
View File

@ -0,0 +1,678 @@
=====================
The Django admin site
=====================
One of the most powerful parts of Django is the automatic admin interface. It
reads metadata in your model to provide a powerful and production-ready
interface that content producers can immediately use to start adding content to
the site. In this document, we discuss how to activate, use and customize
Django's admin interface.
.. admonition:: Note
The admin site has been refactored significantly since Django 0.96. This
document describes the newest version of the admin site, which allows for
much richer customization. If you follow the development of Django itself,
you may have heard this described as "newforms-admin."
Overview
========
There are four steps in activating the Django admin site:
1. Determine which of your application's models should be editable in the
admin interface.
2. For each of those models, optionally create a ``ModelAdmin`` class that
encapsulates the customized admin functionality and options for that
particular model.
3. Instantiate an ``AdminSite`` and tell it about each of your models and
``ModelAdmin`` classes.
4. Hook the ``AdminSite`` instance into your URLconf.
``ModelAdmin`` objects
======================
The ``ModelAdmin`` class is the representation of a model in the admin
interface. These are stored in a file named ``admin.py`` in your application.
Let's take a look at a very simple example the ``ModelAdmin``::
from django.contrib import admin
from myproject.myapp.models import Author
class AuthorAdmin(admin.ModelAdmin):
pass
admin.site.register(Author, AuthorAdmin)
``ModelAdmin`` Options
----------------------
The ``ModelAdmin`` is very flexible. It has several options for dealing with
customizing the interface. All options are defined on the ``ModelAdmin``
subclass::
class AuthorAdmin(admin.ModelAdmin):
date_hierarchy = 'pub_date'
``date_hierarchy``
~~~~~~~~~~~~~~~~~~
Set ``date_hierarchy`` to the name of a ``DateField`` or ``DateTimeField`` in
your model, and the change list page will include a date-based drilldown
navigation by that field.
Example::
date_hierarchy = 'pub_date'
``fieldsets``
~~~~~~~~~~~~~
Set ``fieldsets`` to control the layout of admin "add" and "change" pages.
``fieldsets`` is a list of two-tuples, in which each two-tuple represents a
``<fieldset>`` on the admin form page. (A ``<fieldset>`` is a "section" of the
form.)
The two-tuples are in the format ``(name, field_options)``, where ``name`` is a
string representing the title of the fieldset and ``field_options`` is a
dictionary of information about the fieldset, including a list of fields to be
displayed in it.
A full example, taken from the ``django.contrib.flatpages.FlatPage`` model::
class FlatPageAdmin(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('url', 'title', 'content', 'sites')
}),
('Advanced options', {
'classes': ('collapse',),
'fields': ('enable_comments', 'registration_required', 'template_name')
}),
)
This results in an admin page that looks like:
.. image:: http://media.djangoproject.com/img/doc/flatfiles_admin.png
If ``fieldsets`` isn't given, Django will default to displaying each field
that isn't an ``AutoField`` and has ``editable=True``, in a single fieldset,
in the same order as the fields are defined in the model.
The ``field_options`` dictionary can have the following keys:
``fields``
A tuple of field names to display in this fieldset. This key is required.
Example::
{
'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
}
To display multiple fields on the same line, wrap those fields in their own
tuple. In this example, the ``first_name`` and ``last_name`` fields will
display on the same line::
{
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
}
``classes``
A string containing extra CSS classes to apply to the fieldset.
Example::
{
'classes': 'wide',
}
Apply multiple classes by separating them with spaces. Example::
{
'classes': 'wide extrapretty',
}
Two useful classes defined by the default admin-site stylesheet are
``collapse`` and ``wide``. Fieldsets with the ``collapse`` style will be
initially collapsed in the admin and replaced with a small "click to expand"
link. Fieldsets with the ``wide`` style will be given extra horizontal space.
``description``
A string of optional extra text to be displayed at the top of each fieldset,
under the heading of the fieldset. It's used verbatim, so you can use any HTML
and you must escape any special HTML characters (such as ampersands) yourself.
``filter_horizontal``
~~~~~~~~~~~~~~~~~~~~~
Use a nifty unobtrusive Javascript "filter" interface instead of the
usability-challenged ``<select multiple>`` in the admin form. The value is a
list of fields that should be displayed as a horizontal filter interface. See
``filter_vertical`` to use a vertical interface.
``filter_vertical``
~~~~~~~~~~~~~~~~~~~
Same as ``filter_horizontal``, but is a vertical display of the filter
interface.
``list_display``
~~~~~~~~~~~~~~~~
Set ``list_display`` to control which fields are displayed on the change list
page of the admin.
Example::
list_display = ('first_name', 'last_name')
If you don't set ``list_display``, the admin site will display a single column
that displays the ``__unicode__()`` representation of each object.
A few special cases to note about ``list_display``:
* If the field is a ``ForeignKey``, Django will display the
``__unicode__()`` of the related object.
* ``ManyToManyField`` fields aren't supported, because that would entail
executing a separate SQL statement for each row in the table. If you
want to do this nonetheless, give your model a custom method, and add
that method's name to ``list_display``. (See below for more on custom
methods in ``list_display``.)
* If the field is a ``BooleanField`` or ``NullBooleanField``, Django will
display a pretty "on" or "off" icon instead of ``True`` or ``False``.
* If the string given is a method of the model, Django will call it and
display the output. This method should have a ``short_description``
function attribute, for use as the header for the field.
Here's a full example model::
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')
* If the string given is a method of the model, Django will HTML-escape the
output by default. If you'd rather not escape the output of the method,
give the method an ``allow_tags`` attribute whose value is ``True``.
Here's a full example model::
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
color_code = models.CharField(max_length=6)
def colored_name(self):
return '<span style="color: #%s;">%s %s</span>' % (self.color_code, self.first_name, self.last_name)
colored_name.allow_tags = True
class PersonAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'colored_name')
* If the string given is a method of the model that returns True or False
Django will display a pretty "on" or "off" icon if you give the method a
``boolean`` attribute whose value is ``True``.
Here's a full example model::
class Person(models.Model):
first_name = models.CharField(max_length=50)
birthday = models.DateField()
def born_in_fifties(self):
return self.birthday.strftime('%Y')[:3] == 5
born_in_fifties.boolean = True
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'born_in_fifties')
* The ``__str__()`` and ``__unicode__()`` methods are just as valid in
``list_display`` as any other model method, so it's perfectly OK to do
this::
list_display = ('__unicode__', 'some_other_field')
* Usually, elements of ``list_display`` that aren't actual database fields
can't be used in sorting (because Django does all the sorting at the
database level).
However, if an element of ``list_display`` represents a certain database
field, you can indicate this fact by setting the ``admin_order_field``
attribute of the item.
For example::
class Person(models.Model):
first_name = models.CharField(max_length=50)
color_code = models.CharField(max_length=6)
def colored_first_name(self):
return '<span style="color: #%s;">%s</span>' % (self.color_code, self.first_name)
colored_first_name.allow_tags = True
colored_first_name.admin_order_field = 'first_name'
class PersonAdmin(admin.ModelAdmin):
list_display = ('first_name', 'colored_first_name')
The above will tell Django to order by the ``first_name`` field when
trying to sort by ``colored_first_name`` in the admin.
``list_display_links``
~~~~~~~~~~~~~~~~~~~~~~
Set ``list_display_links`` to control which fields in ``list_display`` should
be linked to the "change" page for an object.
By default, the change list page will link the first column -- the first field
specified in ``list_display`` -- to the change page for each item. But
``list_display_links`` lets you change which columns are linked. Set
``list_display_links`` to a list or tuple of field names (in the same format as
``list_display``) to link.
``list_display_links`` can specify one or many field names. As long as the
field names appear in ``list_display``, Django doesn't care how many (or how
few) fields are linked. The only requirement is: If you want to use
``list_display_links``, you must define ``list_display``.
In this example, the ``first_name`` and ``last_name`` fields will be linked on
the change list page::
class PersonAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'birthday')
list_display_links = ('first_name', 'last_name')
Finally, note that in order to use ``list_display_links``, you must define
``list_display``, too.
``list_filter``
~~~~~~~~~~~~~~~
Set ``list_filter`` to activate filters in the right sidebar of the change list
page of the admin. This should be a list of field names, and each specified
field should be either a ``BooleanField``, ``CharField``, ``DateField``,
``DateTimeField``, ``IntegerField`` or ``ForeignKey``.
This example, taken from the ``django.contrib.auth.models.User`` model, shows
how both ``list_display`` and ``list_filter`` work::
class UserAdmin(admin.ModelAdmin):
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
The above code results in an admin change list page that looks like this:
.. image:: http://media.djangoproject.com/img/doc/users_changelist.png
(This example also has ``search_fields`` defined. See below.)
``list_per_page``
~~~~~~~~~~~~~~~~~
Set ``list_per_page`` to control how many items appear on each paginated admin
change list page. By default, this is set to ``100``.
``list_select_related``
~~~~~~~~~~~~~~~~~~~~~~~
Set ``list_select_related`` to tell Django to use ``select_related()`` in
retrieving the list of objects on the admin change list page. This can save you
a bunch of database queries.
The value should be either ``True`` or ``False``. Default is ``False``.
Note that Django will use ``select_related()``, regardless of this setting,
if one of the ``list_display`` fields is a ``ForeignKey``.
For more on ``select_related()``, see `the select_related() docs`_.
.. _the select_related() docs: ../db-api/#select-related
``inlines``
~~~~~~~~~~~
See ``InlineModelAdmin`` objects below.
``ordering``
~~~~~~~~~~~~
Set ``ordering`` to specify how objects on the admin change list page should be
ordered. This should be a list or tuple in the same format as a model's
``ordering`` parameter.
If this isn't provided, the Django admin will use the model's default ordering.
``prepopulated_fields``
~~~~~~~~~~~~~~~~~~~~~~~
Set ``prepopulated_fields`` to a dictionary mapping field names to the fields
it should prepopulate from::
class ArticleAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
When set the given fields will use a bit of Javascript to populate from the
fields assigned.
``prepopulated_fields`` doesn't accept DateTimeFields, ForeignKeys nor
ManyToManyFields.
``radio_fields``
~~~~~~~~~~~~~~~~
By default, Django's admin uses a select-box interface (<select>) for
fields that are ``ForeignKey`` or have ``choices`` set. If a field is present
in ``radio_fields``, Django will use a radio-button interface instead.
Assuming ``group`` is a ``ForeignKey`` on the ``Person`` model::
class PersonAdmin(admin.ModelAdmin):
radio_fields = {"group": admin.VERTICAL}
You have the choice of using ``HORIZONTAL`` or ``VERTICAL`` from the
``django.contrib.admin`` module.
Don't include a field in ``radio_fields`` unless it's a ``ForeignKey`` or has
``choices`` set.
``raw_id_fields``
~~~~~~~~~~~~~~~~~
By default, Django's admin uses a select-box interface (<select>) for
fields that are ``ForeignKey``. Sometimes you don't want to incur the
overhead of having to select all the related instances to display in the
drop-down.
``raw_id_fields`` is a list of fields you would like to change
into a ``Input`` widget for the primary key.
``save_as``
~~~~~~~~~~~
Set ``save_as`` to enable a "save as" feature on admin change forms.
Normally, objects have three save options: "Save", "Save and continue editing"
and "Save and add another". If ``save_as`` is ``True``, "Save and add another"
will be replaced by a "Save as" button.
"Save as" means the object will be saved as a new object (with a new ID),
rather than the old object.
By default, ``save_as`` is set to ``False``.
``save_on_top``
~~~~~~~~~~~~~~~
Set ``save_on_top`` to add save buttons across the top of your admin change
forms.
Normally, the save buttons appear only at the bottom of the forms. If you set
``save_on_top``, the buttons will appear both on the top and the bottom.
By default, ``save_on_top`` is set to ``False``.
``search_fields``
~~~~~~~~~~~~~~~~~
Set ``search_fields`` to enable a search box on the admin change list page.
This should be set to a list of field names that will be searched whenever
somebody submits a search query in that text box.
These fields should be some kind of text field, such as ``CharField`` or
``TextField``. You can also perform a related lookup on a ``ForeignKey`` with
the lookup API "follow" notation::
search_fields = ['foreign_key__related_fieldname']
When somebody does a search in the admin search box, Django splits the search
query into words and returns all objects that contain each of the words, case
insensitive, where each word must be in at least one of ``search_fields``. For
example, if ``search_fields`` is set to ``['first_name', 'last_name']`` and a
user searches for ``john lennon``, Django will do the equivalent of this SQL
``WHERE`` clause::
WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%')
AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')
For faster and/or more restrictive searches, prefix the field name
with an operator:
``^``
Matches the beginning of the field. For example, if ``search_fields`` is
set to ``['^first_name', '^last_name']`` and a user searches for
``john lennon``, Django will do the equivalent of this SQL ``WHERE``
clause::
WHERE (first_name ILIKE 'john%' OR last_name ILIKE 'john%')
AND (first_name ILIKE 'lennon%' OR last_name ILIKE 'lennon%')
This query is more efficient than the normal ``'%john%'`` query, because
the database only needs to check the beginning of a column's data, rather
than seeking through the entire column's data. Plus, if the column has an
index on it, some databases may be able to use the index for this query,
even though it's a ``LIKE`` query.
``=``
Matches exactly, case-insensitive. For example, if
``search_fields`` is set to ``['=first_name', '=last_name']`` and
a user searches for ``john lennon``, Django will do the equivalent
of this SQL ``WHERE`` clause::
WHERE (first_name ILIKE 'john' OR last_name ILIKE 'john')
AND (first_name ILIKE 'lennon' OR last_name ILIKE 'lennon')
Note that the query input is split by spaces, so, following this example,
it's currently not possible to search for all records in which
``first_name`` is exactly ``'john winston'`` (containing a space).
``@``
Performs a full-text match. This is like the default search method but uses
an index. Currently this is only available for MySQL.
``ModelAdmin`` media definitions
--------------------------------
There are times where you would like add a bit of CSS and/or Javascript to
the add/change views. This can be accomplished by using a Media inner class
on your ``ModelAdmin``::
class ArticleAdmin(admin.ModelAdmin):
class Media:
css = {
"all": ("my_styles.css",)
}
js = ("my_code.js",)
Keep in mind that this will be prepended with ``MEDIA_URL``. The same rules
apply as `regular media definitions on forms`_.
.. _regular media definitions on forms: ../newforms/#media
``InlineModelAdmin`` objects
============================
The admin interface has the ability to edit models on the same page as a
parent model. These are called inlines. You can add them a model being
specifing them in a ``ModelAdmin.inlines`` attribute::
class BookInline(admin.TabularInline):
model = Book
class AuthorAdmin(admin.ModelAdmin):
inlines = [
BookInline,
]
Django provides two subclasses of ``InlineModelAdmin`` and they are::
* ``TabularInline``
* ``StackedInline``
The difference between these two is merely the template used to render them.
``InlineModelAdmin`` options
-----------------------------
The ``InlineModelAdmin`` class is a subclass of ``ModelAdmin`` so it inherits
all the same functionality as well as some of its own:
``model``
~~~~~~~~~
The model in which the inline is using. This is required.
``fk_name``
~~~~~~~~~~~
The name of the foreign key on the model. In most cases this will be dealt
with automatically, but ``fk_name`` must be specified explicitly if there are
more than one foreign key to the same parent model.
``formset``
~~~~~~~~~~~
This defaults to ``BaseInlineFormset``. Using your own formset can give you
many possibilities of customization. Inlines are built around
`model formsets`_.
.. _model formsets: ../modelforms/#model-formsets
``form``
~~~~~~~~
The value for ``form`` is inherited from ``ModelAdmin``. This is what is
passed through to ``formset_factory`` when creating the formset for this
inline.
``extra``
~~~~~~~~~
This controls the number of extra forms the formset will display in addition
to the initial forms. See the `formsets documentation`_ for more information.
.. _formsets documentation: ../newforms/#formsets
``max_num``
~~~~~~~~~~~
This controls the maximum number of forms to show in the inline. This doesn't
directly corrolate to the number of objects, but can if the value is small
enough. See `max_num in formsets`_ for more information.
.. _max_num in formsets: ../modelforms/#limiting-the-number-of-objects-editable
``template``
~~~~~~~~~~~~
The template used to render the inline on the page.
``verbose_name``
~~~~~~~~~~~~~~~~
An override to the ``verbose_name`` found in the model's inner ``Meta`` class.
``verbose_name_plural``
~~~~~~~~~~~~~~~~~~~~~~~
An override to the ``verbose_name_plural`` found in the model's inner ``Meta``
class.
Working with a model with two or more foreign keys to the same parent model
---------------------------------------------------------------------------
It is sometimes possible to have more than one foreign key to the same model.
Take this model for instance::
class Friendship(models.Model):
to_person = models.ForeignKey(Person, related_name="friends")
from_person = models.ForeignKey(Person, related_name="from_friends")
If you wanted to display an inline on the ``Person`` admin add/change pages
you need to explicitly define the foreign key since it is unable to do so
automatically::
class FriendshipInline(admin.TabularInline):
model = Friendship
fk_name = "to_person"
class PersonAdmin(admin.ModelAdmin):
inlines = [
FriendshipInline,
]
``AdminSite`` objects
=====================
Hooking ``AdminSite`` instances into your URLconf
-------------------------------------------------
The last step in setting up the Django admin is to hook your ``AdminSite``
instance into your URLconf. Do this by pointing a given URL at the
``AdminSite.root`` method.
In this example, we register the default ``AdminSite`` instance
``django.contrib.admin.site`` at the URL ``/admin/`` ::
# urls.py
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
('^admin/(.*)', admin.site.root),
)
Above we used ``admin.autodiscover()`` to automatically load the
``INSTALLED_APPS`` admin.py modules.
In this example, we register the ``AdminSite`` instance
``myproject.admin.admin_site`` at the URL ``/myadmin/`` ::
# urls.py
from django.conf.urls.defaults import *
from myproject.admin import admin_site
urlpatterns = patterns('',
('^myadmin/(.*)', admin_site.root),
)
There is really no need to use autodiscover when using your own ``AdminSite``
instance since you will likely be importing all the per-app admin.py modules
in your ``myproject.admin`` module.
Note that the regular expression in the URLpattern *must* group everything in
the URL that comes after the URL root -- hence the ``(.*)`` in these examples.
Multiple admin sites in the same URLconf
----------------------------------------
It's easy to create multiple instances of the admin site on the same
Django-powered Web site. Just create multiple instances of ``AdminSite`` and
root each one at a different URL.
In this example, the URLs ``/basic-admin/`` and ``/advanced-admin/`` feature
separate versions of the admin site -- using the ``AdminSite`` instances
``myproject.admin.basic_site`` and ``myproject.admin.advanced_site``,
respectively::
# urls.py
from django.conf.urls.defaults import *
from myproject.admin import basic_site, advanced_site
urlpatterns = patterns('',
('^basic-admin/(.*)', basic_site.root),
('^advanced-admin/(.*)', advanced_site.root),
)

View File

@ -516,8 +516,8 @@ It's your responsibility to provide the login form in a template called
``registration/login.html`` by default. This template gets passed three
template context variables:
* ``form``: A ``FormWrapper`` object representing the login form. See the
`forms documentation`_ for more on ``FormWrapper`` objects.
* ``form``: A ``Form`` object representing the login form. See the
`newforms documentation`_ for more on ``Form`` objects.
* ``next``: The URL to redirect to after successful login. This may contain
a query string, too.
* ``site_name``: The name of the current ``Site``, according to the
@ -541,14 +541,14 @@ block::
{% block content %}
{% if form.has_errors %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action=".">
<table>
<tr><td><label for="id_username">Username:</label></td><td>{{ form.username }}</td></tr>
<tr><td><label for="id_password">Password:</label></td><td>{{ form.password }}</td></tr>
<tr><td>{{ form.username.label_tag }}</td><td>{{ form.username }}</td></tr>
<tr><td>{{ form.password.label_tag }}</td><td>{{ form.password }}</td></tr>
</table>
<input type="submit" value="login" />
@ -557,7 +557,7 @@ block::
{% endblock %}
.. _forms documentation: ../forms/
.. _newforms documentation: ../newforms/
.. _site framework docs: ../sites/
Other built-in views
@ -677,29 +677,29 @@ successful login.
* ``login_url``: The URL of the login page to redirect to. This
will default to ``settings.LOGIN_URL`` if not supplied.
Built-in manipulators
---------------------
Built-in forms
--------------
**New in Django development version.**
If you don't want to use the built-in views, but want the convenience
of not having to write manipulators for this functionality, the
authentication system provides several built-in manipulators:
of not having to write forms for this functionality, the authentication
system provides several built-in forms:
* ``django.contrib.auth.forms.AdminPasswordChangeForm``: A
manipulator used in the admin interface to change a user's
password.
* ``django.contrib.auth.forms.AdminPasswordChangeForm``: A form used in
the admin interface to change a user's password.
* ``django.contrib.auth.forms.AuthenticationForm``: A manipulator
for logging a user in.
* ``django.contrib.auth.forms.AuthenticationForm``: A form for logging a
user in.
* ``django.contrib.auth.forms.PasswordChangeForm``: A manipulator
for allowing a user to change their password.
* ``django.contrib.auth.forms.PasswordChangeForm``: A form for allowing a
user to change their password.
* ``django.contrib.auth.forms.PasswordResetForm``: A manipulator
for resetting a user's password and emailing the new password to
them.
* ``django.contrib.auth.forms.PasswordResetForm``: A form for resetting a
user's password and emailing the new password to them.
* ``django.contrib.auth.forms.UserCreationForm``: A manipulator
for creating a new user.
* ``django.contrib.auth.forms.UserCreationForm``: A form for creating a
new user.
Limiting access to logged-in users that pass a test
---------------------------------------------------

View File

@ -204,7 +204,6 @@ order:
* ``unique_for_year``
* ``validator_list``
* ``choices``
* ``radio_admin``
* ``help_text``
* ``db_column``
* ``db_tablespace``: Currently only used with the Oracle backend and only

View File

@ -20,10 +20,10 @@ For example, here's how you can create a form with a field representing a
French telephone number::
from django import newforms as forms
from django.contrib.localflavor.fr.forms import FRPhoneNumberField
from django.contrib.localflavor import fr
class MyForm(forms.Form):
my_french_phone_no = FRPhoneNumberField()
my_french_phone_no = fr.forms.FRPhoneNumberField()
Supported countries
===================

View File

@ -12,8 +12,6 @@ The basics:
* Each attribute of the model represents a database field.
* Model metadata (non-field information) goes in an inner class named
``Meta``.
* Metadata used for Django's admin site goes into an inner class named
``Admin``.
* With all of this, Django gives you an automatically-generated
database-access API, which is explained in the `Database API reference`_.
@ -425,18 +423,6 @@ not specified, Django will use a default length of 50.
Implies ``db_index=True``.
Accepts an extra option, ``prepopulate_from``, which is a list of fields
from which to auto-populate the slug, via JavaScript, in the object's admin
form::
models.SlugField(prepopulate_from=("pre_name", "name"))
``prepopulate_from`` doesn't accept DateTimeFields, ForeignKeys nor
ManyToManyFields.
The admin represents ``SlugField`` as an ``<input type="text">`` (a
single-line input).
``SmallIntegerField``
~~~~~~~~~~~~~~~~~~~~~
@ -665,16 +651,6 @@ unless you want to override the default primary-key behavior.
``primary_key=True`` implies ``null=False`` and ``unique=True``. Only
one primary key is allowed on an object.
``radio_admin``
~~~~~~~~~~~~~~~
By default, Django's admin uses a select-box interface (<select>) for
fields that are ``ForeignKey`` or have ``choices`` set. If ``radio_admin``
is set to ``True``, Django will use a radio-button interface instead.
Don't use this for a field unless it's a ``ForeignKey`` or has ``choices``
set.
``unique``
~~~~~~~~~~
@ -822,14 +798,6 @@ relationship should work. All are optional:
======================= ============================================================
Argument Description
======================= ============================================================
``edit_inline`` If ``True``, this related object is edited
"inline" on the related object's page. This means
that the object will not have its own admin
interface. Use either ``models.TABULAR`` or ``models.STACKED``,
which, respectively, designate whether the inline-editable
objects are displayed as a table or as a "stack" of
fieldsets.
``limit_choices_to`` A dictionary of lookup arguments and values (see
the `Database API reference`_) that limit the
available admin choices for this object. Use this
@ -848,39 +816,6 @@ relationship should work. All are optional:
Not compatible with ``edit_inline``.
``max_num_in_admin`` For inline-edited objects, this is the maximum
number of related objects to display in the admin.
Thus, if a pizza could only have up to 10
toppings, ``max_num_in_admin=10`` would ensure
that a user never enters more than 10 toppings.
Note that this doesn't ensure more than 10 related
toppings ever get created. It simply controls the
admin interface; it doesn't enforce things at the
Python API level or database level.
``min_num_in_admin`` The minimum number of related objects displayed in
the admin. Normally, at the creation stage,
``num_in_admin`` inline objects are shown, and at
the edit stage ``num_extra_on_change`` blank
objects are shown in addition to all pre-existing
related objects. However, no fewer than
``min_num_in_admin`` related objects will ever be
displayed.
``num_extra_on_change`` The number of extra blank related-object fields to
show at the change stage.
``num_in_admin`` The default number of inline objects to display
on the object page at the add stage.
``raw_id_admin`` Only display a field for the integer to be entered
instead of a drop-down menu. This is useful when
related to an object type that will have too many
rows to make a select box practical.
Not used with ``edit_inline``.
``related_name`` The name to use for the relation from the related
object back to this one. See the
`related objects documentation`_ for a full
@ -957,13 +892,6 @@ the relationship should work. All are optional:
======================= ============================================================
``related_name`` See the description under ``ForeignKey`` above.
``filter_interface`` Use a nifty unobtrusive Javascript "filter" interface
instead of the usability-challenged ``<select multiple>``
in the admin form for this object. The value should be
``models.HORIZONTAL`` or ``models.VERTICAL`` (i.e.
should the interface be stacked horizontally or
vertically).
``limit_choices_to`` See the description under ``ForeignKey`` above.
``symmetrical`` Only used in the definition of ManyToManyFields on self.
@ -1255,412 +1183,6 @@ attribute is the primary key field for the model. You can read and set this
value, just as you would for any other attribute, and it will update the
correct field in the model.
Admin options
=============
If you want your model to be visible to Django's admin site, give your model an
inner ``"class Admin"``, like so::
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class Admin:
# Admin options go here
pass
The ``Admin`` class tells Django how to display the model in the admin site.
Here's a list of all possible ``Admin`` options. None of these options are
required. To use an admin interface without specifying any options, use
``pass``, like so::
class Admin:
pass
Adding ``class Admin`` to a model is completely optional.
``date_hierarchy``
------------------
Set ``date_hierarchy`` to the name of a ``DateField`` or ``DateTimeField`` in
your model, and the change list page will include a date-based drilldown
navigation by that field.
Example::
date_hierarchy = 'pub_date'
``fields``
----------
Set ``fields`` to control the layout of admin "add" and "change" pages.
``fields`` is a list of two-tuples, in which each two-tuple represents a
``<fieldset>`` on the admin form page. (A ``<fieldset>`` is a "section" of the
form.)
The two-tuples are in the format ``(name, field_options)``, where ``name`` is a
string representing the title of the fieldset and ``field_options`` is a
dictionary of information about the fieldset, including a list of fields to be
displayed in it.
A full example, taken from the ``django.contrib.flatpages.FlatPage`` model::
class Admin:
fields = (
(None, {
'fields': ('url', 'title', 'content', 'sites')
}),
('Advanced options', {
'classes': 'collapse',
'fields' : ('enable_comments', 'registration_required', 'template_name')
}),
)
This results in an admin page that looks like:
.. image:: http://media.djangoproject.com/img/doc/flatfiles_admin.png
If ``fields`` isn't given, Django will default to displaying each field that
isn't an ``AutoField`` and has ``editable=True``, in a single fieldset, in
the same order as the fields are defined in the model.
The ``field_options`` dictionary can have the following keys:
``fields``
~~~~~~~~~~
A tuple of field names to display in this fieldset. This key is required.
Example::
{
'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
}
To display multiple fields on the same line, wrap those fields in their own
tuple. In this example, the ``first_name`` and ``last_name`` fields will
display on the same line::
{
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
}
``classes``
~~~~~~~~~~~
A string containing extra CSS classes to apply to the fieldset.
Example::
{
'classes': 'wide',
}
Apply multiple classes by separating them with spaces. Example::
{
'classes': 'wide extrapretty',
}
Two useful classes defined by the default admin-site stylesheet are
``collapse`` and ``wide``. Fieldsets with the ``collapse`` style will be
initially collapsed in the admin and replaced with a small "click to expand"
link. Fieldsets with the ``wide`` style will be given extra horizontal space.
``description``
~~~~~~~~~~~~~~~
A string of optional extra text to be displayed at the top of each fieldset,
under the heading of the fieldset. It's used verbatim, so you can use any HTML
and you must escape any special HTML characters (such as ampersands) yourself.
``js``
------
A list of strings representing URLs of JavaScript files to link into the admin
screen via ``<script src="">`` tags. This can be used to tweak a given type of
admin page in JavaScript or to provide "quick links" to fill in default values
for certain fields.
If you use relative URLs -- URLs that don't start with ``http://`` or ``/`` --
then the admin site will automatically prefix these links with
``settings.ADMIN_MEDIA_PREFIX``.
``list_display``
----------------
Set ``list_display`` to control which fields are displayed on the change list
page of the admin.
Example::
list_display = ('first_name', 'last_name')
If you don't set ``list_display``, the admin site will display a single column
that displays the ``__str__()`` representation of each object.
A few special cases to note about ``list_display``:
* If the field is a ``ForeignKey``, Django will display the
``__unicode__()`` of the related object.
* ``ManyToManyField`` fields aren't supported, because that would entail
executing a separate SQL statement for each row in the table. If you
want to do this nonetheless, give your model a custom method, and add
that method's name to ``list_display``. (See below for more on custom
methods in ``list_display``.)
* If the field is a ``BooleanField`` or ``NullBooleanField``, Django will
display a pretty "on" or "off" icon instead of ``True`` or ``False``.
* If the string given is a method of the model, Django will call it and
display the output. This method should have a ``short_description``
function attribute, for use as the header for the field.
Here's a full example model::
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
class Admin:
list_display = ('name', 'decade_born_in')
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
* If the string given is a method of the model, Django will HTML-escape the
output by default. If you'd rather not escape the output of the method,
give the method an ``allow_tags`` attribute whose value is ``True``.
Here's a full example model::
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
color_code = models.CharField(max_length=6)
class Admin:
list_display = ('first_name', 'last_name', 'colored_name')
def colored_name(self):
return '<span style="color: #%s;">%s %s</span>' % (self.color_code, self.first_name, self.last_name)
colored_name.allow_tags = True
* If the string given is a method of the model that returns True or False
Django will display a pretty "on" or "off" icon if you give the method a
``boolean`` attribute whose value is ``True``.
Here's a full example model::
class Person(models.Model):
first_name = models.CharField(max_length=50)
birthday = models.DateField()
class Admin:
list_display = ('name', 'born_in_fifties')
def born_in_fifties(self):
return self.birthday.strftime('%Y')[:3] == 5
born_in_fifties.boolean = True
* The ``__str__()`` and ``__unicode__()`` methods are just as valid in
``list_display`` as any other model method, so it's perfectly OK to do
this::
list_display = ('__unicode__', 'some_other_field')
* Usually, elements of ``list_display`` that aren't actual database fields
can't be used in sorting (because Django does all the sorting at the
database level).
However, if an element of ``list_display`` represents a certain database
field, you can indicate this fact by setting the ``admin_order_field``
attribute of the item.
For example::
class Person(models.Model):
first_name = models.CharField(max_length=50)
color_code = models.CharField(max_length=6)
class Admin:
list_display = ('first_name', 'colored_first_name')
def colored_first_name(self):
return '<span style="color: #%s;">%s</span>' % (self.color_code, self.first_name)
colored_first_name.allow_tags = True
colored_first_name.admin_order_field = 'first_name'
The above will tell Django to order by the ``first_name`` field when
trying to sort by ``colored_first_name`` in the admin.
``list_display_links``
----------------------
Set ``list_display_links`` to control which fields in ``list_display`` should
be linked to the "change" page for an object.
By default, the change list page will link the first column -- the first field
specified in ``list_display`` -- to the change page for each item. But
``list_display_links`` lets you change which columns are linked. Set
``list_display_links`` to a list or tuple of field names (in the same format as
``list_display``) to link.
``list_display_links`` can specify one or many field names. As long as the
field names appear in ``list_display``, Django doesn't care how many (or how
few) fields are linked. The only requirement is: If you want to use
``list_display_links``, you must define ``list_display``.
In this example, the ``first_name`` and ``last_name`` fields will be linked on
the change list page::
class Admin:
list_display = ('first_name', 'last_name', 'birthday')
list_display_links = ('first_name', 'last_name')
Finally, note that in order to use ``list_display_links``, you must define
``list_display``, too.
``list_filter``
---------------
Set ``list_filter`` to activate filters in the right sidebar of the change list
page of the admin. This should be a list of field names, and each specified
field should be either a ``BooleanField``, ``CharField``, ``DateField``,
``DateTimeField``, ``IntegerField`` or ``ForeignKey``.
This example, taken from the ``django.contrib.auth.models.User`` model, shows
how both ``list_display`` and ``list_filter`` work::
class Admin:
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
The above code results in an admin change list page that looks like this:
.. image:: http://media.djangoproject.com/img/doc/users_changelist.png
(This example also has ``search_fields`` defined. See below.)
``list_per_page``
-----------------
Set ``list_per_page`` to control how many items appear on each paginated admin
change list page. By default, this is set to ``100``.
``list_select_related``
-----------------------
Set ``list_select_related`` to tell Django to use ``select_related()`` in
retrieving the list of objects on the admin change list page. This can save you
a bunch of database queries.
The value should be either ``True`` or ``False``. Default is ``False``.
Note that Django will use ``select_related()``, regardless of this setting,
if one of the ``list_display`` fields is a ``ForeignKey``.
For more on ``select_related()``, see `the select_related() docs`_.
.. _the select_related() docs: ../db-api/#select-related
``ordering``
------------
Set ``ordering`` to specify how objects on the admin change list page should be
ordered. This should be a list or tuple in the same format as a model's
``ordering`` parameter.
If this isn't provided, the Django admin will use the model's default ordering.
``save_as``
-----------
Set ``save_as`` to enable a "save as" feature on admin change forms.
Normally, objects have three save options: "Save", "Save and continue editing"
and "Save and add another". If ``save_as`` is ``True``, "Save and add another"
will be replaced by a "Save as" button.
"Save as" means the object will be saved as a new object (with a new ID),
rather than the old object.
By default, ``save_as`` is set to ``False``.
``save_on_top``
---------------
Set ``save_on_top`` to add save buttons across the top of your admin change
forms.
Normally, the save buttons appear only at the bottom of the forms. If you set
``save_on_top``, the buttons will appear both on the top and the bottom.
By default, ``save_on_top`` is set to ``False``.
``search_fields``
-----------------
Set ``search_fields`` to enable a search box on the admin change list page.
This should be set to a list of field names that will be searched whenever
somebody submits a search query in that text box.
These fields should be some kind of text field, such as ``CharField`` or
``TextField``. You can also perform a related lookup on a ``ForeignKey`` with
the lookup API "follow" notation::
search_fields = ['foreign_key__related_fieldname']
When somebody does a search in the admin search box, Django splits the search
query into words and returns all objects that contain each of the words, case
insensitive, where each word must be in at least one of ``search_fields``. For
example, if ``search_fields`` is set to ``['first_name', 'last_name']`` and a
user searches for ``john lennon``, Django will do the equivalent of this SQL
``WHERE`` clause::
WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%')
AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')
For faster and/or more restrictive searches, prefix the field name
with an operator:
``^``
Matches the beginning of the field. For example, if ``search_fields`` is
set to ``['^first_name', '^last_name']`` and a user searches for
``john lennon``, Django will do the equivalent of this SQL ``WHERE``
clause::
WHERE (first_name ILIKE 'john%' OR last_name ILIKE 'john%')
AND (first_name ILIKE 'lennon%' OR last_name ILIKE 'lennon%')
This query is more efficient than the normal ``'%john%'`` query, because
the database only needs to check the beginning of a column's data, rather
than seeking through the entire column's data. Plus, if the column has an
index on it, some databases may be able to use the index for this query,
even though it's a ``LIKE`` query.
``=``
Matches exactly, case-insensitive. For example, if
``search_fields`` is set to ``['=first_name', '=last_name']`` and
a user searches for ``john lennon``, Django will do the equivalent
of this SQL ``WHERE`` clause::
WHERE (first_name ILIKE 'john' OR last_name ILIKE 'john')
AND (first_name ILIKE 'lennon' OR last_name ILIKE 'lennon')
Note that the query input is split by spaces, so, following this example,
it's currently not possible to search for all records in which
``first_name`` is exactly ``'john winston'`` (containing a space).
``@``
Performs a full-text match. This is like the default search method but uses
an index. Currently this is only available for MySQL.
Managers
========

View File

@ -376,3 +376,125 @@ There are a couple of things to note, however.
Chances are these notes won't affect you unless you're trying to do something
tricky with subclassing.
Model Formsets
==============
Similar to regular formsets there are a couple enhanced formset classes that
provide all the right things to work with your models. Lets reuse the
``Author`` model from above::
>>> from django.newforms.models import modelformset_factory
>>> AuthorFormSet = modelformset_factory(Author)
This will create a formset that is capable of working with the data associated
to the ``Author`` model. It works just like a regular formset::
>>> formset = AuthorFormSet()
>>> print formset
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected="selected">---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select></td></tr>
<tr><th><label for="id_form-0-birth_date">Birth date:</label></th><td><input type="text" name="form-0-birth_date" id="id_form-0-birth_date" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>
.. note::
One thing to note is that ``modelformset_factory`` uses ``formset_factory``
and by default uses ``can_delete=True``.
Changing the queryset
---------------------
By default when you create a formset from a model the queryset will be all
objects in the model. This is best shown as ``Author.objects.all()``. This is
configurable::
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
Alternatively, you can use a subclassing based approach::
from django.newforms.models import BaseModelFormSet
class BaseAuthorFormSet(BaseModelFormSet):
def get_queryset(self):
return super(BaseAuthorFormSet, self).get_queryset().filter(name__startswith='O')
Then your ``BaseAuthorFormSet`` would be passed into the factory function to
be used as a base::
>>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)
Saving objects in the formset
-----------------------------
Similar to a ``ModelForm`` you can save the data into the model. This is done
with the ``save()`` method on the formset::
# create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# assuming all is valid, save the data
>>> instances = formset.save()
The ``save()`` method will return the instances that have been saved to the
database. If an instance did not change in the bound data it will not be
saved to the database and not found in ``instances`` in the above example.
You can optionally pass in ``commit=False`` to ``save()`` to only return the
model instances without any database interaction::
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
This gives you the ability to attach data to the instances before saving them
to the database. If your formset contains a ``ManyToManyField`` you will also
need to make a call to ``formset.save_m2m()`` to ensure the many-to-many
relationships are saved properly.
Limiting the number of objects editable
---------------------------------------
Similar to regular formsets you can use the ``max_num`` parameter to
``modelformset_factory`` to limit the number of forms displayed. With
model formsets this will properly limit the query to only select the maximum
number of objects needed::
>>> Author.objects.order_by('name')
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
>>> AuthorFormSet = modelformset_factory(Author, max_num=2, extra=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
If the value of ``max_num`` is less than the total objects returned it will
fill the rest with extra forms::
>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>
Using ``inlineformset_factory``
-------------------------------
The ``inlineformset_factory`` is a helper to a common usage pattern of working
with related objects through a foreign key. Suppose you have two models
``Author`` and ``Book``. You want to create a formset that works with the
books of a specific author. Here is how you could accomplish this::
>>> from django.newforms.models import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book)
>>> author = Author.objects.get(name=u'Orson Scott Card')
>>> formset = BookFormSet(instance=author)

View File

@ -76,6 +76,9 @@ The library deals with these concepts:
* **Form** -- A collection of fields that knows how to validate itself and
display itself as HTML.
* **Media** -- A definition of the CSS and JavaScript resources that are
required to render a form.
The library is decoupled from the other Django components, such as the database
layer, views and templates. It relies only on Django settings, a couple of
``django.utils`` helper functions and Django's internationalization hooks (but
@ -1864,6 +1867,643 @@ They've been deprecated, but you can still `view the documentation`_.
.. _ModelForms documentation: ../modelforms/
.. _view the documentation: ../form_for_model/
Media
=====
Rendering an attractive and easy-to-use web form requires more than just
HTML - it also requires CSS stylesheets, and if you want to use fancy
"Web2.0" widgets, you may also need to include some JavaScript on each
page. The exact combination of CSS and JavaScript that is required for
any given page will depend upon the widgets that are in use on that page.
This is where Django media definitions come in. Django allows you to
associate different media files with the forms and widgets that require
that media. For example, if you want to use a calendar to render DateFields,
you can define a custom Calendar widget. This widget can then be associated
with the CSS and Javascript that is required to render the calendar. When
the Calendar widget is used on a form, Django is able to identify the CSS and
JavaScript files that are required, and provide the list of file names
in a form suitable for easy inclusion on your web page.
.. admonition:: Media and Django Admin
The Django Admin application defines a number of customized widgets
for calendars, filtered selections, and so on. These widgets define
media requirements, and the Django Admin uses the custom widgets
in place of the Django defaults. The Admin templates will only include
those media files that are required to render the widgets on any
given page.
If you like the widgets that the Django Admin application uses,
feel free to use them in your own application! They're all stored
in ``django.contrib.admin.widgets``.
.. admonition:: Which JavaScript toolkit?
Many JavaScript toolkits exist, and many of them include widgets (such
as calendar widgets) that can be used to enhance your application.
Django has deliberately avoided blessing any one JavaScript toolkit.
Each toolkit has its own relative strengths and weaknesses - use
whichever toolkit suits your requirements. Django is able to integrate
with any JavaScript toolkit.
Media as a static definition
----------------------------
The easiest way to define media is as a static definition. Using this method,
the media declaration is an inner class. The properties of the inner class
define the media requirements.
Here's a simple example::
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('pretty.css',)
}
js = ('animations.js', 'actions.js')
This code defines a ``CalendarWidget``, which will be based on ``TextInput``.
Every time the CalendarWidget is used on a form, that form will be directed
to include the CSS file ``pretty.css``, and the JavaScript files
``animations.js`` and ``actions.js``.
This static media definition is converted at runtime into a widget property
named ``media``. The media for a CalendarWidget instance can be retrieved
through this property::
>>> w = CalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
Here's a list of all possible ``Media`` options. There are no required options.
``css``
~~~~~~~
A dictionary describing the CSS files required for various forms of output
media.
The values in the dictionary should be a tuple/list of file names. See
`the section on media paths`_ for details of how to specify paths to media
files.
.. _the section on media paths: `Paths in media definitions`_
The keys in the dictionary are the output media types. These are the same
types accepted by CSS files in media declarations: 'all', 'aural', 'braille',
'embossed', 'handheld', 'print', 'projection', 'screen', 'tty' and 'tv'. If
you need to have different stylesheets for different media types, provide
a list of CSS files for each output medium. The following example would
provide two CSS options -- one for the screen, and one for print::
class Media:
css = {
'screen': ('pretty.css',),
'print': ('newspaper.css',)
}
If a group of CSS files are appropriate for multiple output media types,
the dictionary key can be a comma separated list of output media types.
In the following example, TV's and projectors will have the same media
requirements::
class Media:
css = {
'screen': ('pretty.css',),
'tv,projector': ('lo_res.css',),
'print': ('newspaper.css',)
}
If this last CSS definition were to be rendered, it would become the following HTML::
<link href="http://media.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" />
<link href="http://media.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
<link href="http://media.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />
``js``
~~~~~~
A tuple describing the required javascript files. See
`the section on media paths`_ for details of how to specify paths to media
files.
``extend``
~~~~~~~~~~
A boolean defining inheritance behavior for media declarations.
By default, any object using a static media definition will inherit all the
media associated with the parent widget. This occurs regardless of how the
parent defines its media requirements. For example, if we were to extend our
basic Calendar widget from the example above::
class FancyCalendarWidget(CalendarWidget):
class Media:
css = {
'all': ('fancy.css',)
}
js = ('whizbang.js',)
>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
The FancyCalendar widget inherits all the media from it's parent widget. If
you don't want media to be inherited in this way, add an ``extend=False``
declaration to the media declaration::
class FancyCalendar(Calendar):
class Media:
extend = False
css = {
'all': ('fancy.css',)
}
js = ('whizbang.js',)
>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
If you require even more control over media inheritance, define your media
using a `dynamic property`_. Dynamic properties give you complete control over
which media files are inherited, and which are not.
.. _dynamic property: `Media as a dynamic property`_
Media as a dynamic property
---------------------------
If you need to perform some more sophisticated manipulation of media
requirements, you can define the media property directly. This is done
by defining a model property that returns an instance of ``forms.Media``.
The constructor for ``forms.Media`` accepts ``css`` and ``js`` keyword
arguments in the same format as that used in a static media definition.
For example, the static media definition for our Calendar Widget could
also be defined in a dynamic fashion::
class CalendarWidget(forms.TextInput):
def _media(self):
return forms.Media(css={'all': ('pretty.css',)},
js=('animations.js', 'actions.js'))
media = property(_media)
See the section on `Media objects`_ for more details on how to construct
return values for dynamic media properties.
Paths in media definitions
--------------------------
Paths used to specify media can be either relative or absolute. If a path
starts with '/', 'http://' or 'https://', it will be interpreted as an absolute
path, and left as-is. All other paths will be prepended with the value of
``settings.MEDIA_URL``. For example, if the MEDIA_URL for your site was
``http://media.example.com/``::
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('/css/pretty.css',),
}
js = ('animations.js', 'http://othersite.com/actions.js')
>>> w = CalendarWidget()
>>> print w.media
<link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>
Media objects
-------------
When you interrogate the media attribute of a widget or form, the value that
is returned is a ``forms.Media`` object. As we have already seen, the string
representation of a Media object is the HTML required to include media
in the ``<head>`` block of your HTML page.
However, Media objects have some other interesting properties.
Media subsets
~~~~~~~~~~~~~
If you only want media of a particular type, you can use the subscript operator
to filter out a medium of interest. For example::
>>> w = CalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
>>> print w.media['css']
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
When you use the subscript operator, the value that is returned is a new
Media object -- but one that only contains the media of interest.
Combining media objects
~~~~~~~~~~~~~~~~~~~~~~~
Media objects can also be added together. When two media objects are added,
the resulting Media object contains the union of the media from both files::
class CalendarWidget(forms.TextInput):
class Media:
css = {
'all': ('pretty.css',)
}
js = ('animations.js', 'actions.js')
class OtherWidget(forms.TextInput):
class Media:
js = ('whizbang.js',)
>>> w1 = CalendarWidget()
>>> w2 = OtherWidget()
>>> print w1+w2
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
Media on Forms
--------------
Widgets aren't the only objects that can have media definitions -- forms
can also define media. The rules for media definitions on forms are the
same as the rules for widgets: declarations can be static or dynamic;
path and inheritance rules for those declarations are exactly the same.
Regardless of whether you define a media declaration, *all* Form objects
have a media property. The default value for this property is the result
of adding the media definitions for all widgets that are part of the form::
class ContactForm(forms.Form):
date = DateField(widget=CalendarWidget)
name = CharField(max_length=40, widget=OtherWidget)
>>> f = ContactForm()
>>> f.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
If you want to associate additional media with a form -- for example, CSS for form
layout -- simply add a media declaration to the form::
class ContactForm(forms.Form):
date = DateField(widget=CalendarWidget)
name = CharField(max_length=40, widget=OtherWidget)
class Media:
css = {
'all': ('layout.css',)
}
>>> f = ContactForm()
>>> f.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
Formsets
========
A formset is a layer of abstraction to working with multiple forms on the same
page. It can be best compared to a data grid. Let's say you have the following
form::
>>> from django import newforms as forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
You might want to allow the user to create several articles at once. To create
a formset of ``ArticleForm``s you would do::
>>> from django.newforms.formsets import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
You now have created a formset named ``ArticleFormSet``. The formset gives you
the ability to iterate over the forms in the formset and display them as you
would with a regular form::
>>> formset = ArticleFormSet()
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
As you can see it only displayed one form. This is because by default the
``formset_factory`` defines one extra form. This can be controlled with the
``extra`` parameter::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
Using initial data with a formset
---------------------------------
Initial data is what drives the main usability of a formset. As shown above
you can define the number of extra forms. What this means is that you are
telling the formset how many additional forms to show in addition to the
number of forms it generates from the initial data. Lets take a look at an
example::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(initial=[
... {'title': u'Django is now open source',
... 'pub_date': datetime.date.today()},
... ])
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
There are now a total of three forms showing above. One for the initial data
that was passed in and two extra forms. Also note that we are passing in a
list of dictionaries as the initial data.
Limiting the maximum number of forms
------------------------------------
The ``max_num`` parameter to ``formset_factory`` gives you the ability to
force the maximum number of forms the formset will display::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormset()
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
The default value of ``max_num`` is ``0`` which is the same as saying put no
limit on the number forms displayed.
Formset validation
------------------
Validation with a formset is about identical to a regular ``Form``. There is
an ``is_valid`` method on the formset to provide a convenient way to validate
each form in the formset::
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> formset = ArticleFormSet({})
>>> formset.is_valid()
True
We passed in no data to the formset which is resulting in a valid form. The
formset is smart enough to ignore extra forms that were not changed. If we
attempt to provide an article, but fail to do so::
>>> data = {
... 'form-TOTAL_FORMS': u'1',
... 'form-INITIAL_FORMS': u'1',
... 'form-0-title': u'Test',
... 'form-0-pub_date': u'',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{'pub_date': [u'This field is required.']}]
As we can see the formset properly performed validation and gave us the
expected errors.
Understanding the ManagementForm
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You may have noticed the additional data that was required in the formset's
data above. This data is coming from the ``ManagementForm``. This form is
dealt with internally to the formset. If you don't use it, it will result in
an exception::
>>> data = {
... 'form-0-title': u'Test',
... 'form-0-pub_date': u'',
... }
>>> formset = ArticleFormSet(data)
Traceback (most recent call last):
...
django.newforms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
It is used to keep track of how many form instances are being displayed. If
you are adding new forms via javascript, you should increment the count fields
in this form as well.
Custom formset validation
~~~~~~~~~~~~~~~~~~~~~~~~~
A formset has a ``clean`` method similar to the one on a ``Form`` class. This
is where you define your own validation that deals at the formset level::
>>> from django.newforms.formsets import BaseFormSet
>>> class BaseArticleFormSet(BaseFormSet):
... def clean(self):
... raise forms.ValidationError, u'An error occured.'
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet({})
>>> formset.is_valid()
False
>>> formset.non_form_errors()
[u'An error occured.']
The formset ``clean`` method is called after all the ``Form.clean`` methods
have been called. The errors will be found using the ``non_form_errors()``
method on the formset.
Dealing with ordering and deletion of forms
-------------------------------------------
Common use cases with a formset is dealing with ordering and deletion of the
form instances. This has been dealt with for you. The ``formset_factory``
provides two optional parameters ``can_order`` and ``can_delete`` that will do
the extra work of adding the extra fields and providing simpler ways of
getting to that data.
``can_order``
~~~~~~~~~~~~~
Default: ``False``
Lets create a formset with the ability to order::
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="text" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="text" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="text" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>
This adds an additional field to each form. This new field is named ``ORDER``
and is an ``forms.IntegerField``. For the forms that came from the initial
data it automatically assigned them a numeric value. Lets look at what will
happen when the user changes these values::
>>> data = {
... 'form-TOTAL_FORMS': u'3',
... 'form-INITIAL_FORMS': u'2',
... 'form-0-title': u'Article #1',
... 'form-0-pub_date': u'2008-05-10',
... 'form-0-ORDER': u'2',
... 'form-1-title': u'Article #2',
... 'form-1-pub_date': u'2008-05-11',
... 'form-1-ORDER': u'1',
... 'form-2-title': u'Article #3',
... 'form-2-pub_date': u'2008-05-01',
... 'form-2-ORDER': u'0',
... }
>>> formset = ArticleFormSet(data, initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
... print form.cleaned_data
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'}
``can_delete``
~~~~~~~~~~~~~~
Default: ``False``
Lets create a formset with the ability to delete::
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset.forms:
.... print form.as_table()
<input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" />
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr>
Similar to ``can_order`` this adds a new field to each form named ``DELETE``
and is a ``forms.BooleanField``. When data comes through marking any of the
delete fields you can access them with ``deleted_forms``::
>>> data = {
... 'form-TOTAL_FORMS': u'3',
... 'form-INITIAL_FORMS': u'2',
... 'form-0-title': u'Article #1',
... 'form-0-pub_date': u'2008-05-10',
... 'form-0-DELETE': u'on',
... 'form-1-title': u'Article #2',
... 'form-1-pub_date': u'2008-05-11',
... 'form-1-DELETE': u'',
... 'form-2-title': u'',
... 'form-2-pub_date': u'',
... 'form-2-DELETE': u'',
... }
>>> formset = ArticleFormSet(data, initial=[
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
Adding additional fields to a formset
-------------------------------------
If you need to add additional fields to the formset this can be easily
accomplished. The formset base class provides an ``add_fields`` method. You
can simply override this method to add your own fields or even redefine the
default fields/attributes of the order and deletion fields::
>>> class BaseArticleFormSet(BaseFormSet):
... def add_fields(self, form, index):
... super(BaseArticleFormSet, self).add_fields(form, index)
... form.fields["my_field"] = forms.CharField()
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr>
Using a formsets in views and templates
---------------------------------------
Using a formset inside a view is as easy as using a regular ``Form`` class.
The only thing you will want to be aware of is making sure to use the
management form inside the template. Lets look at a sample view::
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
if request.method == 'POST':
formset = ArticleFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
else:
formset = ArticleFormSet()
return render_to_response('manage_articles.html', {'formset': formset})
The ``manage_articles.html`` template might look like this::
<form method="POST" action="">
{{ formset.management_form }}
<table>
{% for form in formset.forms %}
{{ form }}
{% endfor %}
</table>
</form>
However the above can be slightly shortcutted and let the formset itself deal
with the management form::
<form method="POST" action="">
<table>
{{ formset }}
</table>
</form>
The above ends up calling the ``as_table`` method on the formset class.
More coming soon
================

View File

@ -31,10 +31,10 @@ activate the admin site for your installation, do these three things:
* Add ``"django.contrib.admin"`` to your ``INSTALLED_APPS`` setting.
* Run ``python manage.py syncdb``. Since you have added a new application
to ``INSTALLED_APPS``, the database tables need to be updated.
* Edit your ``mysite/urls.py`` file and uncomment the line below
"Uncomment this for admin:". This file is a URLconf; we'll dig into
URLconfs in the next tutorial. For now, all you need to know is that it
maps URL roots to applications.
* Edit your ``mysite/urls.py`` file and uncomment the lines below the
"Uncomment this for admin:" comments. This file is a URLconf; we'll dig
into URLconfs in the next tutorial. For now, all you need to know is that
it maps URL roots to applications.
Start the development server
============================
@ -71,19 +71,13 @@ Make the poll app modifiable in the admin
But where's our poll app? It's not displayed on the admin index page.
Just one thing to do: We need to specify in the ``Poll`` model that ``Poll``
Just one thing to do: We need to tell the admin that ``Poll``
objects have an admin interface. Edit the ``mysite/polls/models.py`` file and
make the following change to add an inner ``Admin`` class::
add the following to the bottom of the file::
class Poll(models.Model):
# ...
class Admin:
pass
The ``class Admin`` will contain all the settings that control how this model
appears in the Django admin. All the settings are optional, however, so
creating an empty class means "give this object an admin interface using
all the default options."
from django.contrib import admin
admin.site.register(Poll)
Now reload the Django admin page to see your changes. Note that you don't have
to restart the development server -- the server will auto-reload your project,
@ -92,8 +86,8 @@ so any modifications code will be seen immediately in your browser.
Explore the free admin functionality
====================================
Now that ``Poll`` has the inner ``Admin`` class, Django knows that it should be
displayed on the admin index page:
Now that we've registered ``Poll``, Django knows that it should be displayed on
the admin index page:
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin03t.png
:alt: Django admin index page, now with polls displayed
@ -145,17 +139,26 @@ with the timestamp and username of the person who made the change:
Customize the admin form
========================
Take a few minutes to marvel at all the code you didn't have to write.
Take a few minutes to marvel at all the code you didn't have to write. When you
call ``admin.site.register(Poll)``, Django just lets you edit the object and
"guess" at how to display it within the admin. Often you'll want to control how
the admin looks and works. You'll do this by telling Django about the options
you want when you register the object.
Let's customize this a bit. We can reorder the fields by explicitly adding a
``fields`` parameter to ``Admin``::
Let's see how this works by reordering the fields on the edit form. Replace the
``admin.site.register(Poll)`` line with::
class Admin:
fields = (
(None, {'fields': ('pub_date', 'question')}),
)
class PollAdmin(admin.ModelAdmin):
fields = ['pub_date', 'question']
admin.site.register(Poll, PollAdmin)
That made the "Publication date" show up first instead of second:
You'll follow this pattern -- create a model admin object, then pass it as the
second argument to ``admin.site.register()`` -- any time you need to change the
admin options for an object.
This particular change above makes the "Publication date" come before the
"Question" field:
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin07.png
:alt: Fields have been reordered
@ -166,13 +169,15 @@ of fields, choosing an intuitive order is an important usability detail.
And speaking of forms with dozens of fields, you might want to split the form
up into fieldsets::
class Admin:
fields = (
(None, {'fields': ('question',)}),
('Date information', {'fields': ('pub_date',)}),
)
class PollAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question']}),
('Date information', {'fields': ['pub_date']}),
]
admin.site.register(Poll, PollAdmin)
The first element of each tuple in ``fields`` is the title of the fieldset.
The first element of each tuple in ``fieldsets`` is the title of the fieldset.
Here's what our form looks like now:
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin08t.png
@ -184,11 +189,11 @@ You can assign arbitrary HTML classes to each fieldset. Django provides a
This is useful when you have a long form that contains a number of fields that
aren't commonly used::
class Admin:
fields = (
(None, {'fields': ('question',)}),
('Date information', {'fields': ('pub_date',), 'classes': 'collapse'}),
)
class PollAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question']}),
('Date information', {'fields': ['pub_date'], 'classes': 'pub_date'}),
]
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin09.png
:alt: Fieldset is initially collapsed
@ -201,14 +206,10 @@ the admin page doesn't display choices.
Yet.
There are two ways to solve this problem. The first is to give the ``Choice``
model its own inner ``Admin`` class, just as we did with ``Poll``. Here's what
that would look like::
There are two ways to solve this problem. The first register ``Choice`` with the
admin just as we did with ``Poll``. That's easy::
class Choice(models.Model):
# ...
class Admin:
pass
admin.site.register(Choice)
Now "Choices" is an available option in the Django admin. The "Add choice" form
looks like this:
@ -220,33 +221,35 @@ In that form, the "Poll" field is a select box containing every poll in the
database. Django knows that a ``ForeignKey`` should be represented in the admin
as a ``<select>`` box. In our case, only one poll exists at this point.
Also note the "Add Another" link next to "Poll." Every object with a ForeignKey
relationship to another gets this for free. When you click "Add Another," you'll
get a popup window with the "Add poll" form. If you add a poll in that window
and click "Save," Django will save the poll to the database and dynamically add
it as the selected choice on the "Add choice" form you're looking at.
Also note the "Add Another" link next to "Poll." Every object with a
``ForeignKey`` relationship to another gets this for free. When you click "Add
Another," you'll get a popup window with the "Add poll" form. If you add a poll
in that window and click "Save," Django will save the poll to the database and
dynamically add it as the selected choice on the "Add choice" form you're
looking at.
But, really, this is an inefficient way of adding Choice objects to the system.
It'd be better if you could add a bunch of Choices directly when you create the
Poll object. Let's make that happen.
Remove the ``Admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)``
field like so::
Remove the ``register()`` call for the Choice model. Then, edit the ``Poll``
registration code to read::
poll = models.ForeignKey(Poll, edit_inline=models.STACKED, num_in_admin=3)
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 3
class PollAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question']}),
('Date information', {'fields': ['pub_date'], 'classes': 'pub_date'}),
]
inlines = [ChoiceInline]
admin.site.register(Poll, PollAdmin)
This tells Django: "Choice objects are edited on the Poll admin page. By
default, provide enough fields for 3 Choices."
Then change the other fields in ``Choice`` to give them ``core=True``::
choice = models.CharField(max_length=200, core=True)
votes = models.IntegerField(core=True)
This tells Django: "When you edit a Choice on the Poll admin page, the 'choice'
and 'votes' fields are required. The presence of at least one of them signifies
the addition of a new Choice object, and clearing both of them signifies the
deletion of that existing Choice object."
default, provide enough fields for 3 choices."
Load the "Add poll" page to see how that looks:
@ -255,19 +258,18 @@ Load the "Add poll" page to see how that looks:
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin11.png
It works like this: There are three slots for related Choices -- as specified
by ``num_in_admin`` -- but each time you come back to the "Change" page for an
already-created object, you get one extra slot. (This means there's no
hard-coded limit on how many related objects can be added.) If you wanted space
for three extra Choices each time you changed the poll, you'd use
``num_extra_on_change=3``.
by ``extra`` -- and each time you come back to the "Change" page for an
already-created object, you get another three extra slots.
One small problem, though. It takes a lot of screen space to display all the
fields for entering related Choice objects. For that reason, Django offers an
alternate way of displaying inline related objects::
tabular way of displaying inline related objects; you just need to change
the ``ChoiceInline`` declaration to read::
poll = models.ForeignKey(Poll, edit_inline=models.TABULAR, num_in_admin=3)
class ChoiceInline(admin.TabularInline):
#...
With that ``edit_inline=models.TABULAR`` (instead of ``models.STACKED``), the
With that ``TabularInline`` (instead of ``StackedInline``), the
related objects are displayed in a more compact, table-based format:
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin12.png
@ -285,21 +287,21 @@ Here's what it looks like at this point:
:alt: Polls change list page
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04.png
By default, Django displays the ``str()`` of each object. But sometimes it'd
be more helpful if we could display individual fields. To do that, use the
``list_display`` option, which is a tuple of field names to display, as columns,
on the change list page for the object::
By default, Django displays the ``str()`` of each object. But sometimes it'd be
more helpful if we could display individual fields. To do that, use the
``list_display`` admin option, which is a tuple of field names to display, as
columns, on the change list page for the object::
class Poll(models.Model):
class PollAdmin(admin.ModelAdmin):
# ...
class Admin:
# ...
list_display = ('question', 'pub_date')
list_display = ('question', 'pub_date')
Just for good measure, let's also include the ``was_published_today`` custom
method from Tutorial 1::
list_display = ('question', 'pub_date', 'was_published_today')
class PollAdmin(admin.ModelAdmin):
# ...
list_display = ('question', 'pub_date', 'was_published_today')
Now the poll change list page looks like this:
@ -318,9 +320,8 @@ method a ``short_description`` attribute::
return self.pub_date.date() == datetime.date.today()
was_published_today.short_description = 'Published today?'
Let's add another improvement to the Poll change list page: Filters. Add the
following line to ``Poll.Admin``::
following line to ``PollAdmin``::
list_filter = ['pub_date']

View File

@ -5,12 +5,11 @@ This example exists purely to point out errors in models.
"""
from django.db import models
model_errors = ""
class FieldErrors(models.Model):
charfield = models.CharField()
decimalfield = models.DecimalField()
filefield = models.FileField()
prepopulate = models.CharField(max_length=10, prepopulate_from='bad')
choices = models.CharField(max_length=10, choices='bad')
choices2 = models.CharField(max_length=10, choices=[(1,2,3),(1,2,3)])
index = models.CharField(max_length=10, db_index='bad')
@ -116,7 +115,6 @@ model_errors = """invalid_models.fielderrors: "charfield": CharFields require a
invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple.
invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list).
invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.

View File

@ -992,4 +992,22 @@ True
u'...test3.png'
>>> instance.delete()
# Media on a ModelForm ########################################################
# Similar to a regular Form class you can define custom media to be used on
# the ModelForm.
>>> class ModelFormWithMedia(ModelForm):
... class Media:
... js = ('/some/form/javascript',)
... css = {
... 'all': ('/some/form/css',)
... }
... class Meta:
... model = PhoneNumber
>>> f = ModelFormWithMedia()
>>> print f.media
<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="/some/form/javascript"></script>
"""}

View File

@ -0,0 +1,324 @@
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
def __unicode__(self):
return self.title
class AuthorMeeting(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
created = models.DateField(editable=False)
def __unicode__(self):
return self.name
__test__ = {'API_TESTS': """
>>> from datetime import date
>>> from django.newforms.models import modelformset_factory
>>> qs = Author.objects.all()
>>> AuthorFormSet = modelformset_factory(Author, extra=3)
>>> formset = AuthorFormSet(queryset=qs)
>>> for form in formset.forms:
... print form.as_p()
<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>
<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>
<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
>>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-name': 'Charles Baudelaire',
... 'form-1-name': 'Arthur Rimbaud',
... 'form-2-name': '',
... }
>>> formset = AuthorFormSet(data=data, queryset=qs)
>>> formset.is_valid()
True
>>> formset.save()
[<Author: Charles Baudelaire>, <Author: Arthur Rimbaud>]
>>> for author in Author.objects.order_by('name'):
... print author.name
Arthur Rimbaud
Charles Baudelaire
Gah! We forgot Paul Verlaine. Let's create a formset to edit the existing
authors with an extra form to add him. We *could* pass in a queryset to
restrict the Author objects we edit, but in this case we'll use it to display
them in alphabetical order by name.
>>> qs = Author.objects.order_by('name')
>>> AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=False)
>>> formset = AuthorFormSet(queryset=qs)
>>> for form in formset.forms:
... print form.as_p()
<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
>>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '2', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1',
... 'form-1-name': 'Charles Baudelaire',
... 'form-2-name': 'Paul Verlaine',
... }
>>> formset = AuthorFormSet(data=data, queryset=qs)
>>> formset.is_valid()
True
# Only changed or new objects are returned from formset.save()
>>> formset.save()
[<Author: Paul Verlaine>]
>>> for author in Author.objects.order_by('name'):
... print author.name
Arthur Rimbaud
Charles Baudelaire
Paul Verlaine
This probably shouldn't happen, but it will. If an add form was marked for
deltetion, make sure we don't save that form.
>>> qs = Author.objects.order_by('name')
>>> AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=True)
>>> formset = AuthorFormSet(queryset=qs)
>>> for form in formset.forms:
... print form.as_p()
<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>
<p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>
<p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" value="Paul Verlaine" maxlength="100" /></p>
<p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /><input type="hidden" name="form-2-id" value="3" id="id_form-2-id" /></p>
<p><label for="id_form-3-name">Name:</label> <input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /></p>
<p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>
>>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1',
... 'form-1-name': 'Charles Baudelaire',
... 'form-2-id': '3',
... 'form-2-name': 'Paul Verlaine',
... 'form-3-name': 'Walt Whitman',
... 'form-3-DELETE': 'on',
... }
>>> formset = AuthorFormSet(data=data, queryset=qs)
>>> formset.is_valid()
True
# No objects were changed or saved so nothing will come back.
>>> formset.save()
[]
>>> for author in Author.objects.order_by('name'):
... print author.name
Arthur Rimbaud
Charles Baudelaire
Paul Verlaine
Let's edit a record to ensure save only returns that one record.
>>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Walt Whitman',
... 'form-1-id': '1',
... 'form-1-name': 'Charles Baudelaire',
... 'form-2-id': '3',
... 'form-2-name': 'Paul Verlaine',
... 'form-3-name': '',
... 'form-3-DELETE': '',
... }
>>> formset = AuthorFormSet(data=data, queryset=qs)
>>> formset.is_valid()
True
# One record has changed.
>>> formset.save()
[<Author: Walt Whitman>]
Test the behavior of commit=False and save_m2m
>>> meeting = AuthorMeeting.objects.create(created=date.today())
>>> meeting.authors = Author.objects.all()
# create an Author instance to add to the meeting.
>>> new_author = Author.objects.create(name=u'John Steinbeck')
>>> AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, extra=1, can_delete=True)
>>> data = {
... 'form-TOTAL_FORMS': '2', # the number of forms rendered
... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '1',
... 'form-0-name': '2nd Tuesday of the Week Meeting',
... 'form-0-authors': [2, 1, 3, 4],
... 'form-1-name': '',
... 'form-1-authors': '',
... 'form-1-DELETE': '',
... }
>>> formset = AuthorMeetingFormSet(data=data, queryset=AuthorMeeting.objects.all())
>>> formset.is_valid()
True
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... instance.created = date.today()
... instance.save()
>>> formset.save_m2m()
>>> instances[0].authors.all()
[<Author: Charles Baudelaire>, <Author: Walt Whitman>, <Author: Paul Verlaine>, <Author: John Steinbeck>]
# delete the author we created to allow later tests to continue working.
>>> new_author.delete()
Test the behavior of max_num with model formsets. It should properly limit
the queryset to reduce the amount of objects being pulled in when not being
used.
>>> qs = Author.objects.order_by('name')
>>> AuthorFormSet = modelformset_factory(Author, max_num=2)
>>> formset = AuthorFormSet(queryset=qs)
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
>>> AuthorFormSet = modelformset_factory(Author, max_num=3)
>>> formset = AuthorFormSet(queryset=qs)
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}, {'id': 2, 'name': u'Walt Whitman'}]
# Inline Formsets ############################################################
We can also create a formset that is tied to a parent model. This is how the
admin system's edit inline functionality works.
>>> from django.newforms.models import inlineformset_factory
>>> AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3)
>>> author = Author.objects.get(name='Charles Baudelaire')
>>> formset = AuthorBooksFormSet(instance=author)
>>> for form in formset.forms:
... print form.as_p()
<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>
<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '0', # the number of forms with initial data
... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': '',
... 'book_set-2-title': '',
... }
>>> formset = AuthorBooksFormSet(data, instance=author)
>>> formset.is_valid()
True
>>> formset.save()
[<Book: Les Fleurs du Mal>]
>>> for book in author.book_set.all():
... print book.title
Les Fleurs du Mal
Now that we've added a book to Charles Baudelaire, let's try adding another
one. This time though, an edit form will be available for every existing
book.
>>> AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
>>> author = Author.objects.get(name='Charles Baudelaire')
>>> formset = AuthorBooksFormSet(instance=author)
>>> for form in formset.forms:
... print form.as_p()
<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>
<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': 'Le Spleen de Paris',
... 'book_set-2-title': '',
... }
>>> formset = AuthorBooksFormSet(data, instance=author)
>>> formset.is_valid()
True
>>> formset.save()
[<Book: Le Spleen de Paris>]
As you can see, 'Le Spleen de Paris' is now a book belonging to Charles Baudelaire.
>>> for book in author.book_set.order_by('title'):
... print book.title
Le Spleen de Paris
Les Fleurs du Mal
The save_as_new parameter lets you re-associate the data to a new instance.
This is used in the admin for save_as functionality.
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data
... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-id': '2',
... 'book_set-1-title': 'Le Spleen de Paris',
... 'book_set-2-title': '',
... }
>>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
>>> formset.is_valid()
True
>>> new_author = Author.objects.create(name='Charles Baudelaire')
>>> formset.instance = new_author
>>> [book for book in formset.save() if book.author.pk == new_author.pk]
[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
"""}

View File

@ -0,0 +1,46 @@
# coding: utf-8
from django.db import models
class Band(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField()
rank = models.IntegerField()
class Meta:
ordering = ('name',)
__test__ = {'API_TESTS': """
Let's make sure that ModelAdmin.queryset uses the ordering we define in
ModelAdmin rather that ordering defined in the model's inner Meta
class.
>>> from django.contrib.admin.options import ModelAdmin
>>> b1 = Band(name='Aerosmith', bio='', rank=3)
>>> b1.save()
>>> b2 = Band(name='Radiohead', bio='', rank=1)
>>> b2.save()
>>> b3 = Band(name='Van Halen', bio='', rank=2)
>>> b3.save()
The default ordering should be by name, as specified in the inner Meta class.
>>> ma = ModelAdmin(Band, None)
>>> [b.name for b in ma.queryset(None)]
[u'Aerosmith', u'Radiohead', u'Van Halen']
Let's use a custom ModelAdmin that changes the ordering, and make sure it
actually changes.
>>> class BandAdmin(ModelAdmin):
... ordering = ('rank',) # default ordering is ('name',)
...
>>> ma = BandAdmin(Band, None)
>>> [b.name for b in ma.queryset(None)]
[u'Radiohead', u'Van Halen', u'Aerosmith']
"""
}

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="100" model="auth.user">
<field type="CharField" name="username">super</field>
<field type="CharField" name="first_name">Super</field>
<field type="CharField" name="last_name">User</field>
<field type="CharField" name="email">super@example.com</field>
<field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
<field type="BooleanField" name="is_staff">True</field>
<field type="BooleanField" name="is_active">True</field>
<field type="BooleanField" name="is_superuser">True</field>
<field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
<field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
</object>
<object pk="101" model="auth.user">
<field type="CharField" name="username">adduser</field>
<field type="CharField" name="first_name">Add</field>
<field type="CharField" name="last_name">User</field>
<field type="CharField" name="email">auser@example.com</field>
<field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
<field type="BooleanField" name="is_staff">True</field>
<field type="BooleanField" name="is_active">True</field>
<field type="BooleanField" name="is_superuser">False</field>
<field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
<field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
</object>
<object pk="102" model="auth.user">
<field type="CharField" name="username">changeuser</field>
<field type="CharField" name="first_name">Change</field>
<field type="CharField" name="last_name">User</field>
<field type="CharField" name="email">cuser@example.com</field>
<field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
<field type="BooleanField" name="is_staff">True</field>
<field type="BooleanField" name="is_active">True</field>
<field type="BooleanField" name="is_superuser">False</field>
<field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
<field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
</object>
<object pk="103" model="auth.user">
<field type="CharField" name="username">deleteuser</field>
<field type="CharField" name="first_name">Delete</field>
<field type="CharField" name="last_name">User</field>
<field type="CharField" name="email">duser@example.com</field>
<field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
<field type="BooleanField" name="is_staff">True</field>
<field type="BooleanField" name="is_active">True</field>
<field type="BooleanField" name="is_superuser">False</field>
<field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
<field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
</object>
<object pk="104" model="auth.user">
<field type="CharField" name="username">joepublic</field>
<field type="CharField" name="first_name">Joe</field>
<field type="CharField" name="last_name">Public</field>
<field type="CharField" name="email">joepublic@example.com</field>
<field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
<field type="BooleanField" name="is_staff">False</field>
<field type="BooleanField" name="is_active">True</field>
<field type="BooleanField" name="is_superuser">False</field>
<field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
<field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
</object>
<object pk="1" model="admin_views.section">
<field type="CharField" name="name">Test section</field>
</object>
<object pk="1" model="admin_views.article">
<field type="TextField" name="content">&lt;p&gt;test content&lt;/p&gt;</field>
<field type="DateTimeField" name="date">2008-03-18 11:54:58</field>
<field to="admin_views.section" name="section" rel="ManyToOneRel">1</field>
</object>
</django-objects>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="1" model="admin_views.modelwithstringprimarykey">
<field type="CharField" name="id"><![CDATA[abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`]]></field>
</object>
</django-objects>

View File

@ -0,0 +1,61 @@
from django.db import models
from django.contrib import admin
class Section(models.Model):
"""
A simple section that links to articles, to test linking to related items
in admin views.
"""
name = models.CharField(max_length=100)
class Article(models.Model):
"""
A simple article to test admin views. Test backwards compatibility.
"""
content = models.TextField()
date = models.DateTimeField()
section = models.ForeignKey(Section)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('content', 'date')
list_filter = ('date',)
def changelist_view(self, request):
"Test that extra_context works"
return super(ArticleAdmin, self).changelist_view(
request, extra_context={
'extra_var': 'Hello!'
}
)
class CustomArticle(models.Model):
content = models.TextField()
date = models.DateTimeField()
class CustomArticleAdmin(admin.ModelAdmin):
"""
Tests various hooks for using custom templates and contexts.
"""
change_list_template = 'custom_admin/change_list.html'
change_form_template = 'custom_admin/change_form.html'
object_history_template = 'custom_admin/object_history.html'
delete_confirmation_template = 'custom_admin/delete_confirmation.html'
def changelist_view(self, request):
"Test that extra_context works"
return super(CustomArticleAdmin, self).changelist_view(
request, extra_context={
'extra_var': 'Hello!'
}
)
class ModelWithStringPrimaryKey(models.Model):
id = models.CharField(max_length=255, primary_key=True)
def __unicode__(self):
return self.id
admin.site.register(Article, ArticleAdmin)
admin.site.register(CustomArticle, CustomArticleAdmin)
admin.site.register(Section)
admin.site.register(ModelWithStringPrimaryKey)

Some files were not shown because too many files have changed in this diff Show More