在 Django 管理屏幕中删除“添加另一个”

Posted

技术标签:

【中文标题】在 Django 管理屏幕中删除“添加另一个”【英文标题】:Remove "add another" in Django admin screen 【发布时间】:2010-12-15 19:34:57 【问题描述】:

每当我使用对象 B 的外键编辑对象 A 时,对象 B 的选项旁边都会出现一个加号选项“添加另一个”。如何删除该选项?

我配置了一个无权添加对象 B 的用户。加号仍然可用,但是当我单击它时,它显示“权限被拒绝”。太丑了。

我正在使用 Django 1.0.2

【问题讨论】:

相关(但不同)问题:***.com/questions/4143886/… 现在可以了! max_num=0 @andi 此解决方案类似于 pistache 的答案,不适用于 OP 的情况(有关详细信息,请参阅我对 pistache 答案的评论)。这个问题类似于这个相关问题的#1红圈:***.com/q/26425818/1027706 见 ModelAdmin.has_add_permission(request): docs.djangoproject.com/en/dev/ref/contrib/admin/… 【参考方案1】:

以下答案是我原来的答案,但它是错误的,并没有回答 OP 的问题:

更简单的解决方案,无需 CSS hack,无需编辑 Django 代码库:

将此添加到您的内联类:

max_num=0

(这仅适用于内联表单,不适用于 OP 要求的外键字段)


上述答案仅对隐藏内联表单的“添加相关”按钮有用,而不是按要求隐藏外键。

当我写答案时,IIRC 接受的答案将两者都隐藏了,这就是我感到困惑的原因。

以下似乎提供了一种解决方案(尽管使用 CSS 隐藏似乎是最可行的做法,特别是如果 FK 的“添加另一个”按钮采用内联形式):

Django 1.7 removing Add button from inline form

【讨论】:

您能否提供更多信息并可能提供示例?这似乎不能解决 OP 的问题。您将使用 InlineAdmin 将 A 内联到 B 的管理页面中。 OP 希望阻止从 A 的管理页面创建 B 对象。在文档中,您可以看到 Book 在 Author 上有 ForeignKey,但是您将 Book 内联到 Author,而不是 Author 到 Book。 docs.djangoproject.com/en/1.1/ref/contrib/admin/… 这不是 OP 问题的真正答案。 为什么?至少在我写它的时候,它确实删除了内联表单中的“添加另一个”链接。 @pistache 这不是 OP 问题的答案,因为在他的情况下,A 有一个到 B 的外键,所以 A 对象可以有 one 引用B,他指的是A 表单中B 字段旁边的+。基本上,如果您在其他 question 中引用图像,则 OP 想要删除 #1 红色圆圈中的加号,而您的答案会删除 #3 或 #4。 是的,你说得很好,这个答案完全是废话,与 OP 的要求相去甚远。这是很久以前写的,我想我对Django的表单的理解并没有我当时想的那么好。很抱歉造成混乱。【参考方案2】:

虽然这里提到的大多数解决方案都有效,但还有另一种更简洁的方法。可能在其他解决方案出现之后,它是在更高版本的 Django 中引入的。 (我目前使用的是 Django 1.7)

要删除“添加另一个”选项,

class ... #(Your inline class)

    def has_add_permission(self, request):
        return False

同样,如果您想禁用“删除?”选项,在 Inline 类中添加以下方法。

    def has_delete_permission(self, request, obj=None):
        return False

【讨论】:

在大多数情况下,这不是正确的解决方案——它不仅删除了 ForeignKey 字段中的小 adddelete 符号,而且还删除了管理界面中所有位置的链接模型。 我认为这是正确的,因为它是InlineModelAdmin(不是ModelAdmin)的子类,需要明确添加到其他ModelAdmininlines。让我修复需要obj 参数的has_add_permission (def has_add_permission(self, request, obj):)。【参考方案3】:

注意适用于 DJango 1.5.2 和可能更早的版本。 can_add_related 属性 appeared 大约 2 年前。

我发现最好的方法是覆盖 ModelAdmin 的 get_form 函数。就我而言,我想强制帖子的作者成为当前登录的用户。下面的代码带有大量的 cmets。真正重要的是widget.can_add_related的设置:

