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

发布于 2024-08-11 03:31:44 字数 150 浏览 20 评论 0 原文

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

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

我正在使用 Django 1.0.2

Whenever I'm editing object A with a foreign key to object B, a plus option "add another" is available next to the choices of object B. How do I remove that option?

I configured a user without rights to add object B. The plus sign is still available, but when I click on it, it says "Permission denied". It's ugly.

I'm using Django 1.0.2

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

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

发布评论

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

评论(12

泛滥成性 2024-08-18 03:31:44

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

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

将其添加到您的内联类中:

<前><代码>max_num=0

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


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

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

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

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

The following answer was my original answer but it is wrong and does not answer OP's question:

Simpler solution, no CSS hack and no editing Django codebase:

Add this to your Inline class:

max_num=0

(this is only applicable to inline forms, not foreign key fields as OP asked)


The above answer is only useful to hide the "add related" button for inline forms, and not foreign keys as requested.

When I wrote the answer, IIRC the accepted answer hid both, which is why I got confused.

The following seems to provide a solution (though hiding using CSS seems the most feasible thing to do, especially if the "add another" buttons of FKs are in inline forms):

Django 1.7 removing Add button from inline form

若言繁花未落 2024-08-18 03:31:44

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

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

class ... #(Your inline class)

    def has_add_permission(self, request):
        return False

类似地,如果您想禁用“删除?”选项,在内联类中添加以下方法。

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

Though most of the solutions mentioned here work, there is another cleaner way of doing it. Probably it was introduced in a later version of Django, after the other solutions were presented. (I'm presently using Django 1.7)

To remove the "Add another" option,

class ... #(Your inline class)

    def has_add_permission(self, request):
        return False

Similarly if you want to disable "Delete?" option, add the following method in Inline class.

    def has_delete_permission(self, request, obj=None):
        return False
李白 2024-08-18 03:31:44

注意适用于 DJango 1.5.2 及可能更旧的版本。 can_add_lated 属性出现大约在 2 年前。

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

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.widget 是一个django.contrib.admin.widgets.RelatedFieldWidgetWrapper 的实例,就像您尝试在 formfield_for_xxxxx 函数之一中进行更改一样,该小部件是实际表单小部件的实例,在这个典型的ForeignKey案例中,它是一个django.forms.widgets.Select

N.B. Works for DJango 1.5.2 and possibly older. The can_add_related property appeared around 2 years ago.

The best way I've found is to override your ModelAdmin's get_form function. In my case I wanted to force the author of a post to be the currently logged in user. Code below with copious comments. The really important bit is the setting of 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

The interesting part of making the changes here in get_form is that author.widget is an instance of django.contrib.admin.widgets.RelatedFieldWidgetWrapper where as if you try and make changes in one of the formfield_for_xxxxx functions, the widget is an instance of the actual form widget, in this typical ForeignKey case it's a django.forms.widgets.Select.

短叹 2024-08-18 03:31:44

使用以下方法

我对 FormInlineForm Django 2.0、Python 3+

Form

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  

Inline 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

I use the following approaches for Form and InlineForm

Django 2.0, Python 3+

Form

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  

Inline 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
や莫失莫忘 2024-08-18 03:31:44

@Slipstream 的答案展示了如何实施该解决方案,即。通过覆盖表单字段小部件的属性,但是,在我看来,get_form 并不是执行此操作最合乎逻辑的位置。

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

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

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

它还允许(稍微)更干净和更清晰的代码,并且作为奖励,我们可以轻松设置附加表单 Field 属性,例如初始值和/或禁用 (例如此处),将它们添加到kwargs(在调用super)。

因此,结合两个答案(假设 OP 的模型是 ModelAModelB,并且 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)

注意:如果 ForeignKey 模型字段具有 on_delete=models.CASCADE,则 can_delete_lated 属性为 False 默认情况下,如 RelatedFieldWidgetWrapper 的>源代码

The answer by @Slipstream shows how to implement the solution, viz. by overriding the attributes for the formfield's widget, but, in my opinion, get_form is not the most logical place to do this.

The answer by @cethegeek shows where to implement the solution, viz. in an extension of formfield_for_dbfield, but does not provide an explicit example.

Why use formfield_for_dbfield? Its docstring suggests that it is the designated hook for messing with form fields:

Hook for specifying the form Field instance for a given database Field instance.

It also allows for (slightly) cleaner and clearer code, and, as a bonus, we can easily set additional form Field attributes, such as initial value and/or disabled (example here), by adding them to the kwargs (before calling super).

So, combining the two answers (assuming the OP's models are ModelA and ModelB, and the ForeignKey model field is named 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)

NOTE: If the ForeignKey model field has on_delete=models.CASCADE, the can_delete_related attribute is False by default, as can be seen in the source for RelatedFieldWidgetWrapper.

雪花飘飘的天空 2024-08-18 03:31:44

查看 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 的子类),将您的模型基于该新类,覆盖 < code>formfield_fo_dbfield 并使其不会/或有条件地将小部件包装在 RelatedFieldWidgetWrapper 中。

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

Look at django.contrib.admin.options.py and check out the BaseModelAdmin class, formfield_for_dbfield method.

You will see this:

# 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)

I think your best bet is create subclass of ModelAdmin (which in turn is a subclass of BaseModelAdmin), base your model on that new class, override formfield_fo_dbfield and make it so that it won't/or will conditionally wrap the widget in RelatedFieldWidgetWrapper.

One could argue that if you have a user that doesn't have rights to adding related objects, the RelatedFieldWidgetWrapper should not display the add link? Maybe this is something that is deserving of mention in Django trac?

妄断弥空 2024-08-18 03:31:44

已弃用的答案

Django 使这成为可能。


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

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

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 文档 - 媒体作为静态定义

注意/编辑:

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

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',) }

DEPRECATED ANSWER

Django has since made this possible.


Have you considered instead, using CSS to simply not show the button? Maybe that's a little too hacky.

This is untested, but I'm thinking...

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 for doing this -- Media as a static definition

Note/Edit: The documentation says the files will be prepended with the MEDIA_URL but in my experimentation it isn't. Your mileage may vary.

If you find this is the case for you, there's a quick fix for this...

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',) }
你丑哭了我 2024-08-18 03:31:44

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

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

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

{#{% if show_save_and_add_another %}{% endif %}#}

我知道这个问题已经得到解答。但也许将来有人会遇到和我类似的情况。

I'm using Django 2.x and I think I found best solution, at least for my case.

The HTML file to the "Save and Add Another" button is on your_python_installation\Lib\site-packages\django\contrib\admin\templates\admin\subtmit_line.html.

  1. Copy that html file and paste to your project like so your_project\templates\admin\submit_line.html.
  2. Open it and comment/delete the button code as desired:

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

I know this problem is already answered. But maybe someone in the future have a similar case with me.

一曲琵琶半遮面シ 2024-08-18 03:31:44

根据 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

Based on cethegeek answer I made this:

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
从此见与不见 2024-08-18 03:31:44

我根据 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
    

The way i fixed a similar situation based on django docs

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

The outcome of the solution is that it lets you add an inline just for that instance. Or in different words: add an inline and just one; no other buttons.

#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
    
不打扰别人 2024-08-18 03:31:44

正如评论中指出的:

max_num = 0

这里也得到了证实:
https://code.djangoproject.com/ticket/13424#comment:1

PS:这也适用于内联。

As it's been pointed out in comments:

max_num = 0

It has also been confirmed here:
https://code.djangoproject.com/ticket/13424#comment:1

PS: This also works for inlines.

星星的轨迹 2024-08-18 03:31:44

django.contrib.admin.widgets.py

(Django Install Dir)/django/contrib/admin/widgets.py: 注释第 239 行和第 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" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))

django.contrib.admin.widgets.py

(Django Install Dir)/django/contrib/admin/widgets.py: Comment everything between Line 239 & Line 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" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文