1

Given following admin settings:

class BrokerLocationSetForm(forms.ModelForm):
    class Meta:
        model = BrokerLocationSet
        fields = ('broker', 'program', 'label', 'locations')
        widgets = {
            'locations': autocomplete.ModelSelect2Multiple(url='admin-autocomplete-location', forward=('broker','program')),
        }
class BrokerLocationSetAdmin(admin.ModelAdmin):
    model = BrokerLocationSet
    form = BrokerLocationSetForm
    list_display=['broker', 'program', 'label']
admin.site.register(BrokerLocationSet, BrokerLocationSetAdmin)

When I try navigate to add view in admin for BrokerLocationSetForm it raises following error:

raise NoReverseMatch(msg) NoReverseMatch: Reverse for 'program_program_change' with arguments '(u'__fk__',)' not found. 1 pattern(s) tried: [u'admin/program/program/(?P<program_pk>\\d+)/change/$']

When I debug in shell:

 reverse('admin:broker_broker_change', 'myapp.urls', args=(u'__fk__',))

it outputs:

u'/admin/broker/broker/fk/change/'

but for:

reverse('admin:program_program_change', 'myapp.urls', args=(u'__fk__',))

I get same error as above. After some debugging I sensed that somehow admin was passing a string instead of an int into reverse function while it expected an integer as below :

reverse('admin:program_program_change', 'myapp.urls', args=(u'1',))

u'/admin/program/program/1/change/'

Since django admin does this url reversing magic I am not sure where I should customize this to fix the bug. I have got this code base fairly new and to get sense completely.

How I can fix above bug by customizing admin model or form. I dont want to update 'admin:program_program_change' but probably provide an alternate route to same view! . Is it possible ? please advise !

sakhunzai
  • 13,900
  • 23
  • 98
  • 159

1 Answers1

0

I found a solution however, I am not sure if this is best one. Since ProgramAdmin expects a numeric parameter while popup link from BrokerLocationSetAdmin is expecting a route with a string parameter. e.g

reverse('admin:program_program_change', 'myapp.urls', args=(u'__fk__',))

Solution was to inject another admin route with same name to ProgramAdmin model by overriding its get_urls method as follow:

class ProgramAdmin(admin.ModelAdmin):
   ...
   ...
   def get_urls(self):
        from django.conf.urls import url
        from functools import update_wrapper

        def wrap(view):
            def wrapper(*args, **kwargs):
                return self.admin_site.admin_view(view)(*args, **kwargs)
            wrapper.model_admin = self
            return update_wrapper(wrapper, view)

        urls = super(ProgramAdmin, self).get_urls()
        info = self.model._meta.app_label, self.model._meta.model_name
        alt_urls=[
            url(r'^(?P<program_pk>\w+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
        ]
        return urls+alt_urls

Now we have two routes with same name but different paths parameter e.g:

/admin/program/program/<program_pk>/change/ django.contrib.admin.options.change_view    admin:program_program_change

admin/program/program/(?P\d+)/change/$

/admin/program/program/<program_pk>/change/ django.contrib.admin.options.change_view    admin:program_program_change

admin/program/program/(?P\w+)/change/$

Depending on the context one of route will be used.

sakhunzai
  • 13,900
  • 23
  • 98
  • 159