自定义 Django Admin 更改表单外键以包含视图记录

发布于 2024-12-06 17:58:04 字数 1732 浏览 0 评论 0原文

在 django 管理更改表单中选择外键时,我尝试添加一个 href,可以查看添加记录的加号旁边的记录。

我只是为了获取要渲染的 href 而尝试将 admins def render 复制到我自己的自定义小部件文件中,并将其添加到并对其进行子类化:

widgets.py

class RelatedFieldWidgetWrapperLink(RelatedFieldWidgetWrapper):

    def render(self, name, value, *args, **kwargs):
        rel_to = self.rel.to
        info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
        try:
            related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
        except NoReverseMatch:
            info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower())
            related_url = '%s%s/%s/add/' % info
        self.widget.choices = self.choices
        output = [self.widget.render(name, value, *args, **kwargs)]
        if self.can_add_related:
            # TODO: "id_" is hard-coded here. This should instead use the correct
            # API to determine the ID dynamically.
            output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
                (related_url, name))
            output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
            output.append(u'<a href="%s" class="testing" id="add_id_%s" onclick="#"> ' % \        
                        (related_url, name))
        return mark_safe(u''.join(output))

和 admin.py

formfield_overrides = {models.ForeignKey:{'widget':RelatedFieldWidgetWrapperLink}}

但是我收到以下错误:

类型错误 init() 至少需要 4 个参数(给定 1 个)

以前有人遇到过这个问题吗?

When selecting a foreignkey in the django admin change form I am trying to add an href that can view the record next to the plus that adds the record.

What I've tried just to get the href to render is I've copied out the admins def render into my own custom widgets file and added it to and subclassed it:

widgets.py

class RelatedFieldWidgetWrapperLink(RelatedFieldWidgetWrapper):

    def render(self, name, value, *args, **kwargs):
        rel_to = self.rel.to
        info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
        try:
            related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
        except NoReverseMatch:
            info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower())
            related_url = '%s%s/%s/add/' % info
        self.widget.choices = self.choices
        output = [self.widget.render(name, value, *args, **kwargs)]
        if self.can_add_related:
            # TODO: "id_" is hard-coded here. This should instead use the correct
            # API to determine the ID dynamically.
            output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
                (related_url, name))
            output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
            output.append(u'<a href="%s" class="testing" id="add_id_%s" onclick="#"> ' % \        
                        (related_url, name))
        return mark_safe(u''.join(output))

and in admin.py

formfield_overrides = {models.ForeignKey:{'widget':RelatedFieldWidgetWrapperLink}}

however I get thefollowing error:

TypeError
init() takes at least 4 arguments (1 given)

Has anyone run into this problem before?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

把人绕傻吧 2024-12-13 17:58:04

RelatedFieldWidgetWrapper 小部件和您的子类不适合用作 formfield_overrides 中的小部件。 __init__ 方法具有不同的函数签名,因此会出现 TypeError

如果您查看 django.contrib.admin.options< 中的代码/code>,可以看到在模型管理的formfield_for_dbfield方法中实例化了RelatedFieldWidgetWrapper小部件,这样它就可以传递参数 reladmin_sitecan_add_lated

我认为您可能必须重写模型管理类的 formfield_for_dbfield 方法,并在那里使用自定义的 RelatedFieldWidgetWrapperLink 小部件。

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>

其他方法

您可能会发现重写 formfield_for_foreignkey 方法比 formfield_for_dbfield 更清晰。

您可以对 Select 小部件进行子类化,并在其渲染方法中添加链接。然后,您的自定义选择小部件将由 RelatedFieldWidgetWrapper 包装。但是,我不确定您是否可以在 render 方法的范围内生成 view_url

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}}

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}}
铁轨上的流浪者 2024-12-13 17:58:04

我稍微改进了 @Alasdair 解决方案:

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}}

它使用与 RelatedFieldWidgetWrapper 相同的代码结构和样式。此外,它使用“更改”图标而不仅仅是字符串。当外键无处指向或外键指向未定义管理界面的模型时,它会优雅地生存。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文