在 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】:
查看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 进行子类化,同时不理会其他人。【参考方案2】:弃用的答案
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 解决方案。【参考方案3】: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')))
【讨论】:
嗨,这不会删除所有链接吗?无论如何,这个问题已经很老了,在上面的建议中已经找到了解决方案。无论如何谢谢:)【参考方案4】:以下答案是我原来的答案,但它是错误的,并没有回答 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的表单的理解并没有我当时想的那么好。很抱歉造成混乱。【参考方案5】:
注意适用于 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.widget
是django.contrib.admin.widgets.RelatedFieldWidgetWrapper
的一个实例,如果您尝试在其中一个formfield_for_xxxxx
函数中进行更改,小部件是一个实例实际的表单小部件,在这个典型的 ForeignKey 案例中,它是 django.forms.widgets.Select
。
【讨论】:
你知道有一种方法可以为InlineModelAdmin
工作吗?
抱歉,从未尝试过 InlineModelAdmin
。不过我希望它是相似的,我只是不确定如何覆盖InlineModelAdmin
,而且自从发布后不久我还没有写过一行 Django 特定的 Python。
@Ad N 检查下面的 InlineModelAdmin 答案。【参考方案6】:
虽然这里提到的大多数解决方案都有效,但还有另一种更简洁的方法。可能在其他解决方案出现之后,它是在更高版本的 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 字段中的小add
、delete
符号,而且还删除了添加/编辑选项管理界面中所有位置的链接模型。
我认为这是是正确的,因为它是InlineModelAdmin
(不是ModelAdmin
)的子类,需要明确添加到其他ModelAdmin
的inlines
。让我修复需要obj
参数的has_add_permission
(def has_add_permission(self, request, obj):
)。【参考方案7】:
根据 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
【讨论】:
您能解释一下为什么/如何它是一个更好的解决方案吗?只是看起来更复杂【参考方案8】:我对 Form 和 InlineForm
使用以下方法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
【讨论】:
【参考方案9】:我正在使用 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 %#
我知道这个问题已经得到解答。不过说不定以后有人跟我有类似的情况。
【讨论】:
【参考方案10】:@Slipstream 的回答显示了如何 实施解决方案,即。通过覆盖表单域小部件的属性,但在我看来,get_form
并不是最合乎逻辑的地方。
@cethegeek 的答案显示了 where 来实施解决方案,即。在formfield_for_dbfield
的扩展中,但没有提供明确的示例。
为什么使用formfield_for_dbfield
?它的docstring 表明它是用于弄乱表单字段的指定钩子:
用于指定给定数据库字段实例的表单字段实例的钩子。
它还允许(稍微)更清晰的代码,并且,作为奖励,我们可以轻松设置其他形式 Field
attributes,例如 initial
值和/或 disabled
(例如 @987654325 @),将它们添加到 kwargs
(在调用 super
之前)。
所以,结合这两个答案(假设 OP 的模型是 ModelA
和 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)
注意:如果ForeignKey
模型字段具有on_delete=models.CASCADE
,则can_delete_related
属性默认为False
,如source 中的RelatedFieldWidgetWrapper
所示。
【讨论】:
【参考方案11】:我根据 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
【讨论】:
以上是关于在 Django 管理屏幕中删除“添加另一个”的主要内容,如果未能解决你的问题,请参考以下文章
Django 管理模型 add_view:如何删除“保存并添加另一个”按钮?
对于 django 模型,如何获取 django 管理 URL 以添加另一个或列出对象等?