Cleaned up and refactored `ModelAdmin.formfield_for_dbfield`:
* The new method uses an admin configuration option (`formfield_overrides`); this makes custom admin widgets especially easy. * Refactored what was left of `formfield_for_dbfield` into a handful of smaller methods so that it's easier to hook in and return custom fields where needed. * These `formfield_for_*` methods now pass around `request` so that you can easily modify fields based on request (as in #3987). Fixes #8306, #3987, #9148. Thanks to James Bennet for the original patch; Alex Gaynor and Brian Rosner also contributed. git-svn-id: http://code.djangoproject.com/svn/django/trunk@9760 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
d579e716fe
commit
f212b24b64
|
@ -13,6 +13,7 @@ from django.shortcuts import get_object_or_404, render_to_response
|
||||||
from django.utils.functional import update_wrapper
|
from django.utils.functional import update_wrapper
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.functional import curry
|
||||||
from django.utils.text import capfirst, get_text_list
|
from django.utils.text import capfirst, get_text_list
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
|
@ -28,8 +29,28 @@ get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
|
||||||
class IncorrectLookupParameters(Exception):
|
class IncorrectLookupParameters(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Defaults for formfield_overrides. ModelAdmin subclasses can change this
|
||||||
|
# by adding to ModelAdmin.formfield_overrides.
|
||||||
|
|
||||||
|
FORMFIELD_FOR_DBFIELD_DEFAULTS = {
|
||||||
|
models.DateTimeField: {
|
||||||
|
'form_class': forms.SplitDateTimeField,
|
||||||
|
'widget': widgets.AdminSplitDateTime
|
||||||
|
},
|
||||||
|
models.DateField: {'widget': widgets.AdminDateWidget},
|
||||||
|
models.TimeField: {'widget': widgets.AdminTimeWidget},
|
||||||
|
models.TextField: {'widget': widgets.AdminTextareaWidget},
|
||||||
|
models.URLField: {'widget': widgets.AdminURLFieldWidget},
|
||||||
|
models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},
|
||||||
|
models.CharField: {'widget': widgets.AdminTextInputWidget},
|
||||||
|
models.ImageField: {'widget': widgets.AdminFileWidget},
|
||||||
|
models.FileField: {'widget': widgets.AdminFileWidget},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class BaseModelAdmin(object):
|
class BaseModelAdmin(object):
|
||||||
"""Functionality common to both ModelAdmin and InlineAdmin."""
|
"""Functionality common to both ModelAdmin and InlineAdmin."""
|
||||||
|
|
||||||
raw_id_fields = ()
|
raw_id_fields = ()
|
||||||
fields = None
|
fields = None
|
||||||
exclude = None
|
exclude = None
|
||||||
|
@ -39,6 +60,10 @@ class BaseModelAdmin(object):
|
||||||
filter_horizontal = ()
|
filter_horizontal = ()
|
||||||
radio_fields = {}
|
radio_fields = {}
|
||||||
prepopulated_fields = {}
|
prepopulated_fields = {}
|
||||||
|
formfield_overrides = {}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides)
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -47,101 +72,92 @@ class BaseModelAdmin(object):
|
||||||
|
|
||||||
If kwargs are given, they're passed to the form Field's constructor.
|
If kwargs are given, they're passed to the form Field's constructor.
|
||||||
"""
|
"""
|
||||||
|
request = kwargs.pop("request", None)
|
||||||
|
|
||||||
# If the field specifies choices, we don't need to look for special
|
# If the field specifies choices, we don't need to look for special
|
||||||
# admin widgets - we just need to use a select widget of some kind.
|
# admin widgets - we just need to use a select widget of some kind.
|
||||||
if db_field.choices:
|
if db_field.choices:
|
||||||
if db_field.name in self.radio_fields:
|
return self.formfield_for_choice_field(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
# ForeignKey or ManyToManyFields
|
||||||
|
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
|
||||||
|
# Combine the field kwargs with any options for formfield_overrides.
|
||||||
|
# Make sure the passed in **kwargs override anything in
|
||||||
|
# formfield_overrides because **kwargs is more specific, and should
|
||||||
|
# always win.
|
||||||
|
if db_field.__class__ in self.formfield_overrides:
|
||||||
|
kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
|
||||||
|
|
||||||
|
# Get the correct formfield.
|
||||||
|
if isinstance(db_field, models.ForeignKey):
|
||||||
|
formfield = self.formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
elif isinstance(db_field, models.ManyToManyField):
|
||||||
|
formfield = self.formfield_for_manytomany(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
# For non-raw_id fields, wrap the widget with a wrapper that adds
|
||||||
|
# extra HTML -- the "add other" interface -- to the end of the
|
||||||
|
# rendered output. formfield can be None if it came from a
|
||||||
|
# OneToOneField with parent_link=True or a M2M intermediary.
|
||||||
|
if formfield and db_field.name not in self.raw_id_fields:
|
||||||
|
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
|
||||||
|
|
||||||
|
return formfield
|
||||||
|
|
||||||
|
# If we've got overrides for the formfield defined, use 'em. **kwargs
|
||||||
|
# passed to formfield_for_dbfield override the defaults.
|
||||||
|
if db_field.__class__ in self.formfield_overrides:
|
||||||
|
kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
|
||||||
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
|
# For any other type of field, just call its formfield() method.
|
||||||
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
|
def formfield_for_choice_field(self, db_field, request=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Get a form Field for a database Field that has declared choices.
|
||||||
|
"""
|
||||||
# If the field is named as a radio_field, use a RadioSelect
|
# If the field is named as a radio_field, use a RadioSelect
|
||||||
|
if db_field.name in self.radio_fields:
|
||||||
|
# Avoid stomping on custom widget/choices arguments.
|
||||||
|
if 'widget' not in kwargs:
|
||||||
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
|
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
|
||||||
'class': get_ul_class(self.radio_fields[db_field.name]),
|
'class': get_ul_class(self.radio_fields[db_field.name]),
|
||||||
})
|
})
|
||||||
|
if 'choices' not in kwargs:
|
||||||
kwargs['choices'] = db_field.get_choices(
|
kwargs['choices'] = db_field.get_choices(
|
||||||
include_blank = db_field.blank,
|
include_blank = db_field.blank,
|
||||||
blank_choice=[('', _('None'))]
|
blank_choice=[('', _('None'))]
|
||||||
)
|
)
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
else:
|
|
||||||
# Otherwise, use the default select widget.
|
|
||||||
return db_field.formfield(**kwargs)
|
|
||||||
|
|
||||||
# For DateTimeFields, use a special field and widget.
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
if isinstance(db_field, models.DateTimeField):
|
"""
|
||||||
kwargs['form_class'] = forms.SplitDateTimeField
|
Get a form Field for a ForeignKey.
|
||||||
kwargs['widget'] = widgets.AdminSplitDateTime()
|
"""
|
||||||
return db_field.formfield(**kwargs)
|
if db_field.name in self.raw_id_fields:
|
||||||
|
|
||||||
# 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 TextFields, add a custom CSS class.
|
|
||||||
if isinstance(db_field, models.TextField):
|
|
||||||
kwargs['widget'] = widgets.AdminTextareaWidget
|
|
||||||
return db_field.formfield(**kwargs)
|
|
||||||
|
|
||||||
# For URLFields, add a custom CSS class.
|
|
||||||
if isinstance(db_field, models.URLField):
|
|
||||||
kwargs['widget'] = widgets.AdminURLFieldWidget
|
|
||||||
return db_field.formfield(**kwargs)
|
|
||||||
|
|
||||||
# For IntegerFields, add a custom CSS class.
|
|
||||||
if isinstance(db_field, models.IntegerField):
|
|
||||||
kwargs['widget'] = widgets.AdminIntegerFieldWidget
|
|
||||||
return db_field.formfield(**kwargs)
|
|
||||||
|
|
||||||
# For CommaSeparatedIntegerFields, add a custom CSS class.
|
|
||||||
if isinstance(db_field, models.CommaSeparatedIntegerField):
|
|
||||||
kwargs['widget'] = widgets.AdminCommaSeparatedIntegerFieldWidget
|
|
||||||
return db_field.formfield(**kwargs)
|
|
||||||
|
|
||||||
# For TextInputs, add a custom CSS class.
|
|
||||||
if isinstance(db_field, models.CharField):
|
|
||||||
kwargs['widget'] = widgets.AdminTextInputWidget
|
|
||||||
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)
|
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
|
||||||
elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields:
|
elif db_field.name in self.radio_fields:
|
||||||
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
|
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
|
||||||
'class': get_ul_class(self.radio_fields[db_field.name]),
|
'class': get_ul_class(self.radio_fields[db_field.name]),
|
||||||
})
|
})
|
||||||
kwargs['empty_label'] = db_field.blank and _('None') or None
|
kwargs['empty_label'] = db_field.blank and _('None') or None
|
||||||
else:
|
|
||||||
if isinstance(db_field, models.ManyToManyField):
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
|
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Get a form Field for a ManyToManyField.
|
||||||
|
"""
|
||||||
# If it uses an intermediary model, don't show field in admin.
|
# If it uses an intermediary model, don't show field in admin.
|
||||||
if db_field.rel.through is not None:
|
if db_field.rel.through is not None:
|
||||||
return None
|
return None
|
||||||
elif db_field.name in self.raw_id_fields:
|
|
||||||
|
if db_field.name in self.raw_id_fields:
|
||||||
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
|
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
|
||||||
kwargs['help_text'] = ''
|
kwargs['help_text'] = ''
|
||||||
elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
|
elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
|
||||||
kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
|
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 can be None if it came from a OneToOneField with
|
|
||||||
# parent_link=True
|
|
||||||
if formfield is not None:
|
|
||||||
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
|
|
||||||
return formfield
|
|
||||||
|
|
||||||
# For any other type of field, just call its formfield() method.
|
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
def _declared_fieldsets(self):
|
def _declared_fieldsets(self):
|
||||||
|
@ -292,7 +308,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"form": self.form,
|
"form": self.form,
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
"exclude": exclude + kwargs.get("exclude", []),
|
"exclude": exclude + kwargs.get("exclude", []),
|
||||||
"formfield_callback": self.formfield_for_dbfield,
|
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
|
||||||
}
|
}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return modelform_factory(self.model, **defaults)
|
return modelform_factory(self.model, **defaults)
|
||||||
|
@ -837,7 +853,7 @@ class InlineModelAdmin(BaseModelAdmin):
|
||||||
"fk_name": self.fk_name,
|
"fk_name": self.fk_name,
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
"exclude": exclude + kwargs.get("exclude", []),
|
"exclude": exclude + kwargs.get("exclude", []),
|
||||||
"formfield_callback": self.formfield_for_dbfield,
|
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
|
||||||
"extra": self.extra,
|
"extra": self.extra,
|
||||||
"max_num": self.max_num,
|
"max_num": self.max_num,
|
||||||
}
|
}
|
||||||
|
|
|
@ -597,6 +597,47 @@ with an operator:
|
||||||
Performs a full-text match. This is like the default search method but uses
|
Performs a full-text match. This is like the default search method but uses
|
||||||
an index. Currently this is only available for MySQL.
|
an index. Currently this is only available for MySQL.
|
||||||
|
|
||||||
|
``formfield_overrides``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This provides a quick-and-dirty way to override some of the
|
||||||
|
:class:`~django.forms.Field` options for use in the admin.
|
||||||
|
``formfield_overrides`` is a dictionary mapping a field class to a dict of
|
||||||
|
arguments to pass to the field at construction time.
|
||||||
|
|
||||||
|
Since that's a bit abstract, let's look at a concrete example. The most common
|
||||||
|
use of ``formfield_overrides`` is to add a custom widget for a certain type of
|
||||||
|
field. So, imagine we've written a ``RichTextEditorWidget`` that we'd like to
|
||||||
|
use for large text fields instead of the default ``<textarea>``. Here's how we'd
|
||||||
|
do that::
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Import our custom widget and our model from where they're defined
|
||||||
|
from myapp.widgets import RichTextEditorWidget
|
||||||
|
from myapp.models import MyModel
|
||||||
|
|
||||||
|
class MyModelAdmin(admin.ModelAdmin):
|
||||||
|
formfield_overrides = {
|
||||||
|
models.TextField: {'widget': RichTextEditorWidget},
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that the key in the dictionary is the actual field class, *not* a string.
|
||||||
|
The value is another dictionary; these arguments will be passed to
|
||||||
|
:meth:`~django.forms.Field.__init__`. See :ref:`ref-forms-api` for details.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If you want to use a custom widget with a relation field (i.e.
|
||||||
|
:class:`~django.db.models.ForeignKey` or
|
||||||
|
:class:`~django.db.models.ManyToManyField`), make sure you haven't included
|
||||||
|
that field's name in ``raw_id_fields`` or ``radio_fields``.
|
||||||
|
|
||||||
|
``formfield_overrides`` won't let you change the widget on relation fields
|
||||||
|
that have ``raw_id_fields`` or ``radio_fields`` set. That's because
|
||||||
|
``raw_id_fields`` and ``radio_fields`` imply custom widgets of their own.
|
||||||
|
|
||||||
``ModelAdmin`` methods
|
``ModelAdmin`` methods
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -675,6 +716,23 @@ Notice the wrapped view in the fifth line above::
|
||||||
|
|
||||||
This wrapping will protect ``self.my_view`` from unauthorized access.
|
This wrapping will protect ``self.my_view`` from unauthorized access.
|
||||||
|
|
||||||
|
``formfield_for_foreignkey(self, db_field, request, **kwargs)``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``formfield_for_foreignkey`` method on a ``ModelAdmin`` allows you to
|
||||||
|
override the default formfield for a foreign key field. For example, to
|
||||||
|
return a subset of objects for this foreign key field based on the user::
|
||||||
|
|
||||||
|
class MyModelAdmin(admin.ModelAdmin):
|
||||||
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||||
|
if db_field.name == "car":
|
||||||
|
kwargs["queryset"] = Car.object.filter(owner=request.user)
|
||||||
|
return db_field.formfield(**kwargs)
|
||||||
|
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
This uses the ``HttpRequest`` instance to filter the ``Car`` foreign key field
|
||||||
|
to only the cars owned by the ``User`` instance.
|
||||||
|
|
||||||
``ModelAdmin`` media definitions
|
``ModelAdmin`` media definitions
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?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">testser</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="1" model="admin_widgets.car">
|
||||||
|
<field to="auth.user" name="owner" rel="ManyToOneRel">100</field>
|
||||||
|
<field type="CharField" name="make">Volkswagon</field>
|
||||||
|
<field type="CharField" name="model">Passat</field>
|
||||||
|
</object>
|
||||||
|
<object pk="2" model="admin_widgets.car">
|
||||||
|
<field to="auth.user" name="owner" rel="ManyToOneRel">101</field>
|
||||||
|
<field type="CharField" name="make">BMW</field>
|
||||||
|
<field type="CharField" name="model">M3</field>
|
||||||
|
</object>
|
||||||
|
|
||||||
|
</django-objects>
|
|
@ -2,9 +2,12 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
class Member(models.Model):
|
class Member(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
|
birthdate = models.DateTimeField(blank=True, null=True)
|
||||||
|
gender = models.CharField(max_length=1, blank=True, choices=[('M','Male'), ('F', 'Female')])
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -41,6 +44,28 @@ class Inventory(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class Event(models.Model):
|
||||||
|
band = models.ForeignKey(Band)
|
||||||
|
date = models.DateField(blank=True, null=True)
|
||||||
|
start_time = models.TimeField(blank=True, null=True)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
link = models.URLField(blank=True)
|
||||||
|
min_age = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
|
class Car(models.Model):
|
||||||
|
owner = models.ForeignKey(User)
|
||||||
|
make = models.CharField(max_length=30)
|
||||||
|
model = models.CharField(max_length=30)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s %s" % (self.make, self.model)
|
||||||
|
|
||||||
|
class CarTire(models.Model):
|
||||||
|
"""
|
||||||
|
A single car tire. This to test that a user can only select their own cars.
|
||||||
|
"""
|
||||||
|
car = models.ForeignKey(Car)
|
||||||
|
|
||||||
__test__ = {'WIDGETS_TESTS': """
|
__test__ = {'WIDGETS_TESTS': """
|
||||||
>>> from datetime import datetime
|
>>> from datetime import datetime
|
||||||
>>> from django.utils.html import escape, conditional_escape
|
>>> from django.utils.html import escape, conditional_escape
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import widgets
|
||||||
|
from unittest import TestCase
|
||||||
|
from django.test import TestCase as DjangoTestCase
|
||||||
|
import models
|
||||||
|
|
||||||
|
class AdminFormfieldForDBFieldTests(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for correct behavior of ModelAdmin.formfield_for_dbfield
|
||||||
|
"""
|
||||||
|
|
||||||
|
def assertFormfield(self, model, fieldname, widgetclass, **admin_overrides):
|
||||||
|
"""
|
||||||
|
Helper to call formfield_for_dbfield for a given model and field name
|
||||||
|
and verify that the returned formfield is appropriate.
|
||||||
|
"""
|
||||||
|
# Override any settings on the model admin
|
||||||
|
class MyModelAdmin(admin.ModelAdmin): pass
|
||||||
|
for k in admin_overrides:
|
||||||
|
setattr(MyModelAdmin, k, admin_overrides[k])
|
||||||
|
|
||||||
|
# Construct the admin, and ask it for a formfield
|
||||||
|
ma = MyModelAdmin(model, admin.site)
|
||||||
|
ff = ma.formfield_for_dbfield(model._meta.get_field(fieldname), request=None)
|
||||||
|
|
||||||
|
# "unwrap" the widget wrapper, if needed
|
||||||
|
if isinstance(ff.widget, widgets.RelatedFieldWidgetWrapper):
|
||||||
|
widget = ff.widget.widget
|
||||||
|
else:
|
||||||
|
widget = ff.widget
|
||||||
|
|
||||||
|
# Check that we got a field of the right type
|
||||||
|
self.assert_(
|
||||||
|
isinstance(widget, widgetclass),
|
||||||
|
"Wrong widget for %s.%s: expected %s, got %s" % \
|
||||||
|
(model.__class__.__name__, fieldname, widgetclass, type(widget))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return the formfield so that other tests can continue
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def testDateField(self):
|
||||||
|
self.assertFormfield(models.Event, 'date', widgets.AdminDateWidget)
|
||||||
|
|
||||||
|
def testDateTimeField(self):
|
||||||
|
self.assertFormfield(models.Member, 'birthdate', widgets.AdminSplitDateTime)
|
||||||
|
|
||||||
|
def testTimeField(self):
|
||||||
|
self.assertFormfield(models.Event, 'start_time', widgets.AdminTimeWidget)
|
||||||
|
|
||||||
|
def testTextField(self):
|
||||||
|
self.assertFormfield(models.Event, 'description', widgets.AdminTextareaWidget)
|
||||||
|
|
||||||
|
def testURLField(self):
|
||||||
|
self.assertFormfield(models.Event, 'link', widgets.AdminURLFieldWidget)
|
||||||
|
|
||||||
|
def testIntegerField(self):
|
||||||
|
self.assertFormfield(models.Event, 'min_age', widgets.AdminIntegerFieldWidget)
|
||||||
|
|
||||||
|
def testCharField(self):
|
||||||
|
self.assertFormfield(models.Member, 'name', widgets.AdminTextInputWidget)
|
||||||
|
|
||||||
|
def testFileField(self):
|
||||||
|
self.assertFormfield(models.Album, 'cover_art', widgets.AdminFileWidget)
|
||||||
|
|
||||||
|
def testForeignKey(self):
|
||||||
|
self.assertFormfield(models.Event, 'band', forms.Select)
|
||||||
|
|
||||||
|
def testRawIDForeignKey(self):
|
||||||
|
self.assertFormfield(models.Event, 'band', widgets.ForeignKeyRawIdWidget,
|
||||||
|
raw_id_fields=['band'])
|
||||||
|
|
||||||
|
def testRadioFieldsForeignKey(self):
|
||||||
|
ff = self.assertFormfield(models.Event, 'band', widgets.AdminRadioSelect,
|
||||||
|
radio_fields={'band':admin.VERTICAL})
|
||||||
|
self.assertEqual(ff.empty_label, None)
|
||||||
|
|
||||||
|
def testManyToMany(self):
|
||||||
|
self.assertFormfield(models.Band, 'members', forms.SelectMultiple)
|
||||||
|
|
||||||
|
def testRawIDManyTOMany(self):
|
||||||
|
self.assertFormfield(models.Band, 'members', widgets.ManyToManyRawIdWidget,
|
||||||
|
raw_id_fields=['members'])
|
||||||
|
|
||||||
|
def testFilteredManyToMany(self):
|
||||||
|
self.assertFormfield(models.Band, 'members', widgets.FilteredSelectMultiple,
|
||||||
|
filter_vertical=['members'])
|
||||||
|
|
||||||
|
def testFormfieldOverrides(self):
|
||||||
|
self.assertFormfield(models.Event, 'date', forms.TextInput,
|
||||||
|
formfield_overrides={'widget': forms.TextInput})
|
||||||
|
|
||||||
|
def testFieldWithChoices(self):
|
||||||
|
self.assertFormfield(models.Member, 'gender', forms.Select)
|
||||||
|
|
||||||
|
def testChoicesWithRadioFields(self):
|
||||||
|
self.assertFormfield(models.Member, 'gender', widgets.AdminRadioSelect,
|
||||||
|
radio_fields={'gender':admin.VERTICAL})
|
||||||
|
|
||||||
|
|
||||||
|
class AdminFormfieldForDBFieldWithRequestTests(DjangoTestCase):
|
||||||
|
fixtures = ["admin-widgets-users.xml"]
|
||||||
|
|
||||||
|
def testFilterChoicesByRequestUser(self):
|
||||||
|
"""
|
||||||
|
Ensure the user can only see their own cars in the foreign key dropdown.
|
||||||
|
"""
|
||||||
|
self.client.login(username="super", password="secret")
|
||||||
|
response = self.client.get("/widget_admin/admin_widgets/cartire/add/")
|
||||||
|
self.assert_("BMW M3" not in response.content)
|
||||||
|
self.assert_("Volkswagon Passat" in response.content)
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
import widgetadmin
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^', include(widgetadmin.site.urls)),
|
||||||
|
)
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
class WidgetAdmin(admin.AdminSite):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CarTireAdmin(admin.ModelAdmin):
|
||||||
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||||
|
if db_field.name == "car":
|
||||||
|
kwargs["queryset"] = models.Car.objects.filter(owner=request.user)
|
||||||
|
return db_field.formfield(**kwargs)
|
||||||
|
return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
site = WidgetAdmin()
|
||||||
|
|
||||||
|
site.register(models.Car)
|
||||||
|
site.register(models.CarTire, CarTireAdmin)
|
|
@ -25,6 +25,9 @@ urlpatterns = patterns('',
|
||||||
(r'^test_admin/', include('regressiontests.admin_views.urls')),
|
(r'^test_admin/', include('regressiontests.admin_views.urls')),
|
||||||
(r'^generic_inline_admin/', include('regressiontests.generic_inline_admin.urls')),
|
(r'^generic_inline_admin/', include('regressiontests.generic_inline_admin.urls')),
|
||||||
|
|
||||||
|
# admin widget tests
|
||||||
|
(r'widget_admin/', include('regressiontests.admin_widgets.urls')),
|
||||||
|
|
||||||
(r'^utils/', include('regressiontests.utils.urls')),
|
(r'^utils/', include('regressiontests.utils.urls')),
|
||||||
|
|
||||||
# test urlconf for syndication tests
|
# test urlconf for syndication tests
|
||||||
|
|
Loading…
Reference in New Issue