Ticket #5731: 5731-unified.diff

File 5731-unified.diff, 22.4 KB (added by Jeff Anderson, 16 years ago)

unified patch against current newforms-admin

  • 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
     1from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
    22from django.contrib.admin.options import StackedInline, TabularInline
    33from 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 _  
    1515from django.utils.encoding import force_unicode
    1616import sets
    1717
     18HORIZONTAL, VERTICAL = 1, 2
     19# returns the <ul> class for a given radio_admin value
     20get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
     21
    1822class IncorrectLookupParameters(Exception):
    1923    pass
    2024
    class AdminField(object):  
    126130class BaseModelAdmin(object):
    127131    """Functionality common to both ModelAdmin and InlineAdmin."""
    128132    raw_id_fields = ()
     133    radio_admin_fields = {}
    129134    fields = None
    130135    fieldsets = None
    131136    filter_vertical = ()
    class BaseModelAdmin(object):  
    175180        if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
    176181            if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
    177182                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
    178186            else:
    179187                if isinstance(db_field, models.ManyToManyField) and db_field.name in self.raw_id_fields:
    180188                    kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
    class BaseModelAdmin(object):  
    187195                formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel, self.admin_site)
    188196            return formfield
    189197
     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
    190204        # For any other type of field, just call its formfield() method.
    191205        return db_field.formfield(**kwargs)
    192206
  • 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  
    33from django.utils.translation import ugettext_lazy as _
    44
    55class Redirect(models.Model):
    6     site = models.ForeignKey(Site, radio_admin=models.VERTICAL)
     6    site = models.ForeignKey(Site)
    77    old_path = models.CharField(_('redirect from'), max_length=200, db_index=True,
    88        help_text=_("This should be an absolute path, excluding the domain name. Example: '/events/search/'."))
    99    new_path = models.CharField(_('redirect to'), max_length=200, blank=True,
    from django.contrib import admin  
    2828class RedirectAdmin(admin.ModelAdmin):
    2929    list_filter = ('site',)
    3030    search_fields = ('old_path', 'new_path')
     31    radio_admin_fields = { 'site': admin.VERTICAL }
    3132
    3233admin.site.register(Redirect, RedirectAdmin)
    3334
  • 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  
    2424class NOT_PROVIDED:
    2525    pass
    2626
    27 HORIZONTAL, VERTICAL = 1, 2
    28 
    2927# The values to use for "blank" in SelectFields. Will be appended to the start of most "choices" lists.
    3028BLANK_CHOICE_DASH = [("", "---------")]
    3129BLANK_CHOICE_NONE = [("", "None")]
    BLANK_CHOICE_NONE = [("", "None")]  
    3331# prepares a value for use in a LIKE query
    3432prep_for_like_query = lambda x: smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
    3533
    36 # returns the <ul> class for a given radio_admin value
    37 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
    38 
    3934class FieldDoesNotExist(Exception):
    4035    pass
    4136
    class Field(object):  
    8176        max_length=None, unique=False, blank=False, null=False, db_index=False,
    8277        core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True,
    8378        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,
    8580        db_tablespace=None):
    8681        self.name = name
    8782        self.verbose_name = verbose_name
    class Field(object):  
    9994        self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
    10095        self.unique_for_year = unique_for_year
    10196        self._choices = choices or []
    102         self.radio_admin = radio_admin
    10397        self.help_text = help_text
    10498        self.db_column = db_column
    10599        self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE
    class Field(object):  
    255249            params['max_length'] = self.max_length
    256250
    257251        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]
    264253            params['choices'] = self.get_choices_default()
    265254        else:
    266255            field_objs = self.get_manipulator_field_objs()
    class Field(object):  
    347336        return first_choice + lst
    348337
    349338    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()
    354340
    355341    def _get_val_from_obj(self, obj):
    356342        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  
    11from django.db import connection, transaction
    22from django.db.models import signals, get_model
    3 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class
     3from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField
    44from django.db.models.related import RelatedObject
    55from django.utils.text import capfirst
    66from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
    class ForeignKey(RelatedField, Field):  
    496496
    497497    def prepare_field_objs_and_params(self, manipulator, name_prefix):
    498498        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]
    502501        else:
    503             if self.null:
    504                 field_objs = [oldforms.NullSelectField]
    505             else:
    506                 field_objs = [oldforms.SelectField]
     502            field_objs = [oldforms.SelectField]
    507503        params['choices'] = self.get_choices_default()
    508504        return field_objs, params
    509505
    class ForeignKey(RelatedField, Field):  
    521517        if not obj:
    522518            # In required many-to-one fields with only one available choice,
    523519            # 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.
    528522            if not self.blank and self.choices:
    529523                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:
    533525                    return {self.attname: choice_list[1][0]}
    534526        return Field.flatten_data(self, follow, obj)
    535527
    class OneToOneField(RelatedField, IntegerField):  
    591583    # ManyToManyField. This works for now.
    592584    def prepare_field_objs_and_params(self, manipulator, name_prefix):
    593585        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]
    597588        else:
    598             if self.null:
    599                 field_objs = [oldforms.NullSelectField]
    600             else:
    601                 field_objs = [oldforms.SelectField]
     589            field_objs = [oldforms.SelectField]
    602590        params['choices'] = self.get_choices_default()
    603591        return field_objs, params
    604592
  • 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):  
    438438
    439439    def render(self):
    440440        """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])))
    443444
    444445class RadioSelect(Select):
    445446    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:  
    3535``ModelAdmin`` objects
    3636======================
    3737
     38
     39``radio_admin_fields``
     40~~~~~~~~~~~~~~~~~~~~~~
     41
     42By default, Django's admin uses a select-box interface (<select>) for
     43fields that are ``ForeignKey`` or have ``choices`` set. If a field is
     44present in ``radio_admin_fields``, Django will use a radio-button interface
     45instead. ``radio_admin_fields`` is a dictionary, keyed by field names.
     46Values should be set to either django.contrib.admin.HORIZONTAL for a
     47horizontal radio-button list, or django.contrib.admin.VERTICAL for a
     48vertical presentation.
     49
     50Don't include a field in ``radio_admin_fields`` unless it's a ``ForeignKey``
     51or has ``choices`` set.
     52
     53
    3854``AdminSite`` objects
    3955=====================
    4056
  • 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.  
    664664``primary_key=True`` implies ``blank=False``, ``null=False`` and
    665665``unique=True``. Only one primary key is allowed on an object.
    666666
    667 ``radio_admin``
    668 ~~~~~~~~~~~~~~~
    669 
    670 By default, Django's admin uses a select-box interface (<select>) for
    671 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 
    677667``unique``
    678668~~~~~~~~~~
    679669
  • 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  
    423423zero-based index.
    424424>>> f = FrameworkForm(auto_id='id_%s')
    425425>>> print f['language']
    426 <ul>
     426<ul id="id_language">
    427427<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
    428428<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
    429429</ul>
    either as_table() or as_ul(), the label for the RadioSelect will point to the  
    433433ID of the *first* radio button.
    434434>>> print f
    435435<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">
    437437<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
    438438<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
    439439</ul></td></tr>
    440440>>> print f.as_ul()
    441441<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">
    443443<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
    444444<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
    445445</ul></li>
    446446>>> print f.as_p()
    447447<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">
    449449<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
    450450<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
    451451</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...  
    4040...     somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf')
    4141>>> f = SomeForm()
    4242>>> 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>'
     43u'<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>'
    4444
    4545Testing choice validation with UTF-8 bytestrings as input (these are the
    4646Russian abbreviations "мес." and "шт.".
    Translated error messages used to be buggy.  
    5656>>> activate('ru')
    5757>>> f = SomeForm({})
    5858>>> 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>'
     59u'<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>'
    6060>>> deactivate()
    6161
    6262Deep 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  
    755755# Attributes provided at instantiation are passed to the constituent inputs
    756756>>> w = RadioSelect(attrs={'id':'foo'})
    757757>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
    758 <ul>
     758<ul id="foo">
    759759<li><label><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li>
    760760<li><label><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li>
    761761<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  
    765765# Attributes provided at render-time are passed to the constituent inputs
    766766>>> w = RadioSelect()
    767767>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'})
    768 <ul>
     768<ul id="bar">
    769769<li><label><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li>
    770770<li><label><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li>
    771771<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  
    44class Band(models.Model):
    55    name = models.CharField(max_length=100)
    66    bio = models.TextField()
     7    def __unicode__(self):
     8        return self.name
     9
     10class 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)
    715
    816
    917__test__ = {'API_TESTS': """
    1018
    11 >>> from django.contrib.admin.options import ModelAdmin
     19>>> from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
    1220>>> from django.contrib.admin.sites import AdminSite
    1321
    1422None of the following tests really depend on the content of the request, so
    we'll just pass in None.  
    1725>>> request = None
    1826
    1927>>> band = Band(name='The Doors', bio='')
     28>>> band.save()
    2029
    2130Under the covers, the admin system will initialize ModelAdmin with a Model
    2231class and an AdminSite instance, so let's just go ahead and do that manually
    displayed because you forgot to add it to fields/fielsets  
    8493>>> ma.form_change(request, band).base_fields.keys()
    8594['name']
    8695
    87 
    88 
     96# radio_admin_fields behavior ################################################
     97
     98First, without any radio_admin_fields specified, the widgets for ForeignKey
     99and fields with choices specified ought to be a basic Select widget.
     100For 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
     121Now specify all the fields as radio_admin_fields.  Widgets should now be
     122RadioSelect, and the choices list should have a first entry of 'None' iff
     123blank=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
     158band.delete()
    89159
    90160"""
    91161}
Back to Top