Ticket #5731: 5731-unified.diff
File 5731-unified.diff, 22.4 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/__init__.py
diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py index 9e8b4fb..6a459ec 100644
a b 1 from django.contrib.admin.options import ModelAdmin 1 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL 2 2 from django.contrib.admin.options import StackedInline, TabularInline 3 3 from django.contrib.admin.sites import AdminSite, site -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c3289ea..5da2489 100644
a b from django.utils.translation import ugettext as _ 15 15 from django.utils.encoding import force_unicode 16 16 import sets 17 17 18 HORIZONTAL, VERTICAL = 1, 2 19 # returns the <ul> class for a given radio_admin value 20 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') 21 18 22 class IncorrectLookupParameters(Exception): 19 23 pass 20 24 … … class AdminField(object): 126 130 class BaseModelAdmin(object): 127 131 """Functionality common to both ModelAdmin and InlineAdmin.""" 128 132 raw_id_fields = () 133 radio_admin_fields = {} 129 134 fields = None 130 135 fieldsets = None 131 136 filter_vertical = () … … class BaseModelAdmin(object): 175 180 if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): 176 181 if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields: 177 182 kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) 183 elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_admin_fields: 184 kwargs['widget'] = forms.RadioSelect(attrs={'class':get_ul_class(self.radio_admin_fields[db_field.name])}) 185 kwargs['empty_label'] = db_field.blank and _('None') or None 178 186 else: 179 187 if isinstance(db_field, models.ManyToManyField) and db_field.name in self.raw_id_fields: 180 188 kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) … … class BaseModelAdmin(object): 187 195 formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel, self.admin_site) 188 196 return formfield 189 197 198 if db_field.choices and db_field.name in self.radio_admin_fields: 199 kwargs['widget'] = forms.RadioSelect(choices=db_field.get_choices(include_blank=db_field.blank, 200 blank_choice=[("", _('None'))]), 201 attrs={'class':get_ul_class(self.radio_admin_fields[db_field.name])}) 202 return db_field.formfield(**kwargs) 203 190 204 # For any other type of field, just call its formfield() method. 191 205 return db_field.formfield(**kwargs) 192 206 -
django/contrib/redirects/models.py
diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py index 68f4afe..1d1e5c9 100644
a b from django.contrib.sites.models import Site 3 3 from django.utils.translation import ugettext_lazy as _ 4 4 5 5 class Redirect(models.Model): 6 site = models.ForeignKey(Site , radio_admin=models.VERTICAL)6 site = models.ForeignKey(Site) 7 7 old_path = models.CharField(_('redirect from'), max_length=200, db_index=True, 8 8 help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'.")) 9 9 new_path = models.CharField(_('redirect to'), max_length=200, blank=True, … … from django.contrib import admin 28 28 class RedirectAdmin(admin.ModelAdmin): 29 29 list_filter = ('site',) 30 30 search_fields = ('old_path', 'new_path') 31 radio_admin_fields = { 'site': admin.VERTICAL } 31 32 32 33 admin.site.register(Redirect, RedirectAdmin) 33 34 -
django/db/models/fields/__init__.py
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 0ea4eae..40bed55 100644
a b from django.utils.maxlength import LegacyMaxlength 24 24 class NOT_PROVIDED: 25 25 pass 26 26 27 HORIZONTAL, VERTICAL = 1, 228 29 27 # The values to use for "blank" in SelectFields. Will be appended to the start of most "choices" lists. 30 28 BLANK_CHOICE_DASH = [("", "---------")] 31 29 BLANK_CHOICE_NONE = [("", "None")] … … BLANK_CHOICE_NONE = [("", "None")] 33 31 # prepares a value for use in a LIKE query 34 32 prep_for_like_query = lambda x: smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") 35 33 36 # returns the <ul> class for a given radio_admin value37 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')38 39 34 class FieldDoesNotExist(Exception): 40 35 pass 41 36 … … class Field(object): 81 76 max_length=None, unique=False, blank=False, null=False, db_index=False, 82 77 core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, 83 78 unique_for_date=None, unique_for_month=None, unique_for_year=None, 84 validator_list=None, choices=None, radio_admin=None,help_text='', db_column=None,79 validator_list=None, choices=None, help_text='', db_column=None, 85 80 db_tablespace=None): 86 81 self.name = name 87 82 self.verbose_name = verbose_name … … class Field(object): 99 94 self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month 100 95 self.unique_for_year = unique_for_year 101 96 self._choices = choices or [] 102 self.radio_admin = radio_admin103 97 self.help_text = help_text 104 98 self.db_column = db_column 105 99 self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE … … class Field(object): 255 249 params['max_length'] = self.max_length 256 250 257 251 if self.choices: 258 if self.radio_admin: 259 field_objs = [oldforms.RadioSelectField] 260 params['ul_class'] = get_ul_class(self.radio_admin) 261 else: 262 field_objs = [oldforms.SelectField] 263 252 field_objs = [oldforms.SelectField] 264 253 params['choices'] = self.get_choices_default() 265 254 else: 266 255 field_objs = self.get_manipulator_field_objs() … … class Field(object): 347 336 return first_choice + lst 348 337 349 338 def get_choices_default(self): 350 if self.radio_admin: 351 return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE) 352 else: 353 return self.get_choices() 339 return self.get_choices() 354 340 355 341 def _get_val_from_obj(self, obj): 356 342 if obj: -
django/db/models/fields/related.py
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 2bd31c9..7146311 100644
a b 1 1 from django.db import connection, transaction 2 2 from django.db.models import signals, get_model 3 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField , get_ul_class3 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField 4 4 from django.db.models.related import RelatedObject 5 5 from django.utils.text import capfirst 6 6 from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ … … class ForeignKey(RelatedField, Field): 496 496 497 497 def prepare_field_objs_and_params(self, manipulator, name_prefix): 498 498 params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname} 499 if self.radio_admin: 500 field_objs = [oldforms.RadioSelectField] 501 params['ul_class'] = get_ul_class(self.radio_admin) 499 if self.null: 500 field_objs = [oldforms.NullSelectField] 502 501 else: 503 if self.null: 504 field_objs = [oldforms.NullSelectField] 505 else: 506 field_objs = [oldforms.SelectField] 502 field_objs = [oldforms.SelectField] 507 503 params['choices'] = self.get_choices_default() 508 504 return field_objs, params 509 505 … … class ForeignKey(RelatedField, Field): 521 517 if not obj: 522 518 # In required many-to-one fields with only one available choice, 523 519 # select that one available choice. Note: For SelectFields 524 # (radio_admin=False), we have to check that the length of choices 525 # is *2*, not 1, because SelectFields always have an initial 526 # "blank" value. Otherwise (radio_admin=True), we check that the 527 # length is 1. 520 # we have to check that the length of choices is *2*, not 1, 521 # because SelectFields always have an initial "blank" value. 528 522 if not self.blank and self.choices: 529 523 choice_list = self.get_choices_default() 530 if self.radio_admin and len(choice_list) == 1: 531 return {self.attname: choice_list[0][0]} 532 if not self.radio_admin and len(choice_list) == 2: 524 if len(choice_list) == 2: 533 525 return {self.attname: choice_list[1][0]} 534 526 return Field.flatten_data(self, follow, obj) 535 527 … … class OneToOneField(RelatedField, IntegerField): 591 583 # ManyToManyField. This works for now. 592 584 def prepare_field_objs_and_params(self, manipulator, name_prefix): 593 585 params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname} 594 if self.radio_admin: 595 field_objs = [oldforms.RadioSelectField] 596 params['ul_class'] = get_ul_class(self.radio_admin) 586 if self.null: 587 field_objs = [oldforms.NullSelectField] 597 588 else: 598 if self.null: 599 field_objs = [oldforms.NullSelectField] 600 else: 601 field_objs = [oldforms.SelectField] 589 field_objs = [oldforms.SelectField] 602 590 params['choices'] = self.get_choices_default() 603 591 return field_objs, params 604 592 -
django/newforms/widgets.py
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index 4f65ff6..9c72883 100644
a b class RadioFieldRenderer(StrAndUnicode): 438 438 439 439 def render(self): 440 440 """Outputs a <ul> for this set of radio fields.""" 441 return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' 442 % force_unicode(w) for w in self])) 441 return mark_safe(u'<ul%s>\n%s\n</ul>' % 442 (flatatt(self.attrs), 443 u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self]))) 443 444 444 445 class RadioSelect(Select): 445 446 renderer = RadioFieldRenderer -
docs/admin.txt
diff --git a/docs/admin.txt b/docs/admin.txt index 5bcc94d..c949a4d 100644
a b There are four steps in activating the Django admin site: 35 35 ``ModelAdmin`` objects 36 36 ====================== 37 37 38 39 ``radio_admin_fields`` 40 ~~~~~~~~~~~~~~~~~~~~~~ 41 42 By default, Django's admin uses a select-box interface (<select>) for 43 fields that are ``ForeignKey`` or have ``choices`` set. If a field is 44 present in ``radio_admin_fields``, Django will use a radio-button interface 45 instead. ``radio_admin_fields`` is a dictionary, keyed by field names. 46 Values should be set to either django.contrib.admin.HORIZONTAL for a 47 horizontal radio-button list, or django.contrib.admin.VERTICAL for a 48 vertical presentation. 49 50 Don't include a field in ``radio_admin_fields`` unless it's a ``ForeignKey`` 51 or has ``choices`` set. 52 53 38 54 ``AdminSite`` objects 39 55 ===================== 40 56 -
docs/model-api.txt
diff --git a/docs/model-api.txt b/docs/model-api.txt index 3f908ec..748a149 100644
a b unless you want to override the default primary-key behavior. 664 664 ``primary_key=True`` implies ``blank=False``, ``null=False`` and 665 665 ``unique=True``. Only one primary key is allowed on an object. 666 666 667 ``radio_admin``668 ~~~~~~~~~~~~~~~669 670 By default, Django's admin uses a select-box interface (<select>) for671 fields that are ``ForeignKey`` or have ``choices`` set. If ``radio_admin``672 is set to ``True``, Django will use a radio-button interface instead.673 674 Don't use this for a field unless it's a ``ForeignKey`` or has ``choices``675 set.676 677 667 ``unique`` 678 668 ~~~~~~~~~~ 679 669 -
tests/regressiontests/forms/forms.py
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index 7c0cf8a..6205ca6 100644
a b gets a distinct ID, formed by appending an underscore plus the button's 423 423 zero-based index. 424 424 >>> f = FrameworkForm(auto_id='id_%s') 425 425 >>> print f['language'] 426 <ul >426 <ul id="id_language"> 427 427 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li> 428 428 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li> 429 429 </ul> … … either as_table() or as_ul(), the label for the RadioSelect will point to the 433 433 ID of the *first* radio button. 434 434 >>> print f 435 435 <tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" id="id_name" /></td></tr> 436 <tr><th><label for="id_language_0">Language:</label></th><td><ul >436 <tr><th><label for="id_language_0">Language:</label></th><td><ul id="id_language"> 437 437 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li> 438 438 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li> 439 439 </ul></td></tr> 440 440 >>> print f.as_ul() 441 441 <li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li> 442 <li><label for="id_language_0">Language:</label> <ul >442 <li><label for="id_language_0">Language:</label> <ul id="id_language"> 443 443 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li> 444 444 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li> 445 445 </ul></li> 446 446 >>> print f.as_p() 447 447 <p><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p> 448 <p><label for="id_language_0">Language:</label> <ul >448 <p><label for="id_language_0">Language:</label> <ul id="id_language"> 449 449 <li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li> 450 450 <li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li> 451 451 </ul></p> -
tests/regressiontests/forms/regressions.py
diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py index 1bb6f6e..c4f0ac0 100644
a b Unicode decoding problems... 40 40 ... somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf') 41 41 >>> f = SomeForm() 42 42 >>> f.as_p() 43 u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul >\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'43 u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul id="id_somechoice">\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>' 44 44 45 45 Testing choice validation with UTF-8 bytestrings as input (these are the 46 46 Russian abbreviations "мес." and "шт.". … … Translated error messages used to be buggy. 56 56 >>> activate('ru') 57 57 >>> f = SomeForm({}) 58 58 >>> f.as_p() 59 u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul >\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'59 u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul id="id_somechoice">\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>' 60 60 >>> deactivate() 61 61 62 62 Deep copying translated text shouldn't raise an error -
tests/regressiontests/forms/widgets.py
diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py index ccfddc9..2a38477 100644
a b u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0 755 755 # Attributes provided at instantiation are passed to the constituent inputs 756 756 >>> w = RadioSelect(attrs={'id':'foo'}) 757 757 >>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) 758 <ul >758 <ul id="foo"> 759 759 <li><label><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li> 760 760 <li><label><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li> 761 761 <li><label><input type="radio" id="foo_2" value="G" name="beatle" /> George</label></li> … … u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0 765 765 # Attributes provided at render-time are passed to the constituent inputs 766 766 >>> w = RadioSelect() 767 767 >>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'}) 768 <ul >768 <ul id="bar"> 769 769 <li><label><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li> 770 770 <li><label><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li> 771 771 <li><label><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></li> -
tests/regressiontests/modeladmin/models.py
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py index 6275135..155e24b 100644
a b from django.db import models 4 4 class Band(models.Model): 5 5 name = models.CharField(max_length=100) 6 6 bio = models.TextField() 7 def __unicode__(self): 8 return self.name 9 10 class Gig(models.Model): 11 main_band = models.ForeignKey(Band, related_name='main_gigs') 12 opening_band = models.ForeignKey(Band, related_name='opening_gigs', blank=True) 13 day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat'))) 14 transport = models.CharField(max_length=100, choices=((1, 'Plane'), (2, 'Train'), (3, 'Bus')), blank=True) 7 15 8 16 9 17 __test__ = {'API_TESTS': """ 10 18 11 >>> from django.contrib.admin.options import ModelAdmin 19 >>> from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL 12 20 >>> from django.contrib.admin.sites import AdminSite 13 21 14 22 None of the following tests really depend on the content of the request, so … … we'll just pass in None. 17 25 >>> request = None 18 26 19 27 >>> band = Band(name='The Doors', bio='') 28 >>> band.save() 20 29 21 30 Under the covers, the admin system will initialize ModelAdmin with a Model 22 31 class and an AdminSite instance, so let's just go ahead and do that manually … … displayed because you forgot to add it to fields/fielsets 84 93 >>> ma.form_change(request, band).base_fields.keys() 85 94 ['name'] 86 95 87 88 96 # radio_admin_fields behavior ################################################ 97 98 First, without any radio_admin_fields specified, the widgets for ForeignKey 99 and fields with choices specified ought to be a basic Select widget. 100 For Select fields, all of the choices lists have a first entry of dashes. 101 102 >>> gma = ModelAdmin(Gig, site) 103 >>> gmafa = gma.form_add(request) 104 >>> gmafa.base_fields['main_band'].widget 105 <django.newforms.widgets.Select object at... 106 >>> list(gmafa.base_fields['main_band'].widget.choices) 107 [(u'', u'---------'), (1, u'The Doors')] 108 >>> gmafa.base_fields['opening_band'].widget 109 <django.newforms.widgets.Select object at... 110 >>> list(gmafa.base_fields['opening_band'].widget.choices) 111 [(u'', u'---------'), (1, u'The Doors')] 112 >>> gmafa.base_fields['day'].widget 113 <django.newforms.widgets.Select object at... 114 >>> list(gmafa.base_fields['day'].widget.choices) 115 [('', '---------'), (1, 'Fri'), (2, 'Sat')] 116 >>> gmafa.base_fields['transport'].widget 117 <django.newforms.widgets.Select object at... 118 >>> list(gmafa.base_fields['transport'].widget.choices) 119 [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')] 120 121 Now specify all the fields as radio_admin_fields. Widgets should now be 122 RadioSelect, and the choices list should have a first entry of 'None' iff 123 blank=True for the model field. Finally, the widget should have the 124 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL. 125 126 >>> class GigAdmin(ModelAdmin): 127 ... radio_admin_fields = {'main_band':HORIZONTAL, 'opening_band':VERTICAL, 'day':VERTICAL, 'transport':HORIZONTAL } 128 129 >>> gma = GigAdmin(Gig, site) 130 >>> gmafa = gma.form_add(request) 131 >>> gmafa.base_fields['main_band'].widget 132 <django.newforms.widgets.RadioSelect object at... 133 >>> gmafa.base_fields['main_band'].widget.attrs 134 {'class': 'radiolist inline'} 135 >>> list(gmafa.base_fields['main_band'].widget.choices) 136 [(1, u'The Doors')] 137 >>> gmafa.base_fields['opening_band'].widget 138 <django.newforms.widgets.RadioSelect object at... 139 >>> gmafa.base_fields['opening_band'].widget.attrs 140 {'class': 'radiolist'} 141 >>> list(gmafa.base_fields['opening_band'].widget.choices) 142 [(u'', u'None'), (1, u'The Doors')] 143 >>> gmafa.base_fields['day'].widget 144 <django.newforms.widgets.RadioSelect object at... 145 >>> gmafa.base_fields['day'].widget.attrs 146 {'class': 'radiolist'} 147 >>> list(gmafa.base_fields['day'].widget.choices) 148 [(1, 'Fri'), (2, 'Sat')] 149 >>> gmafa.base_fields['transport'].widget 150 <django.newforms.widgets.RadioSelect object at... 151 >>> gmafa.base_fields['transport'].widget.attrs 152 {'class': 'radiolist inline'} 153 >>> list(gmafa.base_fields['transport'].widget.choices) 154 [('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')] 155 156 157 158 band.delete() 89 159 90 160 """ 91 161 }