Django

Code

Ticket #2723: booleanwidget_radio_with_tests.diff

File booleanwidget_radio_with_tests.diff, 9.4 kB (added by shanx, 4 months ago)

Improved patch with tests

  • django/contrib/admin/options.py

    old new  
    161161            kwargs['widget'] = widgets.AdminTimeWidget 
    162162            return db_field.formfield(**kwargs) 
    163163 
     164        # For BooleanFields, use a special widget with a custom css class. 
     165        if isinstance(db_field, models.BooleanField): 
     166            kwargs['widget'] = widgets.AdminBooleanSelectWidget 
     167            return db_field.formfield(**kwargs) 
     168 
    164169        # For FileFields and ImageFields add a link to the current file. 
    165170        if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField): 
    166171            kwargs['widget'] = widgets.AdminFileWidget 
  • django/contrib/admin/widgets.py

    old new  
    77from django.utils.text import capfirst, truncate_words 
    88from django.utils.translation import ugettext as _ 
    99from django.utils.safestring import mark_safe 
     10from django.utils.encoding import force_unicode 
    1011from django.conf import settings 
    1112 
    1213class FilteredSelectMultiple(forms.SelectMultiple): 
     
    4647 
    4748    def __init__(self, attrs={}): 
    4849        super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}) 
     50 
     51class AdminBooleanSelectRenderer(forms.widgets.RadioFieldRenderer): 
     52    def render(self): 
     53        """Outputs a <ul> for this set of radio fields.""" 
     54        return mark_safe(u'<ul class="plainlist">\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' 
     55                % force_unicode(w) for w in self])) 
     56 
     57class AdminBooleanSelectWidget(forms.RadioSelect): 
     58    renderer = AdminBooleanSelectRenderer 
    4959     
     60    def __init__(self, attrs=None): 
     61        choices = ((u'1', _('Yes')), (u'2', _('No'))) 
     62        super(AdminBooleanSelectWidget, self).__init__(attrs, choices) 
     63 
     64    def render(self, name, value, attrs=None, choices=()): 
     65        try: 
     66            value = {True: u'1', False: u'2', u'1': u'1', u'2': u'2'}[value] 
     67        except KeyError: 
     68            value = u'2' 
     69        return super(AdminBooleanSelectWidget, self).render(name, value, attrs, choices) 
     70 
     71    def value_from_datadict(self, data, files, name): 
     72        value = data.get(name, None) 
     73        return {u'1': True, u'2': False, True: True, False: False}.get(value, None) 
     74     
    5075class AdminSplitDateTime(forms.SplitDateTimeWidget): 
    5176    """ 
    5277    A SplitDateTime Widget that has some admin-specific styling. 
  • tests/regressiontests/admin_widgets/models.py

    old new  
    2828>>> from django.contrib.admin.widgets import FilteredSelectMultiple, AdminSplitDateTime 
    2929>>> from django.contrib.admin.widgets import AdminFileWidget, ForeignKeyRawIdWidget, ManyToManyRawIdWidget 
    3030>>> from django.contrib.admin.widgets import RelatedFieldWidgetWrapper 
     31>>> from django.contrib.admin.widgets import AdminBooleanSelectWidget 
    3132 
    3233Calling conditional_escape on the output of widget.render will simulate what 
    3334happens in the template. This is easier than setting up a template and context 
     
    6768>>> print conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})) 
    6869<input type="text" name="test" value="1,2" class="vManyToManyRawIdAdminField" /><a href="../../../admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a> 
    6970 
     71The list should have a plainlist class, otherwise both radioselects would be rendered with square bullets in front of them in the Admin. 
     72 
     73>>> w = AdminBooleanSelectWidget() 
     74>>> print conditional_escape(w.render('test', False)) 
     75<ul class="plainlist"> 
     76<li><label><input type="radio" name="test" value="1" /> Yes</label></li> 
     77<li><label><input checked="checked" type="radio" name="test" value="2" /> No</label></li> 
     78</ul> 
     79 
     80>>> print conditional_escape(w.render('test', True)) 
     81<ul class="plainlist"> 
     82<li><label><input checked="checked" type="radio" name="test" value="1" /> Yes</label></li> 
     83<li><label><input type="radio" name="test" value="2" /> No</label></li> 
     84</ul> 
    7085""" % { 
    7186    'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX, 
    7287    'MEDIA_URL': settings.MEDIA_URL, 
    73 }} 
     88}, 'HOOK_TESTS': """ 
     89>>> from django.db import models 
     90>>> from django.contrib.admin.options import BaseModelAdmin 
     91 
     92Make sure that the formfield_for_dbfield in the BaseModelAdmin hooks the right admin widgets to admin fields. 
     93 
     94>>> bma = BaseModelAdmin() 
     95 
     96Check widget for fields that are not hooked, should return normal widget 
     97 
     98>>> unhooked_fields = [ 
     99...     models.AutoField(primary_key=True), 
     100...     models.CharField(), 
     101...     models.CommaSeparatedIntegerField(), 
     102...     models.DecimalField(), 
     103...     models.EmailField(), 
     104...     models.FilePathField(), 
     105...     models.FloatField(), 
     106...     models.IntegerField(), 
     107...     models.IPAddressField(), 
     108...     models.NullBooleanField(), 
     109...     models.PhoneNumberField(), 
     110...     models.PositiveIntegerField(), 
     111...     models.PositiveSmallIntegerField(), 
     112...     models.SlugField(), 
     113...     models.SmallIntegerField(), 
     114...     models.TextField(), 
     115...     models.TimeField(), 
     116...     models.URLField(), 
     117...     models.USStateField(), 
     118...     models.XMLField() 
     119... ] 
     120 
     121>>> for model_field in unhooked_fields: 
     122...     field = bma.formfield_for_dbfield(model_field) 
     123...     field 
     124<django.newforms.fields.CharField object at ...> 
     125<django.newforms.fields.CharField object at ...> 
     126<django.newforms.fields.DecimalField object at ...> 
     127<django.newforms.fields.EmailField object at ...> 
     128<django.newforms.fields.CharField object at ...> 
     129<django.newforms.fields.FloatField object at ...> 
     130<django.newforms.fields.IntegerField object at ...> 
     131<django.newforms.fields.IPAddressField object at ...> 
     132<django.newforms.fields.NullBooleanField object at ...> 
     133<django.contrib.localflavor.us.forms.USPhoneNumberField object at ...> 
     134<django.newforms.fields.IntegerField object at ...> 
     135<django.newforms.fields.IntegerField object at ...> 
     136<django.newforms.fields.CharField object at ...> 
     137<django.newforms.fields.IntegerField object at ...> 
     138<django.newforms.fields.CharField object at ...> 
     139<django.newforms.fields.TimeField object at ...> 
     140<django.newforms.fields.URLField object at ...> 
     141<django.newforms.fields.CharField object at ...> 
     142<django.newforms.fields.CharField object at ...> 
     143 
     144Check widget for fields that are hooked, should return special admin widgets. 
     145 
     146>>> field = bma.formfield_for_dbfield(models.DateTimeField()) 
     147>>> field.widget 
     148<django.contrib.admin.widgets.AdminSplitDateTime object at ...> 
     149 
     150>>> field = bma.formfield_for_dbfield(models.DateField()) 
     151>>> field.widget 
     152<django.contrib.admin.widgets.AdminDateWidget object at ...> 
     153 
     154>>> field = bma.formfield_for_dbfield(models.TimeField()) 
     155>>> field.widget 
     156<django.contrib.admin.widgets.AdminTimeWidget object at ...> 
     157 
     158>>> field = bma.formfield_for_dbfield(models.BooleanField()) 
     159>>> field.widget 
     160<django.contrib.admin.widgets.AdminBooleanSelectWidget object at ...> 
     161 
     162>>> field = bma.formfield_for_dbfield(models.ImageField()) 
     163>>> field.widget 
     164<django.contrib.admin.widgets.AdminFileWidget object at ...> 
     165 
     166>>> field = bma.formfield_for_dbfield(models.FileField()) 
     167>>> field.widget 
     168<django.contrib.admin.widgets.AdminFileWidget object at ...> 
     169 
     170Test ForeignKey and ManyToManyField widgets here, use the model defined 
     171above to retrieve the ForeignKey field, as to be able to traverse the relation. 
     172 
     173Make sure that ForeignKey field returns a raw id widget when the field is flagged as such.  
     174 
     175>>> bma.raw_id_fields = ('band', ) 
     176>>> field = bma.formfield_for_dbfield(Album._meta.get_field('band')) 
     177>>> field.widget 
     178<django.contrib.admin.widgets.ForeignKeyRawIdWidget object at ...> 
     179 
     180Check to see if normal widget is returned when no raw id is selected. 
     181 
     182>>> bma.raw_id_fields = () 
     183>>> bma.admin_site = 'admin site' 
     184>>> field = bma.formfield_for_dbfield(Album._meta.get_field('band')) 
     185>>> field.widget 
     186<django.newforms.widgets.Select object at ...> 
     187 
     188Do the same for ManyToManyField. 
     189 
     190>>> bma.raw_id_fields = ('members',) 
     191>>> field = bma.formfield_for_dbfield(Band._meta.get_field('members')) 
     192>>> field.widget 
     193<django.contrib.admin.widgets.ManyToManyRawIdWidget object at ...> 
     194 
     195Check for no raw id in. 
     196 
     197>>> bma.raw_id_fields = () 
     198>>> field = bma.formfield_for_dbfield(Band._meta.get_field('members')) 
     199>>> field.widget 
     200<django.newforms.widgets.SelectMultiple object at ...> 
     201 
     202>>> bma.filter_horizontal = ('members', ) 
     203>>> field = bma.formfield_for_dbfield(Band._meta.get_field('members')) 
     204>>> field.widget 
     205<django.contrib.admin.widgets.FilteredSelectMultiple object at ...> 
     206"""} 
  • AUTHORS

    old new  
    283283    Philippe Raoult <philippe.raoult@n2nsoft.com> 
    284284    Massimiliano Ravelli <massimiliano.ravelli@gmail.com> 
    285285    Brian Ray <http://brianray.chipy.org/> 
    286     remco@diji.biz 
     286    Remco Wendt <remco@diji.biz> 
    287287    David Reynolds <david@reynoldsfamily.org.uk> 
    288288    rhettg@gmail.com 
    289289    ricardojbarrios@gmail.com