Author: brosner
Date: 2008-09-01 17:43:38 -0500 (Mon, 01 Sep 2008)
New Revision: 8823
Modified:
django/trunk/django/contrib/admin/templatetags/admin_list.py
django/trunk/django/contrib/admin/views/main.py
django/trunk/django/contrib/admin/widgets.py
django/trunk/django/db/models/fields/related.py
django/trunk/django/forms/models.py
django/trunk/tests/modeltests/model_forms/models.py
django/trunk/tests/regressiontests/admin_widgets/models.py
Log:
Fixed #8648 -- Admin no longer ignores to_field. Thanks for the help Karen
Tracey and SmileyChris.
Modified: django/trunk/django/contrib/admin/templatetags/admin_list.py
===================================================================
--- django/trunk/django/contrib/admin/templatetags/admin_list.py
2008-09-01 22:32:40 UTC (rev 8822)
+++ django/trunk/django/contrib/admin/templatetags/admin_list.py
2008-09-01 22:43:38 UTC (rev 8823)
@@ -222,7 +222,11 @@
url = cl.url_for_result(result)
# Convert the pk to something that can be used in Javascript.
# 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>' % \
(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:
Modified: django/trunk/django/contrib/admin/views/main.py
===================================================================
--- django/trunk/django/contrib/admin/views/main.py 2008-09-01 22:32:40 UTC
(rev 8822)
+++ django/trunk/django/contrib/admin/views/main.py 2008-09-01 22:43:38 UTC
(rev 8823)
@@ -24,6 +24,7 @@
ORDER_TYPE_VAR = 'ot'
PAGE_VAR = 'p'
SEARCH_VAR = 'q'
+TO_FIELD_VAR = 't'
IS_POPUP_VAR = 'pop'
ERROR_FLAG = 'e'
@@ -52,9 +53,12 @@
self.page_num = 0
self.show_all = ALL_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())
if PAGE_VAR in self.params:
del self.params[PAGE_VAR]
+ if TO_FIELD_VAR in self.params:
+ del self.params[TO_FIELD_VAR]
if ERROR_FLAG in self.params:
del self.params[ERROR_FLAG]
Modified: django/trunk/django/contrib/admin/widgets.py
===================================================================
--- django/trunk/django/contrib/admin/widgets.py 2008-09-01 22:32:40 UTC
(rev 8822)
+++ django/trunk/django/contrib/admin/widgets.py 2008-09-01 22:43:38 UTC
(rev 8823)
@@ -41,20 +41,20 @@
class AdminDateWidget(forms.TextInput):
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")
-
+
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",
+ 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.
@@ -86,7 +86,7 @@
"""
def __init__(self, attrs={}):
super(AdminFileWidget, self).__init__(attrs)
-
+
def render(self, name, value, attrs=None):
output = []
if value and hasattr(value, "url"):
@@ -105,11 +105,13 @@
super(ForeignKeyRawIdWidget, self).__init__(attrs)
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())
+ params = {}
if self.rel.limit_choices_to:
- url = '?' + '&'.join(['%s=%s' % (k, ','.join(v)) for k, v in
self.rel.limit_choices_to.items()])
- else:
- url = ''
+ params.update(dict([(k, ','.join(v)) for k, v in
self.rel.limit_choices_to.items()]))
+ params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
+ url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
if not attrs.has_key('class'):
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks
for this hook.
output = [super(ForeignKeyRawIdWidget, self).render(name, value,
attrs)]
@@ -121,11 +123,12 @@
if value:
output.append(self.label_for_value(value))
return mark_safe(u''.join(output))
-
+
def label_for_value(self, value):
- return ' <strong>%s</strong>' % \
- truncate_words(self.rel.to.objects.get(pk=value), 14)
-
+ key = self.rel.get_related_field().name
+ obj = self.rel.to.objects.get(**{key: value})
+ return ' <strong>%s</strong>' % truncate_words(obj, 14)
+
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
"""
A Widget for displaying ManyToMany ids in the "raw_id" interface rather
than
@@ -133,7 +136,7 @@
"""
def __init__(self, rel, attrs=None):
super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
-
+
def render(self, name, value, attrs=None):
attrs['class'] = 'vManyToManyRawIdAdminField'
if value:
@@ -141,7 +144,7 @@
else:
value = ''
return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
-
+
def label_for_value(self, value):
return ''
@@ -152,7 +155,7 @@
if value:
return [value]
return None
-
+
def _has_changed(self, initial, data):
if initial is None:
initial = []
Modified: django/trunk/django/db/models/fields/related.py
===================================================================
--- django/trunk/django/db/models/fields/related.py 2008-09-01 22:32:40 UTC
(rev 8822)
+++ django/trunk/django/db/models/fields/related.py 2008-09-01 22:43:38 UTC
(rev 8823)
@@ -691,7 +691,12 @@
setattr(cls, related.get_accessor_name(),
ForeignRelatedObjectsDescriptor(related))
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)
return super(ForeignKey, self).formfield(**defaults)
Modified: django/trunk/django/forms/models.py
===================================================================
--- django/trunk/django/forms/models.py 2008-09-01 22:32:40 UTC (rev 8822)
+++ django/trunk/django/forms/models.py 2008-09-01 22:43:38 UTC (rev 8823)
@@ -550,15 +550,22 @@
if self.field.cache_choices:
if self.field.choice_cache is None:
self.field.choice_cache = [
- (obj.pk, self.field.label_from_instance(obj))
- for obj in self.queryset.all()
+ self.choice(obj) for obj in self.queryset.all()
]
for choice in self.field.choice_cache:
yield choice
else:
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):
"""A ChoiceField whose choices are a model QuerySet."""
# This class is a subclass of ChoiceField for purity, but it doesn't
@@ -570,7 +577,7 @@
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
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.cache_choices = cache_choices
@@ -580,6 +587,7 @@
*args, **kwargs)
self.queryset = queryset
self.choice_cache = None
+ self.to_field_name = to_field_name
def _get_queryset(self):
return self._queryset
@@ -622,7 +630,8 @@
if value in EMPTY_VALUES:
return None
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:
raise ValidationError(self.error_messages['invalid_choice'])
return value
Modified: django/trunk/tests/modeltests/model_forms/models.py
===================================================================
--- django/trunk/tests/modeltests/model_forms/models.py 2008-09-01 22:32:40 UTC
(rev 8822)
+++ django/trunk/tests/modeltests/model_forms/models.py 2008-09-01 22:43:38 UTC
(rev 8823)
@@ -78,7 +78,7 @@
class WriterProfile(models.Model):
writer = models.OneToOneField(Writer, primary_key=True)
age = models.PositiveIntegerField()
-
+
def __unicode__(self):
return "%s is %s" % (self.writer, self.age)
@@ -137,7 +137,14 @@
class ArticleStatus(models.Model):
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': """
>>> from django import forms
>>> from django.forms.models import ModelForm, model_to_dict
@@ -1135,7 +1142,7 @@
Traceback (most recent call last):
...
ValidationError: [u'Enter only digits separated by commas.']
->>> f.clean(',,,,')
+>>> f.clean(',,,,')
u',,,,'
>>> f.clean('1.2')
Traceback (most recent call last):
@@ -1204,4 +1211,36 @@
...
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>
"""}
Modified: django/trunk/tests/regressiontests/admin_widgets/models.py
===================================================================
--- django/trunk/tests/regressiontests/admin_widgets/models.py 2008-09-01
22:32:40 UTC (rev 8822)
+++ django/trunk/tests/regressiontests/admin_widgets/models.py 2008-09-01
22:43:38 UTC (rev 8823)
@@ -5,14 +5,14 @@
class Member(models.Model):
name = models.CharField(max_length=100)
-
+
def __unicode__(self):
return self.name
class Band(models.Model):
name = models.CharField(max_length=100)
members = models.ManyToManyField(Member)
-
+
def __unicode__(self):
return self.name
@@ -20,10 +20,18 @@
band = models.ForeignKey(Band)
name = models.CharField(max_length=100)
cover_art = models.FileField(upload_to='albums')
-
+
def __unicode__(self):
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': """
>>> from datetime import datetime
>>> from django.utils.html import escape, conditional_escape
@@ -84,6 +92,15 @@
>>> w._has_changed([1, 2], [u'1', u'3'])
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,
'STORAGE_URL': default_storage.url(''),
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---