def get_form(self,request, obj=None, **kwargs):
    # get base form object    
    form = super(BlogPostAdmin,self).get_form(request, obj, **kwargs)

    # get the foreign key field I want to restrict
    author = form.base_fields["author"]

    # remove the green + by setting can_add_related to False on the widget
    author.widget.can_add_related = False

    # restrict queryset for field to just the current user
    author.queryset = User.objects.filter(pk=request.user.pk)

    # set the initial value of the field to current user. Redundant as there will
    # only be one option anyway.
    author.initial = request.user.pk

    # set the field's empty_label to None to remove the "------" null 
    # field from the select. 
    author.empty_label = None

    # return our now modified form.
    return form

get_form 中进行更改的有趣部分是author.widgetdjango.contrib.admin.widgets.RelatedFieldWidgetWrapper 的一个实例,如果您尝试在其中一个formfield_for_xxxxx 函数中进行更改,小部件是一个实例实际的表单小部件,在这个典型的 ForeignKey 案例中,它是 django.forms.widgets.Select

【讨论】:

你知道有一种方法可以为InlineModelAdmin 工作吗? 抱歉,从未尝试过 InlineModelAdmin。不过我希望它是相似的,我只是不确定如何覆盖InlineModelAdmin,而且自从发布后不久我还没有写过一行 Django 特定的 Python。 @Ad N 检查下面的 InlineModelAdmin 答案。【参考方案4】:

我对 FormInlineForm

使用以下方法

Django 2.0、Python 3+

表格

class MyModelAdmin(admin.ModelAdmin):
    #...
    def get_form(self,request, obj=None, **kwargs):

        form = super().get_form(request, obj, **kwargs)
        user = form.base_fields["user"]

        user.widget.can_add_related = False
        user.widget.can_delete_related = False
        user.widget.can_change_related = False

        return form  

内联表单

class MyModelInline(admin.TabularInline):
    #...
    def get_formset(self, request, obj=None, **kwargs):

        formset = super().get_formset(request, obj, **kwargs)
        user = formset.form.base_fields['user']

        user.widget.can_add_related = False
        user.widget.can_delete_related = False
        user.widget.can_change_related = False

        return formset

【讨论】:

【参考方案5】:

@Slipstream 的回答显示了如何 实施解决方案,即。通过覆盖表单域小部件的属性,但在我看来,get_form 并不是最合乎逻辑的地方。

@cethegeek 的答案显示了 where 来实施解决方案,即。在formfield_for_dbfield 的扩展中,但没有提供明确的示例。

为什么使用formfield_for_dbfield?它的docstring 表明它是用于弄乱表单字段的指定钩子:

用于指定给定数据库字段实例的表单字段实例的钩子。

它还允许(稍微)更清晰的代码,并且,作为奖励,我们可以轻松设置其他形式 Field attributes,例如 initial 值和/或 disabled(例如 @987654325 @),将它们添加到 kwargs(在调用 super 之前)。

所以,结合这两个答案(假设 OP 的模型是 ModelAModelBForeignKey 模型字段被命名为 b):

class ModelAAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, request, **kwargs):
        # optionally set Field attributes here, by adding them to kwargs
        formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
        if db_field.name == 'b':
            formfield.widget.can_add_related = False
            formfield.widget.can_change_related = False
            formfield.widget.can_delete_related = False
        return formfield

# Don't forget to register...
admin.site.register(ModelA, ModelAAdmin)

注意:如果ForeignKey 模型字段具有on_delete=models.CASCADE,则can_delete_related 属性默认为False,如source 中的RelatedFieldWidgetWrapper 所示。

【讨论】:

【参考方案6】:

查看django.contrib.admin.options.py 并查看BaseModelAdmin 类,formfield_for_dbfield 方法。

你会看到这个:

# 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:
    formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)

我认为你最好的选择是创建ModelAdmin 的子类(它又是BaseModelAdmin 的子类),将你的模型基于该新类,覆盖formfield_fo_dbfield 并使其不会/或将有条件地将小部件包装在 RelatedFieldWidgetWrapper 中。

有人可能会争辩说,如果您的用户无权添加相关对象,RelatedFieldWidgetWrapper 不应该显示添加链接吗?也许这在Django trac 中值得一提?

【讨论】:

+1 这是最好的答案。这不是 CSS hack,也不会破坏小部件在其他情况下的功能。您可以只为您想要的 ModelAdmin 进行子类化,同时不理会其他人。【参考方案7】:

弃用的答案

Django 让这成为可能。


您是否考虑过使用 CSS 来简单地不显示按钮?也许这有点太老套了。

这是未经测试的,但我在想......

no-addanother-button.css

#_addanother  display: none 

admin.py

class YourAdmin(admin.ModelAdmin):
    # ...
    class Media:
        # edit this path to wherever
        css =  'all' : ('css/no-addanother-button.css',) 

