Ticket #8648: 8648.5.diff

File 8648.5.diff, 13.5 KB (added by brosner, 7 years ago)
  • django/contrib/admin/templatetags/admin_list.py

    diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
    index cae33cc..21e87f5 100644
    a b def items_for_result(cl, result): 
    222222            url = cl.url_for_result(result)
    223223            # Convert the pk to something that can be used in Javascript.
    224224            # Problem cases are long ints (23L) and non-ASCII strings.
    225             result_id = repr(force_unicode(getattr(result, pk)))[1:]
     225            if cl.to_field:
     226                attr = str(cl.to_field)
     227            else:
     228                attr = pk
     229            result_id = repr(force_unicode(getattr(result, attr)))[1:]
    226230            yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
    227231                (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))
    228232        else:
  • django/contrib/admin/views/main.py

    diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
    index 0a5ab37..a6a206d 100644
    a b ORDER_VAR = 'o' 
    2424ORDER_TYPE_VAR = 'ot'
    2525PAGE_VAR = 'p'
    2626SEARCH_VAR = 'q'
     27TO_FIELD_VAR = 't'
    2728IS_POPUP_VAR = 'pop'
    2829ERROR_FLAG = 'e'
    2930
    class ChangeList(object): 
    5253            self.page_num = 0
    5354        self.show_all = ALL_VAR in request.GET
    5455        self.is_popup = IS_POPUP_VAR in request.GET
     56        self.to_field = request.GET.get(TO_FIELD_VAR)
    5557        self.params = dict(request.GET.items())
    5658        if PAGE_VAR in self.params:
    5759            del self.params[PAGE_VAR]
     60        if TO_FIELD_VAR in self.params:
     61            del self.params[TO_FIELD_VAR]
    5862        if ERROR_FLAG in self.params:
    5963            del self.params[ERROR_FLAG]
    6064
  • django/contrib/admin/widgets.py

    diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
    index 15720a7..50e55dc 100644
    a b class FilteredSelectMultiple(forms.SelectMultiple): 
    4141
    4242class AdminDateWidget(forms.TextInput):
    4343    class Media:
    44         js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", 
     44        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
    4545              settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
    46        
     46
    4747    def __init__(self, attrs={}):
    4848        super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
    4949
    5050class AdminTimeWidget(forms.TextInput):
    5151    class Media:
    52         js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", 
     52        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
    5353              settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
    5454
    5555    def __init__(self, attrs={}):
    5656        super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
    57    
     57
    5858class AdminSplitDateTime(forms.SplitDateTimeWidget):
    5959    """
    6060    A SplitDateTime Widget that has some admin-specific styling.
    class AdminFileWidget(forms.FileInput): 
    8686    """
    8787    def __init__(self, attrs={}):
    8888        super(AdminFileWidget, self).__init__(attrs)
    89        
     89
    9090    def render(self, name, value, attrs=None):
    9191        output = []
    9292        if value and hasattr(value, "url"):
    class ForeignKeyRawIdWidget(forms.TextInput): 
    105105        super(ForeignKeyRawIdWidget, self).__init__(attrs)
    106106
    107107    def render(self, name, value, attrs=None):
     108        from django.contrib.admin.views.main import TO_FIELD_VAR
    108109        related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
     110        params = {}
    109111        if self.rel.limit_choices_to:
    110             url = '?' + '&amp;'.join(['%s=%s' % (k, ','.join(v)) for k, v in self.rel.limit_choices_to.items()])
    111         else:
    112             url = ''
     112            params.update(dict([(k, ','.join(v)) for k, v in self.rel.limit_choices_to.items()]))
     113        params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
     114        url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
    113115        if not attrs.has_key('class'):
    114116          attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
    115117        output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
    class ForeignKeyRawIdWidget(forms.TextInput): 
    121123        if value:
    122124            output.append(self.label_for_value(value))
    123125        return mark_safe(u''.join(output))
    124    
     126
    125127    def label_for_value(self, value):
    126         return '&nbsp;<strong>%s</strong>' % \
    127             truncate_words(self.rel.to.objects.get(pk=value), 14)
    128            
     128        key = self.rel.get_related_field().name
     129        obj = self.rel.to.objects.get(**{key: value})
     130        return '&nbsp;<strong>%s</strong>' % truncate_words(obj, 14)
     131
    129132class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
    130133    """
    131134    A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
    class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): 
    133136    """
    134137    def __init__(self, rel, attrs=None):
    135138        super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
    136    
     139
    137140    def render(self, name, value, attrs=None):
    138141        attrs['class'] = 'vManyToManyRawIdAdminField'
    139142        if value:
    class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): 
    141144        else:
    142145            value = ''
    143146        return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
    144    
     147
    145148    def label_for_value(self, value):
    146149        return ''
    147150
    class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): 
    152155        if value:
    153156            return [value]
    154157        return None
    155    
     158
    156159    def _has_changed(self, initial, data):
    157160        if initial is None:
    158161            initial = []
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index ef19477..679f63c 100644
    a b class ForeignKey(RelatedField, Field): 
    691691        setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
    692692
    693693    def formfield(self, **kwargs):
    694         defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)}
     694        defaults = {
     695            'form_class': forms.ModelChoiceField,
     696            'queryset': self.rel.to._default_manager.complex_filter(
     697                                                    self.rel.limit_choices_to),
     698            'to_field_name': self.rel.field_name,
     699        }
    695700        defaults.update(kwargs)
    696701        return super(ForeignKey, self).formfield(**defaults)
    697702
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index c153512..3e5d9f8 100644
    a b class ModelChoiceIterator(object): 
    548548        if self.field.cache_choices:
    549549            if self.field.choice_cache is None:
    550550                self.field.choice_cache = [
    551                     (obj.pk, self.field.label_from_instance(obj))
    552                     for obj in self.queryset.all()
     551                    self.choice(obj) for obj in self.queryset.all()
    553552                ]
    554553            for choice in self.field.choice_cache:
    555554                yield choice
    556555        else:
    557556            for obj in self.queryset.all():
    558                 yield (obj.pk, self.field.label_from_instance(obj))
     557                yield self.choice(obj)
     558
     559    def choice(self, obj):
     560        if self.field.to_field_name:
     561            key = getattr(obj, self.field.to_field_name)
     562        else:
     563            key = obj.pk
     564        return (key, self.field.label_from_instance(obj))
     565
    559566
    560567class ModelChoiceField(ChoiceField):
    561568    """A ChoiceField whose choices are a model QuerySet."""
    class ModelChoiceField(ChoiceField): 
    568575
    569576    def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
    570577                 required=True, widget=None, label=None, initial=None,
    571                  help_text=None, *args, **kwargs):
     578                 help_text=None, to_field_name=None, *args, **kwargs):
    572579        self.empty_label = empty_label
    573580        self.cache_choices = cache_choices
    574581
    class ModelChoiceField(ChoiceField): 
    578585                       *args, **kwargs)
    579586        self.queryset = queryset
    580587        self.choice_cache = None
     588        self.to_field_name = to_field_name
    581589
    582590    def _get_queryset(self):
    583591        return self._queryset
    class ModelChoiceField(ChoiceField): 
    620628        if value in EMPTY_VALUES:
    621629            return None
    622630        try:
    623             value = self.queryset.get(pk=value)
     631            key = self.to_field_name or 'pk'
     632            value = self.queryset.get(**{key: value})
    624633        except self.queryset.model.DoesNotExist:
    625634            raise ValidationError(self.error_messages['invalid_choice'])
    626635        return value
  • tests/modeltests/model_forms/models.py

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index 252d46d..9c9ac82 100644
    a b class BetterWriter(Writer): 
    7878class WriterProfile(models.Model):
    7979    writer = models.OneToOneField(Writer, primary_key=True)
    8080    age = models.PositiveIntegerField()
    81    
     81
    8282    def __unicode__(self):
    8383        return "%s is %s" % (self.writer, self.age)
    8484
    class Price(models.Model): 
    136136class ArticleStatus(models.Model):
    137137    status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True)
    138138
     139class Inventory(models.Model):
     140   barcode = models.PositiveIntegerField(unique=True)
     141   parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
     142   name = models.CharField(blank=False, max_length=20)
    139143
     144   def __unicode__(self):
     145      return self.name
     146     
    140147__test__ = {'API_TESTS': """
    141148>>> from django import forms
    142149>>> from django.forms.models import ModelForm, model_to_dict
    u'1,2,3' 
    11341141Traceback (most recent call last):
    11351142...
    11361143ValidationError: [u'Enter only digits separated by commas.']
    1137 >>> f.clean(',,,,') 
     1144>>> f.clean(',,,,')
    11381145u',,,,'
    11391146>>> f.clean('1.2')
    11401147Traceback (most recent call last):
    Traceback (most recent call last): 
    12031210...
    12041211ValidationError: [u'Select a valid choice. z is not one of the available choices.']
    12051212
     1213# Foreign keys which use to_field #############################################
     1214
     1215>>> apple = Inventory.objects.create(barcode=86, name='Apple')
     1216>>> pear = Inventory.objects.create(barcode=22, name='Pear')
     1217>>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
     1218
     1219>>> field = ModelChoiceField(Inventory.objects.all(), to_field_name='barcode')
     1220>>> for choice in field.choices:
     1221...     print choice
     1222(u'', u'---------')
     1223(86, u'Apple')
     1224(22, u'Pear')
     1225(87, u'Core')
     1226
     1227>>> class InventoryForm(ModelForm):
     1228...     class Meta:
     1229...         model = Inventory
     1230>>> form = InventoryForm(instance=core)
     1231>>> print form['parent']
     1232<select name="parent" id="id_parent">
     1233<option value="">---------</option>
     1234<option value="86" selected="selected">Apple</option>
     1235<option value="22">Pear</option>
     1236<option value="87">Core</option>
     1237</select>
     1238
     1239>>> data = model_to_dict(core)
     1240>>> data['parent'] = '22'
     1241>>> form = InventoryForm(data=data, instance=core)
     1242>>> core = form.save()
     1243>>> core.parent
     1244<Inventory: Pear>
    12061245"""}
  • tests/regressiontests/admin_widgets/models.py

    diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
    index e178a75..2bc9075 100644
    a b from django.core.files.storage import default_storage 
    55
    66class Member(models.Model):
    77    name = models.CharField(max_length=100)
    8    
     8
    99    def __unicode__(self):
    1010        return self.name
    1111
    1212class Band(models.Model):
    1313    name = models.CharField(max_length=100)
    1414    members = models.ManyToManyField(Member)
    15    
     15
    1616    def __unicode__(self):
    1717        return self.name
    1818
    class Album(models.Model): 
    2020    band = models.ForeignKey(Band)
    2121    name = models.CharField(max_length=100)
    2222    cover_art = models.FileField(upload_to='albums')
    23    
     23
    2424    def __unicode__(self):
    2525        return self.name
    2626
     27class Inventory(models.Model):
     28   barcode = models.PositiveIntegerField(unique=True)
     29   parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
     30   name = models.CharField(blank=False, max_length=20)
     31
     32   def __unicode__(self):
     33      return self.name
     34
    2735__test__ = {'WIDGETS_TESTS': """
    2836>>> from datetime import datetime
    2937>>> from django.utils.html import escape, conditional_escape
    True 
    8492>>> w._has_changed([1, 2], [u'1', u'3'])
    8593True
    8694
     95# Check that ForeignKeyRawIdWidget works with fields which aren't related to
     96# the model's primary key.
     97>>> apple = Inventory.objects.create(barcode=86, name='Apple')
     98>>> pear = Inventory.objects.create(barcode=22, name='Pear')
     99>>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
     100>>> rel = Inventory._meta.get_field('parent').rel
     101>>> w = ForeignKeyRawIdWidget(rel)
     102>>> print w.render('test', core.parent_id, attrs={})
     103<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>&nbsp;<strong>Apple</strong>
    87104""" % {
    88105    'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
    89106    'STORAGE_URL': default_storage.url(''),
Back to Top