9
The RelatedFieldWidgetWrapper
widget, and your subclass, are not meant to be used as the widget in formfield_overrides
. The __init__
methods have different function signatures, hence the TypeError
.
If you look at the code in django.contrib.admin.options
, you can see that the RelatedFieldWidgetWrapper
widget is instantiated in the model admin’s formfield_for_dbfield
method, so that it can be passed the arguments rel
, admin_site
and can_add_related
.
I think you may have to override your model admin class’ formfield_for_dbfield
method, and use your custom RelatedFieldWidgetWrapperLink
widget there.
class YourModelAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
<snip>
# ForeignKey or ManyToManyFields
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
# Combine the field kwargs with any options for formfield_overrides.
# Make sure the passed in **kwargs override anything in
# formfield_overrides because **kwargs is more specific, and should
# always win.
if db_field.__class__ in self.formfield_overrides:
kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
# Get the correct formfield.
if isinstance(db_field, models.ForeignKey):
formfield = self.formfield_for_foreignkey(db_field, request, **kwargs)
elif isinstance(db_field, models.ManyToManyField):
formfield = self.formfield_for_manytomany(db_field, request, **kwargs)
# For non-raw_id fields, wrap the widget with a wrapper that adds
# extra HTML -- the "add other" interface -- to the end of the
# rendered output. formfield can be None if it came from a
# OneToOneField with parent_link=True or a M2M intermediary.
if formfield and db_field.name not in self.raw_id_fields:
related_modeladmin = self.admin_site._registry.get(
db_field.rel.to)
can_add_related = bool(related_modeladmin and
related_modeladmin.has_add_permission(request))
# use your custom widget
formfield.widget = RelatedFieldWidgetWrapperLink(
formfield.widget, db_field.rel, self.admin_site,
can_add_related=can_add_related)
return formfield
<snip>
Other approaches
You may find it cleaner to override the formfield_for_foreignkey
method than formfield_for_dbfield
.
You may be able to subclass the Select
widget, and add your link in it’s render method. Your custom select widget would then be wrapped by the RelatedFieldWidgetWrapper
. However, I am not sure whether you can produce the view_url
inside the scope of the render
method.
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.forms.widgets import Select
def get_admin_change_url(obj):
ct = ContentType.objects.get_for_model(obj)
change_url_name = 'admin:%s_%s_change' % (ct.app_label, ct.model)
return reverse(change_url_name, args=(obj.id,))
class LinkedSelect(Select):
def render(self, name, value, attrs=None, *args, **kwargs):
output = super(LinkedSelect, self).render(name, value, attrs=attrs, *args, **kwargs)
model = self.choices.field.queryset.model
try:
id = int(value)
obj = model.objects.get(id=id)
view_url = get_admin_change_url(obj)
output += mark_safe(' <a href="%s" target="_blank">view</a> ' % (view_url,))
except model.DoesNotExist:
pass
return output
class YourModelAdmin(admin.ModelAdmin):
formfield_overrides = {models.ForeignKey:{'widget':LinkedSelect}}
0
I improved @Alasdair solution a bit:
from django.contrib.admin.templatetags import admin_static
from django.core import urlresolvers
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
class LinkedSelect(widgets.Select):
def render(self, name, value, attrs=None, *args, **kwargs):
output = [super(LinkedSelect, self).render(name, value, attrs=attrs, *args, **kwargs)]
model = self.choices.field.queryset.model
try:
obj = model.objects.get(id=value)
change_url = urlresolvers.reverse('admin:%s_%s_change' % (obj._meta.app_label, obj._meta.object_name.lower()), args=(obj.pk,))
output.append(u'<a href="%s" class="change-object" id="change_id_%s"> ' % (change_url, name))
output.append(u'<img src="%s" width="10" height="10" alt="%s"/></a>' % (admin_static.static('admin/img/icon_changelink.gif'), _('Change Object')))
except (model.DoesNotExist, urlresolvers.NoReverseMatch):
pass
return safestring.mark_safe(u''.join(output))
class YourModelAdmin(admin.ModelAdmin):
formfield_overrides = {models.ForeignKey: {'widget': LinkedSelect}}
It uses the same code structure and style as RelatedFieldWidgetWrapper
. Additionally, it uses “change” icon instead of just string. It gracefully survives when foreign key points nowhere or where foreign key points to a model which does not have admin interface defined.
- [Django]-Upgrading to django 1.11 many to many column not found
- [Django]-Django rest framework custom Response middleware
- [Django]-Creating a User Registration Page using MongoEngine