Django Doc 用于执行此操作 -- Media as a static definition

注意/编辑:文档说这些文件将带有 MEDIA_URL,但在我的实验中它不是。您的里程可能会有所不同。

如果您发现这种情况适合您,可以快速解决此问题...

class YourAdmin(admin.ModelAdmin):
    # ...
    class Media:
        from django.conf import settings
        media_url = getattr(settings, 'MEDIA_URL', '/media/')
        # edit this path to wherever
        css =  'all' : (media_url+'css/no-addanother-button.css',) 

【讨论】:

不需要肮脏的破解,请参阅下面 pistache 的答案 @ThomasParslow 我添加了一个粗体注释,因为我的答案现在已经过时了。 @T.Stone 如果您仔细阅读 OP 的问题并查看内联类的文档,您会发现 pistache 的答案并没有真正解决问题。我已经添加了一个答案。 @Endophage 你说得对,它不适用于 OP 的情况(请参阅我对 pistache 答案的评论)。 您可以在下面查看我的答案以获取 Form 和 InlineForm 解决方案。【参考方案8】:

我正在使用 Django 2.x,我认为我找到了最好的解决方案,至少对我来说是这样。

“保存并添加另一个”按钮的 HTML 文件位于 your_python_installation\Lib\site-packages\django\contrib\admin\templates\admin\subtmit_line.html

    复制该 html 文件并粘贴到您的项目中,如下所示 your_project\templates\admin\submit_line.html。 打开它并根据需要注释/删除按钮代码:

#% if show_save_and_add_another %<input type="submit" value="% trans 'Save and add another' %" name="_addanother" />% endif %#

我知道这个问题已经得到解答。不过说不定以后有人跟我有类似的情况。

【讨论】:

【参考方案9】:

根据 cethegeek 的回答我做了这个:

class SomeAdmin(admin.ModelAdmin):
    form = SomeForm

    def formfield_for_dbfield(self, db_field, **kwargs):
        formfield = super(SomeAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        if db_field.name == 'some_m2m_field':
            request = kwargs.pop("request", None)
            formfield = self.formfield_for_manytomany(db_field, request, **kwargs)  # for foreignkey: .formfield_for_foreignkey
            wrapper_kwargs = 'can_add_related': False, 'can_change_related': False, 'can_delete_related': False
            formfield.widget = admin.widgets.RelatedFieldWidgetWrapper(
                formfield.widget, db_field.remote_field, self.admin_site, **wrapper_kwargs
            )
        return formfield

【讨论】:

您能解释一下为什么/如何它是一个更好的解决方案吗?只是看起来更复杂【参考方案10】:

我根据 django 文档修复类似情况的方式

https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.extra

解决方案的结果是它允许您仅为该实例添加内联。或者换一种说法:添加一个内联和一个;没有其他按钮。

#models.py
class Model_A(models.Model):
    ...    

class Model_B(models.Model):
    ...
    relevant_field = models.ForeignKey(Model_A, related_name='Model_B_relevant_field')

# forms.py or someotherfile.py
from django.contrib.admin import StackedInline, TabularInline
    
class Model_B_Inline(StackedInline):
    verbose_name = 'Some Name'
    ...

    def get_extra(self, request, obj=None, *args, **kwargs):
        the_extra = super().get_extra(request, obj=obj, *args, **kwargs)
        self.extra = 1
        if obj:
            the_counter = obj.Model_B_relevant_field.count()
        else:
            the_counter = -1
        self.max_num = the_counter + 1
        return the_extra
    

【讨论】:

【参考方案11】:

django.contrib.admin.widgets.py

(Django 安装目录)/django/contrib/admin/widgets.py:注释第 239 行和第 244 行之间的所有内容:

 if rel_to in self.admin_site._registry: # If the related object has an admin interface:
        # 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"   /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))

【讨论】:

嗨,这不会删除所有链接吗?无论如何,这个问题已经很老了,在上面的建议中已经找到了解决方案。无论如何谢谢:)

以上是关于在 Django 管理屏幕中删除“添加另一个”的主要内容,如果未能解决你的问题,请参考以下文章

Django 管理模型 add_view:如何删除“保存并添加另一个”按钮?

对于 django 模型,如何获取 django 管理 URL 以添加另一个或列出对象等?

NoReverseMatch 在渲染 Django 管理屏幕时?

Django 管理屏幕不按名称显示实体

扩展 Django 管理员删除确认页面

如何在 Django 管理员中删除特定模型的添加按钮?