Ticket #13163: combined_13165_13165.diff

File combined_13165_13165.diff, 24.0 KB (added by Simon Meers, 14 years ago)

Added unicode fix

  • django/contrib/admin/options.py

     
    145145        """
    146146        db = kwargs.get('using')
    147147        if db_field.name in self.raw_id_fields:
    148             kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
     148            kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db, admin_site=self.admin_site)
    149149        elif db_field.name in self.radio_fields:
    150150            kwargs['widget'] = widgets.AdminRadioSelect(attrs={
    151151                'class': get_ul_class(self.radio_fields[db_field.name]),
  • django/contrib/admin/util.py

     
    333333        return formats.number_format(value)
    334334    else:
    335335        return smart_unicode(value)
     336
     337
     338def get_related_object(rel, value):
     339    try:
     340        return rel.to.objects.get(pk=value)
     341    except:
     342        return None
     343
     344def get_changelink_url(obj, admin_site):
     345    """ If obj is registered in admin_site, return URL for change view. """
     346    if admin_site is None:
     347        # probably a ManyToManyRawIdWidget
     348        return None
     349    if obj.__class__ in admin_site._registry:
     350        try:
     351            view_name = '%s:%s_%s_change' % (
     352                admin_site.name,
     353                obj._meta.app_label,
     354                obj._meta.object_name.lower())
     355            return reverse(view_name, None, (quote(obj._get_pk_val()),))
     356        except NoReverseMatch:
     357            # should not happen, but fail silently just in case
     358            pass
     359    return None
     360
     361def get_changelink_html(obj, admin_site, show_name=False):
     362    """ Return HTML link to change view for obj if registered in admin_site.
     363
     364    Optionally includes representation of object (%s) in link.
     365    """
     366    url = get_changelink_url(obj, admin_site)
     367    if url is None:
     368        return ''
     369    if show_name:
     370        text = _(u'edit %s' % force_unicode(obj))
     371    else:
     372        text = _('edit')
     373    return (u' <a href="%(url)s" class="related_field_changelink changelink">'
     374            '%(text)s</a>' % {'url': url, 'text': text})
  • django/contrib/admin/helpers.py

     
    22from django.conf import settings
    33from django.contrib.admin.util import flatten_fieldsets, lookup_field
    44from django.contrib.admin.util import display_for_field, label_for_field
     5from django.contrib.admin.util import get_related_object, get_changelink_url
    56from django.contrib.contenttypes.models import ContentType
    67from django.core.exceptions import ObjectDoesNotExist
    78from django.db.models.fields import FieldDoesNotExist
     
    186187                    result_repr = display_for_field(value, f)
    187188        return conditional_escape(result_repr)
    188189
     190    def change_url(self):
     191        """ Return admin change URL for field if applicable. """
     192        field, original, model_admin = \
     193            self.field['field'], self.form.instance, self.model_admin
     194        try:
     195            f, attr, value = lookup_field(field, original, model_admin)
     196        except (AttributeError, ValueError, ObjectDoesNotExist):
     197            return None
     198        return get_changelink_url(value, self.model_admin.admin_site)
     199
     200   
    189201class InlineAdminFormSet(object):
    190202    """
    191203    A wrapper around an inline formset for use in the admin system.
     
    247259        if original is not None:
    248260            self.original_content_type_id = ContentType.objects.get_for_model(original).pk
    249261        self.show_url = original and hasattr(original, 'get_absolute_url')
     262        if original is not None and model_admin is not None:
     263            self.admin_url = get_changelink_url(original,
     264                                                model_admin.admin_site)
     265        else:
     266            self.admin_url = None
    250267        super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields,
    251268            readonly_fields, model_admin)
    252269
  • django/contrib/admin/widgets.py

     
    1414from django.utils.encoding import force_unicode
    1515from django.conf import settings
    1616from django.core.urlresolvers import reverse, NoReverseMatch
     17from django.contrib.admin.util import get_related_object, get_changelink_html
    1718
    1819class FilteredSelectMultiple(forms.SelectMultiple):
    1920    """
     
    105106    A Widget for displaying ForeignKeys in the "raw_id" interface rather than
    106107    in a <select> box.
    107108    """
    108     def __init__(self, rel, attrs=None, using=None):
     109    def __init__(self, rel, attrs=None, using=None, admin_site=None):
    109110        self.rel = rel
    110111        self.db = using
     112        self.admin_site = admin_site
    111113        super(ForeignKeyRawIdWidget, self).__init__(attrs)
    112114
    113115    def render(self, name, value, attrs=None):
     
    129131        output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
    130132        if value:
    131133            output.append(self.label_for_value(value))
     134            output.append(get_changelink_html(
     135                    get_related_object(self.rel, value),
     136                    self.admin_site, show_name=False))
    132137        return mark_safe(u''.join(output))
    133138
    134139    def base_url_parameters(self):
     
    242247            output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
    243248                (related_url, name))
    244249            output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
     250
     251            output.append(get_changelink_html(
     252                    get_related_object(self.rel, value),
     253                    self.admin_site, show_name=True))
    245254        return mark_safe(u''.join(output))
    246255
    247256    def build_attrs(self, extra_attrs=None, **kwargs):
  • django/contrib/admin/templates/admin/edit_inline/stacked.html

     
    66
    77{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
    88  <h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span>
     9    {% if inline_admin_form.admin_url %}<a href="{{ inline_admin_form.admin_url }}" class="inline_changelink changelink">{% trans "edit separately" %}</a>&nbsp;&nbsp;{% endif %}
    910    {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
    1011    {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
    1112  </h3>
  • django/contrib/admin/templates/admin/edit_inline/tabular.html

     
    2525        <td class="original">
    2626          {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
    2727          {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
     28          {% if inline_admin_form.admin_url %}<a href="{{ inline_admin_form.admin_url }}" class="inline_changelink changelink">{% trans "edit separately" %}</a>&nbsp;&nbsp;{% endif %}
    2829          {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
    2930            </p>{% endif %}
    3031          {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
     
    4445            {% for field in line %}
    4546              <td class="{{ field.field.name }}">
    4647              {% if field.is_readonly %}
    47                   <p>{{ field.contents }}</p>
     48                  <p>{{ field.contents }}{% if field.change_url %} <a href="{{ field.change_url }}" class="readonly_changelink changelink">{% trans "edit" %}</a>{% endif %}</p>
    4849              {% else %}
    4950                  {{ field.field.errors.as_ul }}
    5051                  {{ field.field }}
  • django/contrib/admin/templates/admin/includes/fieldset.html

     
     1{% load i18n adminmedia %}
    12<fieldset class="module aligned {{ fieldset.classes }}">
    23    {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
    34    {% if fieldset.description %}
     
    1314                    {% else %}
    1415                        {{ field.label_tag }}
    1516                        {% if field.is_readonly %}
    16                             <p>{{ field.contents }}</p>
     17                            <p>{{ field.contents }}{% if field.change_url %} <a href="{{ field.change_url }}" class="readonly_changelink changelink">edit</a>{% endif %}</p>
    1718                        {% else %}
    1819                            {{ field.field }}
    1920                        {% endif %}
  • tests/regressiontests/admin_views/tests.py

     
    2424    FooAccount, Gallery, ModelWithStringPrimaryKey, \
    2525    Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
    2626    Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
    27     Category, Post, Plot, FunkyTag
     27    Category, Post, Plot, PlotDetails, FunkyTag, Villain, SecretHideout, \
     28    Ambush, Pizza, PizzaOrder, Topping, Donut, DonutOrder
    2829
    2930
    3031class AdminViewBasicTest(TestCase):
     
    21492150        self.assert_('password' not in adminform.form.errors)
    21502151        self.assertEquals(adminform.form.errors['password2'],
    21512152                          [u"The two password fields didn't match."])
     2153
     2154
     2155class RelatedLinksTest(TestCase):
     2156    fixtures = ['admin-views-users.xml']
     2157   
     2158    def setUp(self):
     2159        self.client.login(username='super', password='secret')
     2160        self.RELATED_LINK_CSS_CLASS = 'related_field_changelink'
     2161        self.READONLY_LINK_CSS_CLASS = 'readonly_changelink'
     2162        self.black_knight = Villain.objects.create(name='Black Knight')
     2163        self.plot = Plot.objects.create(name='None shall pass',
     2164                                        team_leader=self.black_knight,
     2165                                        contact=self.black_knight)
     2166        self.plot_details = PlotDetails.objects.create(
     2167            plot=self.plot, details="I'll bite your legs off!")
     2168        self.hideout = SecretHideout.objects.create(villain=self.black_knight,
     2169                                                    location='forest')
     2170        self.ambush = Ambush.objects.create(hideout=self.hideout,
     2171                                            when=datetime.datetime.now())
     2172
     2173        self.pizza = Pizza.objects.create(name='Wafer-thin pizza')
     2174        self.topping = Topping.objects.create(name='Mint')
     2175        self.pizza.toppings.add(self.topping)
     2176        self.donut = Donut.objects.create(name='Wafer-thin donut')
     2177        self.donut.toppings.add(self.topping)
     2178        self.donut_order = DonutOrder.objects.create(donut=self.donut,
     2179                                                     quantity=1000000)
     2180        self.pizza_order = PizzaOrder.objects.create(pizza=self.pizza,
     2181                                                     quantity=50)
     2182       
     2183    def tearDown(self):
     2184        self.client.logout()
     2185
     2186    def test_foreignkey(self):
     2187        """ Confirm changelink appears for populated ForeignKey fields """
     2188        response = self.client.get('/test_admin/admin/admin_views/plot/add/')
     2189        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2190        response = self.client.get('/test_admin/admin/admin_views/plot/%d/' %
     2191                                   self.plot.pk)
     2192        self.assertContains(response, self.RELATED_LINK_CSS_CLASS)
     2193        self.assertContains(response, '>edit %s</a>' % self.black_knight)
     2194
     2195    def test_onetoone(self):
     2196        """ Confirm changelink appears for populated OneToOne fields """
     2197        response = self.client.get(
     2198            '/test_admin/admin/admin_views/plotdetails/add/')
     2199        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2200        response = self.client.get(
     2201            '/test_admin/admin/admin_views/plotdetails/%d/' %
     2202            self.plot_details.pk)
     2203        self.assertContains(response, self.RELATED_LINK_CSS_CLASS)
     2204        self.assertContains(response, '>edit %s</a>' % self.plot)
     2205
     2206    def test_unregistered(self):
     2207        """ Confirm changelink does *not* appear for unregistered fields """
     2208        response = self.client.get(
     2209            '/test_admin/admin/admin_views/ambush/add/')
     2210        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2211        response = self.client.get(
     2212            '/test_admin/admin/admin_views/ambush/%d/' %
     2213            self.ambush.pk)
     2214        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)       
     2215       
     2216    def test_readonly(self):
     2217        """ Confirm changelink appears for populated readonly fields """
     2218        response = self.client.get(
     2219            '/test_admin/admin/admin_views/pizzaorder/add/')
     2220        self.assertNotContains(response, self.READONLY_LINK_CSS_CLASS)
     2221        response = self.client.get(
     2222            '/test_admin/admin/admin_views/pizzaorder/%d/' %
     2223            self.pizza_order.pk)
     2224        self.assertContains(response, self.READONLY_LINK_CSS_CLASS)
     2225        self.assertContains(response, '>edit</a>')       
     2226
     2227    def test_manytomany(self):
     2228        """ Confirm changelinks do *not* appear for ManyToMany fields """
     2229        MULTIPLE_SELECT_STRING = '<select multiple="multiple"'
     2230       
     2231        response = self.client.get(
     2232            '/test_admin/admin/admin_views/pizza/add/')
     2233        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2234        self.assertNotContains(response, MULTIPLE_SELECT_STRING)       
     2235        response = self.client.get(
     2236            '/test_admin/admin/admin_views/pizza/%d/' % self.pizza.pk)
     2237        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2238        self.assertNotContains(response, MULTIPLE_SELECT_STRING)       
     2239
     2240        # try non-readonly ManyToMany
     2241        response = self.client.get(
     2242            '/test_admin/admin/admin_views/donut/add/')
     2243        self.assertContains(response, MULTIPLE_SELECT_STRING)
     2244        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2245        response = self.client.get(
     2246            '/test_admin/admin/admin_views/donut/%d/' % self.donut.pk)
     2247        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2248        self.assertContains(response, MULTIPLE_SELECT_STRING)       
     2249
     2250    def test_raw_id(self):
     2251        """ Confirm links appears for populated raw_id_fields """
     2252        response = self.client.get(
     2253            '/test_admin/admin/admin_views/donutorder/add/')
     2254        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
     2255        response = self.client.get(
     2256            '/test_admin/admin/admin_views/donutorder/%d/' %
     2257            self.donut_order.pk)
     2258        self.assertContains(response, self.RELATED_LINK_CSS_CLASS)
     2259        self.assertContains(response, '>edit</a>')
  • tests/regressiontests/admin_views/models.py

     
    555555    def __unicode__(self):
    556556        return self.location
    557557
     558class Ambush(models.Model):
     559    hideout = models.ForeignKey(SecretHideout)
     560    when = models.DateTimeField()
     561
     562    def __unicode__(self):
     563        return u'Ambush %s at %s' % (self.hideout, self.when)
     564   
    558565class CyclicOne(models.Model):
    559566    name = models.CharField(max_length=25)
    560567    two = models.ForeignKey('CyclicTwo')
     
    579586class PizzaAdmin(admin.ModelAdmin):
    580587    readonly_fields = ('toppings',)
    581588
     589class Donut(models.Model):
     590    name = models.CharField(max_length=20)
     591    toppings = models.ManyToManyField('Topping')
     592
     593    def __unicode__(self):
     594        return self.name
     595
     596class DonutOrder(models.Model):
     597    donut = models.ForeignKey(Donut)
     598    quantity = models.IntegerField()
     599
     600    def __unicode__(self):
     601        return '%s x %d' % (self.donut, self.quantity)
     602   
     603class DonutOrderAdmin(admin.ModelAdmin):
     604    raw_id_fields = ('donut',)
     605
     606class PizzaOrder(models.Model):
     607    pizza = models.ForeignKey(Pizza)
     608    quantity = models.IntegerField()
     609
     610    def __unicode__(self):
     611        return '%s x %d' % (self.pizza, self.quantity)
     612   
     613class PizzaOrderAdmin(admin.ModelAdmin):
     614    readonly_fields = ('pizza',)
     615   
    582616admin.site.register(Article, ArticleAdmin)
    583617admin.site.register(CustomArticle, CustomArticleAdmin)
    584618admin.site.register(Section, save_as=True, inlines=[ArticleInline])
     
    606640admin.site.register(Gadget, GadgetAdmin)
    607641admin.site.register(Villain)
    608642admin.site.register(SuperVillain)
     643admin.site.register(Ambush)
    609644admin.site.register(Plot)
    610645admin.site.register(PlotDetails)
    611646admin.site.register(CyclicOne)
     
    624659admin.site.register(Promo)
    625660admin.site.register(ChapterXtra1)
    626661admin.site.register(Pizza, PizzaAdmin)
     662admin.site.register(PizzaOrder, PizzaOrderAdmin)
    627663admin.site.register(Topping)
     664admin.site.register(Donut)
     665admin.site.register(DonutOrder, DonutOrderAdmin)
     666
  • tests/regressiontests/admin_inlines/tests.py

     
    44from models import Holder, Inner, InnerInline
    55from models import Holder2, Inner2, Holder3, Inner3
    66from models import Person, OutfitItem, Fashionista
     7from models import Household, Individual, Phone, Email
    78
    89class TestInline(TestCase):
    910    fixtures = ['admin-views-users.xml']
     
    100101        response = self.client.get(change_url)
    101102        self.assertContains(response, 'my_awesome_admin_scripts.js')
    102103        self.assertContains(response, 'my_awesome_inline_scripts.js')
     104
     105
     106class TestAdminURL(TestCase):
     107    fixtures = ['admin-views-users.xml']
     108   
     109    def get_admin_url(self, obj_or_class, add=False):
     110        if add:
     111            obj_id = 'add'
     112            cls = obj_or_class
     113        else:
     114            obj_id = obj_or_class.pk
     115            cls = obj_or_class.__class__
     116        return '/test_admin/admin/admin_inlines/%s/%s/' % (
     117            cls.__name__.lower(), obj_id)
     118       
     119    def setUp(self):
     120        self.household = Household.objects.create()
     121        self.individual = Individual.objects.create(household=self.household)
     122        self.phone = Phone.objects.create(household=self.household,
     123                                          number='1234567890')
     124        self.email = Email.objects.create(individual=self.individual,
     125                                          email='me@example.com')
     126       
     127        result = self.client.login(username='super', password='secret')
     128        self.failUnlessEqual(result, True)
     129       
     130    def tearDown(self):
     131        self.client.logout()
     132
     133    def test_admin_url(self):
     134        """
     135        admin_url should be set for admin-registered inline models only.
     136
     137        Also check to ensure URLs look correct and only set on bound forms.
     138        """
     139        response = self.client.get(self.get_admin_url(self.household))
     140        for inline_admin_fset in response.context[-1]['inline_admin_formsets']:
     141            for inline_admin_form in inline_admin_fset:
     142                if inline_admin_form.form._meta.model != Individual:
     143                    self.assertFalse(
     144                        getattr(inline_admin_form, 'admin_url', None),
     145                        'admin_url set with unregistered model')
     146                elif not inline_admin_form.original:
     147                    self.assertFalse(
     148                        getattr(inline_admin_form, 'admin_url', None),
     149                        'admin_url set on unbound form!')
     150                else:
     151                    self.assertTrue(inline_admin_form.admin_url,
     152                                    'admin_url not set')
     153                    self.assertEqual(
     154                        inline_admin_form.original, self.individual,
     155                        'original is not expected object')
     156                    self.assertEqual(
     157                        inline_admin_form.admin_url,
     158                        self.get_admin_url(inline_admin_form.original),
     159                        'admin_url appears incorrect')
     160
     161    def test_link_rendering(self):
     162        """ Confirm links are displayed where appropriate. """
     163        LINK_CSS_CLASS = 'inline_changelink'
     164        LINK_TEXT = 'edit separately'
     165
     166        # test StackedInline rendering
     167       
     168        response = self.client.get(self.get_admin_url(Household, 'add'))
     169        self.assertNotContains(response, LINK_CSS_CLASS)
     170        self.assertNotContains(response, LINK_TEXT)
     171       
     172        response = self.client.get(self.get_admin_url(self.household))
     173        self.assertContains(response, LINK_CSS_CLASS)
     174        self.assertContains(response, LINK_TEXT)
     175
     176        # test TabularInline rendering
     177
     178        response = self.client.get(self.get_admin_url(Individual, 'add'))
     179        self.assertNotContains(response, LINK_CSS_CLASS)
     180        self.assertNotContains(response, LINK_TEXT)
     181       
     182        response = self.client.get(self.get_admin_url(self.individual))
     183        self.assertContains(response, LINK_CSS_CLASS)
     184        self.assertContains(response, LINK_TEXT)       
  • tests/regressiontests/admin_inlines/models.py

     
    145145
    146146"""
    147147}
     148
     149# Test InlineAdminForm.admin_url:
     150
     151class Household(models.Model):
     152    pass
     153
     154class Individual(models.Model):
     155    household = models.ForeignKey(Household)
     156
     157class Phone(models.Model):
     158    household = models.ForeignKey(Household)
     159    number = models.CharField(max_length=64)
     160
     161class Email(models.Model):
     162    individual = models.ForeignKey(Individual)
     163    email = models.EmailField()
     164
     165class IndividualInline(admin.StackedInline):
     166    model = Individual
     167    extra = 1
     168
     169class PhoneInline(admin.StackedInline):
     170    model = Phone
     171    extra = 1
     172
     173class EmailInline(admin.TabularInline):
     174    model = Email
     175    extra = 1   
     176   
     177admin.site.register(Household, inlines=[IndividualInline, PhoneInline])
     178admin.site.register(Individual, inlines=[EmailInline])
     179admin.site.register(Email)
     180# (Phone not registered)
     181
     182
  • AUTHORS

     
    325325    mccutchen@gmail.com
    326326    Paul McLanahan <paul@mclanahan.net>
    327327    Tobias McNulty <http://www.caktusgroup.com/blog>
     328    Simon Meers <http://simonmeers.com/>
    328329    Zain Memon
    329330    Christian Metts
    330331    michael.mcewan@gmail.com
Back to Top