在Django管理界面中删除"添加另一个"

Jac*_* Ha 27 python django django-admin

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

我配置了一个没有添加对象B权限的用户.加号仍然可用,但是当我点击它时,它会显示"权限被拒绝".它很丑.

我正在使用Django 1.0.2

pis*_*che 49

(停止支持这个错误的答案!)

ERRATA:这个答案基本上是错误的,并没有回答OP的问题.见下文.

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

更简单的解决方案,没有CSS黑客,没有编辑Django代码库:

将其添加到您的Inline类:

max_num=0

UPDATE

这不符合OP的问题,只对隐藏内联表单的"添加相关"按钮,而不是按要求隐藏外键.

当我写这个答案时,IIRC接受的答案都隐藏了,这就是为什么我感到困惑.

以下链接似乎提供了一个解决方案(虽然使用CSS隐藏似乎是最可行的事情,特别是如果在内联表单中添加FK的"另一个"按钮):

Django 1.7从内联表单中删除"添加"按钮

  • 你能提供更多信息吗?可能还有一个例子吗?这似乎不能解决OP的问题.您可以使用InlineAdmin将A内联到B的管理页面.OP希望阻止从A的管理页面创建B对象.在文档中,您可以看到Book在作者上有ForeignKey,但是你内联Book in Author,而不是作者书.https://docs.djangoproject.com/en/1.1/ref/contrib/admin/#inlinemodeladmin-objects (5认同)
  • 这不是OP问题的答案. (4认同)
  • 最好的答案! (3认同)
  • @pistache这不是OP问题的答案,因为在他的情况下,`A`有一个'B`的外键,所以一个'A`对象可以****对'B`的引用,他在*A*的形式中引用*B*字段旁边的**+**.基本上,如果您参考其他[问题](http://stackoverflow.com/q/26425818/1027706)中的图像,OP想要删除**#1**红色圆圈中的加号,而您的回答删除#3或#4. (2认同)

Sud*_*pta 25

虽然这里提到的大部分解决方案都有效,但还有另一种更清洁的方法.在提出其他解决方案之后,可能是在Django的更高版本中引入的.(我现在正在使用Django 1.7)

要删除"添加其他"选项,

class ... #(Your inline class)

    def has_add_permission(self, request):
        return False
Run Code Online (Sandbox Code Playgroud)

同样,如果要禁用"删除?" 选项,在Inline类中添加以下方法.

    def has_delete_permission(self, request, obj=None):
        return False
Run Code Online (Sandbox Code Playgroud)

  • 在大多数情况下,这不是正确的解决方案——它不仅从 ForeignKey 字段中删除了小的 `add`、`delete` 符号,而且还从所有位置删除了链接模型的添加/编辑选项。管理界面。 (2认同)
  • 我认为这是正确的,因为它是“InlineModelAdmin”(而不是“ModelAdmin”)的子类,并且需要显式添加到其他“ModelAdmin”的“内联”。让我修复需要 `obj` 参数的 `has_add_permission` (`def has_add_permission(self, request, obj):`)。 (2认同)

End*_*age 13

NB适用于DJango 1.5.2且可能更老.该can_add_related物业出现在大约2年前.

我发现的最好方法是覆盖ModelAdmin的get_form函数.在我的情况下,我想强迫帖子的作者成为当前登录的用户.代码如下,附有大量评论.真正重要的是设置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
Run Code Online (Sandbox Code Playgroud)

在这里进行更改的有趣部分get_form是,这author.widget是一个实例,django.contrib.admin.widgets.RelatedFieldWidgetWrapper如果您尝试在其中一个formfield_for_xxxxx函数中进行更改,则窗口小部件是实际窗体窗口小部件的实例,在此典型的ForeignKey情况下,它是a django.forms.widgets.Select.


djv*_*jvg 9

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

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

为什么使用formfield_for_dbfield?它的文档字符串表明它是用于处理表单字段的指定钩子:

用于为给定数据库 Field 实例指定表单 Field 实例的挂钩。

它还允许(稍微)更清晰和更清晰的代码,作为奖励,我们可以通过将它们添加到(在调用之前)轻松设置额外的表单Field 属性,例如initial值和/或disabled此处的示例)。kwargssuper

因此,结合两个答案(假设 OP 的模型是ModelAand ModelB,并且ForeignKey模型字段名为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)
Run Code Online (Sandbox Code Playgroud)

注意:如果ForeignKey模型字段on_delete=models.CASCADE,该can_delete_related属性是False在默认情况下,如能在可以看到RelatedFieldWidgetWrapper


cet*_*eek 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)
Run Code Online (Sandbox Code Playgroud)

我认为最好的选择是创建子类ModelAdmin(后者又是其子类BaseModelAdmin),将模型基于该新类,重写formfield_fo_dbfield并使其成为不会/或将有条件地包装小部件RelatedFieldWidgetWrapper.

有人可能会说,如果您的用户无权添加相关对象,则RelatedFieldWidgetWrapper不应显示添加链接?也许这是Django trac值得一提的东西?

  • +1这是最好的答案.这不是一个CSS黑客,它不会破坏其他情况下小部件的功能.您可以仅为您想要的ModelAdmin创建子类,同时保留其他模型. (2认同)

Sli*_*eam 5

我对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  
Run Code Online (Sandbox Code Playgroud)

内联表格

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
Run Code Online (Sandbox Code Playgroud)


T. *_*one 4

已弃用的答案

Django 使这一切成为可能。


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

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

no-addanother-button.css

#_addanother { display: none }
Run Code Online (Sandbox Code Playgroud)

admin.py

class YourAdmin(admin.ModelAdmin):
    # ...
    class Media:
        # edit this path to wherever
        css = { 'all' : ('css/no-addanother-button.css',) }
Run Code Online (Sandbox Code Playgroud)

用于执行此操作的 Django Doc——媒体作为静态定义

注意/编辑:文档说文件将以 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',) }
Run Code Online (Sandbox Code Playgroud)

  • 不需要肮脏的黑客,请参阅下面 pistache 的答案 (2认同)