Fixed #8648 -- Admin no longer ignores to_field. Thanks for the help Karen Tracey and SmileyChris.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@8823 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
dcb0e8f959
commit
ce47d4ab83
|
@ -222,7 +222,11 @@ def items_for_result(cl, result):
|
||||||
url = cl.url_for_result(result)
|
url = cl.url_for_result(result)
|
||||||
# Convert the pk to something that can be used in Javascript.
|
# Convert the pk to something that can be used in Javascript.
|
||||||
# Problem cases are long ints (23L) and non-ASCII strings.
|
# Problem cases are long ints (23L) and non-ASCII strings.
|
||||||
result_id = repr(force_unicode(getattr(result, pk)))[1:]
|
if cl.to_field:
|
||||||
|
attr = str(cl.to_field)
|
||||||
|
else:
|
||||||
|
attr = pk
|
||||||
|
result_id = repr(force_unicode(getattr(result, attr)))[1:]
|
||||||
yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
|
yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
|
||||||
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
|
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -24,6 +24,7 @@ ORDER_VAR = 'o'
|
||||||
ORDER_TYPE_VAR = 'ot'
|
ORDER_TYPE_VAR = 'ot'
|
||||||
PAGE_VAR = 'p'
|
PAGE_VAR = 'p'
|
||||||
SEARCH_VAR = 'q'
|
SEARCH_VAR = 'q'
|
||||||
|
TO_FIELD_VAR = 't'
|
||||||
IS_POPUP_VAR = 'pop'
|
IS_POPUP_VAR = 'pop'
|
||||||
ERROR_FLAG = 'e'
|
ERROR_FLAG = 'e'
|
||||||
|
|
||||||
|
@ -52,9 +53,12 @@ class ChangeList(object):
|
||||||
self.page_num = 0
|
self.page_num = 0
|
||||||
self.show_all = ALL_VAR in request.GET
|
self.show_all = ALL_VAR in request.GET
|
||||||
self.is_popup = IS_POPUP_VAR in request.GET
|
self.is_popup = IS_POPUP_VAR in request.GET
|
||||||
|
self.to_field = request.GET.get(TO_FIELD_VAR)
|
||||||
self.params = dict(request.GET.items())
|
self.params = dict(request.GET.items())
|
||||||
if PAGE_VAR in self.params:
|
if PAGE_VAR in self.params:
|
||||||
del self.params[PAGE_VAR]
|
del self.params[PAGE_VAR]
|
||||||
|
if TO_FIELD_VAR in self.params:
|
||||||
|
del self.params[TO_FIELD_VAR]
|
||||||
if ERROR_FLAG in self.params:
|
if ERROR_FLAG in self.params:
|
||||||
del self.params[ERROR_FLAG]
|
del self.params[ERROR_FLAG]
|
||||||
|
|
||||||
|
|
|
@ -41,20 +41,20 @@ class FilteredSelectMultiple(forms.SelectMultiple):
|
||||||
|
|
||||||
class AdminDateWidget(forms.TextInput):
|
class AdminDateWidget(forms.TextInput):
|
||||||
class Media:
|
class Media:
|
||||||
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
|
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
|
||||||
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
|
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
|
||||||
|
|
||||||
def __init__(self, attrs={}):
|
def __init__(self, attrs={}):
|
||||||
super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
|
super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
|
||||||
|
|
||||||
class AdminTimeWidget(forms.TextInput):
|
class AdminTimeWidget(forms.TextInput):
|
||||||
class Media:
|
class Media:
|
||||||
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
|
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
|
||||||
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
|
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
|
||||||
|
|
||||||
def __init__(self, attrs={}):
|
def __init__(self, attrs={}):
|
||||||
super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
|
super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
|
||||||
|
|
||||||
class AdminSplitDateTime(forms.SplitDateTimeWidget):
|
class AdminSplitDateTime(forms.SplitDateTimeWidget):
|
||||||
"""
|
"""
|
||||||
A SplitDateTime Widget that has some admin-specific styling.
|
A SplitDateTime Widget that has some admin-specific styling.
|
||||||
|
@ -86,7 +86,7 @@ class AdminFileWidget(forms.FileInput):
|
||||||
"""
|
"""
|
||||||
def __init__(self, attrs={}):
|
def __init__(self, attrs={}):
|
||||||
super(AdminFileWidget, self).__init__(attrs)
|
super(AdminFileWidget, self).__init__(attrs)
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
output = []
|
output = []
|
||||||
if value and hasattr(value, "url"):
|
if value and hasattr(value, "url"):
|
||||||
|
@ -105,11 +105,13 @@ class ForeignKeyRawIdWidget(forms.TextInput):
|
||||||
super(ForeignKeyRawIdWidget, self).__init__(attrs)
|
super(ForeignKeyRawIdWidget, self).__init__(attrs)
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
|
from django.contrib.admin.views.main import TO_FIELD_VAR
|
||||||
related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
|
related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
|
||||||
|
params = {}
|
||||||
if self.rel.limit_choices_to:
|
if self.rel.limit_choices_to:
|
||||||
url = '?' + '&'.join(['%s=%s' % (k, ','.join(v)) for k, v in self.rel.limit_choices_to.items()])
|
params.update(dict([(k, ','.join(v)) for k, v in self.rel.limit_choices_to.items()]))
|
||||||
else:
|
params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
|
||||||
url = ''
|
url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
|
||||||
if not attrs.has_key('class'):
|
if not attrs.has_key('class'):
|
||||||
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
|
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
|
||||||
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
|
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
|
||||||
|
@ -121,11 +123,12 @@ class ForeignKeyRawIdWidget(forms.TextInput):
|
||||||
if value:
|
if value:
|
||||||
output.append(self.label_for_value(value))
|
output.append(self.label_for_value(value))
|
||||||
return mark_safe(u''.join(output))
|
return mark_safe(u''.join(output))
|
||||||
|
|
||||||
def label_for_value(self, value):
|
def label_for_value(self, value):
|
||||||
return ' <strong>%s</strong>' % \
|
key = self.rel.get_related_field().name
|
||||||
truncate_words(self.rel.to.objects.get(pk=value), 14)
|
obj = self.rel.to.objects.get(**{key: value})
|
||||||
|
return ' <strong>%s</strong>' % truncate_words(obj, 14)
|
||||||
|
|
||||||
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
||||||
"""
|
"""
|
||||||
A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
|
A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
|
||||||
|
@ -133,7 +136,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
||||||
"""
|
"""
|
||||||
def __init__(self, rel, attrs=None):
|
def __init__(self, rel, attrs=None):
|
||||||
super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
|
super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
attrs['class'] = 'vManyToManyRawIdAdminField'
|
attrs['class'] = 'vManyToManyRawIdAdminField'
|
||||||
if value:
|
if value:
|
||||||
|
@ -141,7 +144,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
||||||
else:
|
else:
|
||||||
value = ''
|
value = ''
|
||||||
return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
|
return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
|
||||||
|
|
||||||
def label_for_value(self, value):
|
def label_for_value(self, value):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@ -152,7 +155,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
||||||
if value:
|
if value:
|
||||||
return [value]
|
return [value]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _has_changed(self, initial, data):
|
def _has_changed(self, initial, data):
|
||||||
if initial is None:
|
if initial is None:
|
||||||
initial = []
|
initial = []
|
||||||
|
|
|
@ -691,7 +691,12 @@ class ForeignKey(RelatedField, Field):
|
||||||
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
|
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)}
|
defaults = {
|
||||||
|
'form_class': forms.ModelChoiceField,
|
||||||
|
'queryset': self.rel.to._default_manager.complex_filter(
|
||||||
|
self.rel.limit_choices_to),
|
||||||
|
'to_field_name': self.rel.field_name,
|
||||||
|
}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(ForeignKey, self).formfield(**defaults)
|
return super(ForeignKey, self).formfield(**defaults)
|
||||||
|
|
||||||
|
|
|
@ -550,14 +550,21 @@ class ModelChoiceIterator(object):
|
||||||
if self.field.cache_choices:
|
if self.field.cache_choices:
|
||||||
if self.field.choice_cache is None:
|
if self.field.choice_cache is None:
|
||||||
self.field.choice_cache = [
|
self.field.choice_cache = [
|
||||||
(obj.pk, self.field.label_from_instance(obj))
|
self.choice(obj) for obj in self.queryset.all()
|
||||||
for obj in self.queryset.all()
|
|
||||||
]
|
]
|
||||||
for choice in self.field.choice_cache:
|
for choice in self.field.choice_cache:
|
||||||
yield choice
|
yield choice
|
||||||
else:
|
else:
|
||||||
for obj in self.queryset.all():
|
for obj in self.queryset.all():
|
||||||
yield (obj.pk, self.field.label_from_instance(obj))
|
yield self.choice(obj)
|
||||||
|
|
||||||
|
def choice(self, obj):
|
||||||
|
if self.field.to_field_name:
|
||||||
|
key = getattr(obj, self.field.to_field_name)
|
||||||
|
else:
|
||||||
|
key = obj.pk
|
||||||
|
return (key, self.field.label_from_instance(obj))
|
||||||
|
|
||||||
|
|
||||||
class ModelChoiceField(ChoiceField):
|
class ModelChoiceField(ChoiceField):
|
||||||
"""A ChoiceField whose choices are a model QuerySet."""
|
"""A ChoiceField whose choices are a model QuerySet."""
|
||||||
|
@ -570,7 +577,7 @@ class ModelChoiceField(ChoiceField):
|
||||||
|
|
||||||
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
|
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
|
||||||
required=True, widget=None, label=None, initial=None,
|
required=True, widget=None, label=None, initial=None,
|
||||||
help_text=None, *args, **kwargs):
|
help_text=None, to_field_name=None, *args, **kwargs):
|
||||||
self.empty_label = empty_label
|
self.empty_label = empty_label
|
||||||
self.cache_choices = cache_choices
|
self.cache_choices = cache_choices
|
||||||
|
|
||||||
|
@ -580,6 +587,7 @@ class ModelChoiceField(ChoiceField):
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
self.queryset = queryset
|
self.queryset = queryset
|
||||||
self.choice_cache = None
|
self.choice_cache = None
|
||||||
|
self.to_field_name = to_field_name
|
||||||
|
|
||||||
def _get_queryset(self):
|
def _get_queryset(self):
|
||||||
return self._queryset
|
return self._queryset
|
||||||
|
@ -622,7 +630,8 @@ class ModelChoiceField(ChoiceField):
|
||||||
if value in EMPTY_VALUES:
|
if value in EMPTY_VALUES:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
value = self.queryset.get(pk=value)
|
key = self.to_field_name or 'pk'
|
||||||
|
value = self.queryset.get(**{key: value})
|
||||||
except self.queryset.model.DoesNotExist:
|
except self.queryset.model.DoesNotExist:
|
||||||
raise ValidationError(self.error_messages['invalid_choice'])
|
raise ValidationError(self.error_messages['invalid_choice'])
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -78,7 +78,7 @@ class BetterWriter(Writer):
|
||||||
class WriterProfile(models.Model):
|
class WriterProfile(models.Model):
|
||||||
writer = models.OneToOneField(Writer, primary_key=True)
|
writer = models.OneToOneField(Writer, primary_key=True)
|
||||||
age = models.PositiveIntegerField()
|
age = models.PositiveIntegerField()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s is %s" % (self.writer, self.age)
|
return "%s is %s" % (self.writer, self.age)
|
||||||
|
|
||||||
|
@ -137,7 +137,14 @@ class Price(models.Model):
|
||||||
class ArticleStatus(models.Model):
|
class ArticleStatus(models.Model):
|
||||||
status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True)
|
status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True)
|
||||||
|
|
||||||
|
class Inventory(models.Model):
|
||||||
|
barcode = models.PositiveIntegerField(unique=True)
|
||||||
|
parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
|
||||||
|
name = models.CharField(blank=False, max_length=20)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
__test__ = {'API_TESTS': """
|
__test__ = {'API_TESTS': """
|
||||||
>>> from django import forms
|
>>> from django import forms
|
||||||
>>> from django.forms.models import ModelForm, model_to_dict
|
>>> from django.forms.models import ModelForm, model_to_dict
|
||||||
|
@ -1135,7 +1142,7 @@ u'1,2,3'
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Enter only digits separated by commas.']
|
ValidationError: [u'Enter only digits separated by commas.']
|
||||||
>>> f.clean(',,,,')
|
>>> f.clean(',,,,')
|
||||||
u',,,,'
|
u',,,,'
|
||||||
>>> f.clean('1.2')
|
>>> f.clean('1.2')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
|
@ -1204,4 +1211,36 @@ Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. z is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. z is not one of the available choices.']
|
||||||
|
|
||||||
|
# Foreign keys which use to_field #############################################
|
||||||
|
|
||||||
|
>>> apple = Inventory.objects.create(barcode=86, name='Apple')
|
||||||
|
>>> pear = Inventory.objects.create(barcode=22, name='Pear')
|
||||||
|
>>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
|
||||||
|
|
||||||
|
>>> field = ModelChoiceField(Inventory.objects.all(), to_field_name='barcode')
|
||||||
|
>>> for choice in field.choices:
|
||||||
|
... print choice
|
||||||
|
(u'', u'---------')
|
||||||
|
(86, u'Apple')
|
||||||
|
(22, u'Pear')
|
||||||
|
(87, u'Core')
|
||||||
|
|
||||||
|
>>> class InventoryForm(ModelForm):
|
||||||
|
... class Meta:
|
||||||
|
... model = Inventory
|
||||||
|
>>> form = InventoryForm(instance=core)
|
||||||
|
>>> print form['parent']
|
||||||
|
<select name="parent" id="id_parent">
|
||||||
|
<option value="">---------</option>
|
||||||
|
<option value="86" selected="selected">Apple</option>
|
||||||
|
<option value="22">Pear</option>
|
||||||
|
<option value="87">Core</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
>>> data = model_to_dict(core)
|
||||||
|
>>> data['parent'] = '22'
|
||||||
|
>>> form = InventoryForm(data=data, instance=core)
|
||||||
|
>>> core = form.save()
|
||||||
|
>>> core.parent
|
||||||
|
<Inventory: Pear>
|
||||||
"""}
|
"""}
|
||||||
|
|
|
@ -5,14 +5,14 @@ from django.core.files.storage import default_storage
|
||||||
|
|
||||||
class Member(models.Model):
|
class Member(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Band(models.Model):
|
class Band(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
members = models.ManyToManyField(Member)
|
members = models.ManyToManyField(Member)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@ -20,10 +20,18 @@ class Album(models.Model):
|
||||||
band = models.ForeignKey(Band)
|
band = models.ForeignKey(Band)
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
cover_art = models.FileField(upload_to='albums')
|
cover_art = models.FileField(upload_to='albums')
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class Inventory(models.Model):
|
||||||
|
barcode = models.PositiveIntegerField(unique=True)
|
||||||
|
parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
|
||||||
|
name = models.CharField(blank=False, max_length=20)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
__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
|
||||||
|
@ -84,6 +92,15 @@ True
|
||||||
>>> w._has_changed([1, 2], [u'1', u'3'])
|
>>> w._has_changed([1, 2], [u'1', u'3'])
|
||||||
True
|
True
|
||||||
|
|
||||||
|
# Check that ForeignKeyRawIdWidget works with fields which aren't related to
|
||||||
|
# the model's primary key.
|
||||||
|
>>> apple = Inventory.objects.create(barcode=86, name='Apple')
|
||||||
|
>>> pear = Inventory.objects.create(barcode=22, name='Pear')
|
||||||
|
>>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
|
||||||
|
>>> rel = Inventory._meta.get_field('parent').rel
|
||||||
|
>>> w = ForeignKeyRawIdWidget(rel)
|
||||||
|
>>> print w.render('test', core.parent_id, attrs={})
|
||||||
|
<input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="/admin_media/img/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Apple</strong>
|
||||||
""" % {
|
""" % {
|
||||||
'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
|
'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
|
||||||
'STORAGE_URL': default_storage.url(''),
|
'STORAGE_URL': default_storage.url(''),
|
||||||
|
|
Loading…
Reference in New Issue