Django 1.7 从内联表单中移除添加按钮
我在完成一个(可能)相对简单的任务时遇到了问题。
我有一些可以完全修改的模型(Prodotto, Comune),它们在下面的图片中显示为“可添加”的字段。
我其实不想看到这些字段旁边的 +(添加)按钮,因此想要在这个表单中去掉它们的“可添加”属性。
我尝试在这两个模型中设置 has_add_permission=False,但这样会导致完全无法向这些模型添加新对象,而不仅仅是在这个表单中。
我该怎么做呢?
编辑:为了更清楚地说明我的需求,我希望在外键模型的字段旁边没有 "+",但我仍然希望能够添加全新的内联。为了尽可能清楚,我在评论中提到过这样的场景:https://code.djangoproject.com/attachment/ticket/20367/django_custom_user_admin_form.png 我只需要去掉 Groups 和 Country 旁边的 "+"。
现有代码:
models.py(相关应用的代码):
from django.db import models
from smart_selects.db_fields import ChainedForeignKey
from apps.comune.models import Comune, Cap
class Prodotto(models.Model):
SETTORE_CHOICES = (
('CAL', 'Accessori calzature'),
('ALI', 'Alimentari'),
('ARA', 'Arredamenti e accessori'),
('AEM', 'Auto e moto'),
('CAL', 'Calzature'),
('CEG', 'Cartaria e grafica'),
('CEP', 'Concerie e pelletterie'),
('EDI', 'Edilizia'),
('INV', 'Industrie varie'),
('IST', 'Istruzione'),
('MDC', 'Materiali da costruzione'),
('MMC', 'Metalmeccanica'),
('SEI', 'Serramenti e infissi'),
('STM', 'Strumenti musicali'),
('TEI', 'Terziario innovativo'),
('TAB', 'Tessile abbigliamento'),
('TCP', 'Trasporto cose e persone'),
('VAR', 'Vari'),
)
nome = models.CharField(max_length=100)
settore = models.CharField(max_length=40, choices=SETTORE_CHOICES)
class Meta:
verbose_name_plural = "prodotti"
verbose_name = "prodotto"
ordering = ['nome']
def __unicode__(self):
return self.nome.capitalize()
class Cliente(models.Model):
TIPOLOGIA_CHOICES = (
('AR', 'Artigiano'),
('CO', 'Commerciante'),
('GI', 'Grande impresa'),
('PI', 'Piccola impresa'),
)
FORMA_SOCIETARIA_CHOICES = (
('SNC', 'S.n.c.'),
('SRL', 'S.r.l.'),
('SPA', 'S.p.A.'),
('SAS', 'S.a.s.'),
('COOP', 'Coop.A.r.l.'),
('DI', 'D.I.'),
('SCARL', 'S.c.a.r.l.'),
('SCPA', 'S.c.p.a.'),
)
SETTORE_CHOICES = (
('CAL', 'Accessori calzature'),
('ALI', 'Alimentari'),
('ARA', 'Arredamenti e accessori'),
('AEM', 'Auto e moto'),
('CAL', 'Calzature'),
('CEG', 'Cartaria e grafica'),
('CEP', 'Concerie e pelletterie'),
('EDI', 'Edilizia'),
('INV', 'Industrie varie'),
('IST', 'Istruzione'),
('MDC', 'Materiali da costruzione'),
('MMC', 'Metalmeccanica'),
('SEI', 'Serramenti e infissi'),
('STM', 'Strumenti musicali'),
('TEI', 'Terziario innovativo'),
('TAB', 'Tessile abbigliamento'),
('TCP', 'Trasporto cose e persone'),
('VAR', 'Vari'),
)
ragione_sociale = models.CharField(max_length=200)
forma_societaria = models.CharField(
max_length=5, choices=FORMA_SOCIETARIA_CHOICES)
titolare = models.CharField(max_length=100, blank=True)
partita_iva = models.CharField(
max_length=11, verbose_name='Partita IVA', unique=True)
tipologia = models.CharField(max_length=2, choices=TIPOLOGIA_CHOICES)
settore = models.CharField(max_length=40, choices=SETTORE_CHOICES)
prodotto = models.ManyToManyField(Prodotto, blank=True)
class Meta:
verbose_name_plural = "clienti"
verbose_name = "cliente"
def __unicode__(self):
return self.ragione_sociale.capitalize()
class Sede(models.Model):
nome = models.CharField(max_length=100)
indirizzo = models.CharField(max_length=200, blank=True)
cliente = models.ForeignKey(Cliente)
comune = models.ForeignKey(Comune)
cap = ChainedForeignKey(
Cap,
chained_field="comune",
chained_model_field="comune",
show_all=False,
auto_choose=True,
)
class Meta:
verbose_name_plural = "sedi"
verbose_name = "sede"
ordering = ['nome']
def __unicode__(self):
return self.nome.capitalize() + ", " + self.indirizzo
admin.py(相关应用的代码):
from django.contrib import admin
from .models import Cliente, Prodotto, Sede
from apps.recapito.models import RecapitoCliente
class SedeInline(admin.TabularInline):
model = Sede
extra = 1
def provincia(self, obj):
return obj.comune.provincia
readonly_fields = ['provincia', ]
class RecapitoInline(admin.TabularInline):
model = RecapitoCliente
extra = 1
list_fields = ['cliente', 'tipo', 'recapito', ]
@admin.register(Cliente)
class ClienteAdmin(admin.ModelAdmin):
list_display = [
'ragione_sociale', 'forma_societaria', 'titolare', 'partita_iva', ]
list_filter = ['forma_societaria', ]
search_fields = ['ragione_sociale', ]
inlines = [RecapitoInline, SedeInline]
admin.site.register(Prodotto)
这个应用的管理界面生成了这个:
快捷链接 1 和 2 是我需要去掉的,因为它们指向我内联类中的列(外键)。
快捷链接 3 和 4 要保留,因为它们指向的是内联本身。
4 个回答
其实这个问题有一个简单明了的解决办法:
class YourInline(admin.TabularInline):
extra = 0
max_num=0
经过几天的努力,我终于找到了解决这个问题的方法。
像这个简单的小技巧,在处理ModelAdmin子类时(比如我上面代码中的ClienteAdmin),就足够用了。所以这里是一个不添加“Prodotto”字段功能的类版本:
@admin.register(Cliente)
class ClienteAdmin(admin.ModelAdmin):
list_display = [
'ragione_sociale', 'forma_societaria', 'titolare', 'partita_iva', ]
list_filter = ['forma_societaria', ]
search_fields = ['ragione_sociale', ]
inlines = [RecapitoInline, SedeInline]
def get_form(self, request, obj=None, **kwargs): # Just added this override
form = super(ClienteAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['prodotto'].widget.can_add_related = False
return form
真正麻烦的是处理内联类(TabularInline, StackedInline),因为get_form()这个函数似乎根本没有被调用,所以之前的方法就不管用了。
如果要解释我之前的所有尝试,那会花太多时间,而且我可能对Django还不够熟悉,无法准确说出它们为什么没成功。所以我们直接进入解决方案,其实也并不复杂。
我创建了一个继承自django.contrib.admin.widgets.RelatedFieldWidgetWrapper的小部件,并重写了它的render方法,这样就不会在输出中添加“添加另一个”的链接。这个操作很简单,只需注释掉几行代码。完成后,我用自己的版本替换了原来的RelatedFieldWidgetWrapper(django.contrib.admin.widgets.RelatedFieldWidgetWrapper = NoAddingRelatedFieldWidgetWrapper),就成功了。
显然,为了让它工作,我还需要在admin.py中添加导入语句:
from .widgets import NoAddingRelatedFieldWidgetWrapper
widgets.py
import django.contrib.admin.widgets
from django.utils.safestring import mark_safe
class NoAddingRelatedFieldWidgetWrapper(django.contrib.admin.widgets.RelatedFieldWidgetWrapper):
def render(self, name, value, *args, **kwargs):
from django.contrib.admin.views.main import TO_FIELD_VAR
rel_to = self.rel.to
info = (rel_to._meta.app_label, rel_to._meta.model_name)
self.widget.choices = self.choices
output = [self.widget.render(name, value, *args, **kwargs)]
'''
if self.can_add_related:
related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
url_params = '?%s=%s' % (TO_FIELD_VAR, self.rel.get_related_field().name)
# TODO: "add_id_" is hard-coded here. This should instead use the
# correct API to determine the ID dynamically.
output.append('<a href="%s%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> '
% (related_url, url_params, name))
output.append('<img src="%s" width="10" height="10" alt="%s"/></a>'
% (static('admin/img/icon_addlink.gif'), _('Add Another')))
'''
return mark_safe(''.join(output))
# Monkeypatch
django.contrib.admin.widgets.RelatedFieldWidgetWrapper = NoAddingRelatedFieldWidgetWrapper
为了完整起见,这里是最终版本的admin.py:
admin.py
from django.contrib import admin
import django.contrib.admin.widgets
from django.db import models
from .models import Cliente, Prodotto, Sede
from apps.recapito.models import RecapitoCliente
from .widgets import NoAddingRelatedFieldWidgetWrapper
class SedeInline(admin.TabularInline):
model = Sede
extra = 1
def provincia(self, obj):
return obj.comune.provincia
readonly_fields = ['provincia', ]
class RecapitoInline(admin.TabularInline):
model = RecapitoCliente
extra = 1
readonly_fields = ['cliente', 'tipo', 'recapito', ]
@admin.register(Cliente)
class ClienteAdmin(admin.ModelAdmin):
list_display = [
'ragione_sociale', 'forma_societaria', 'titolare', 'partita_iva', ]
list_filter = ['forma_societaria', ]
search_fields = ['ragione_sociale', ]
inlines = [RecapitoInline, SedeInline]
def get_form(self, request, obj=None, **kwargs):
form = super(ClienteAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['prodotto'].widget.can_add_related = False
return form
如果有人能提出更好的解决方案,我会很乐意接受。
要去掉“添加另一个”这个选项,请在管理界面的内联类中添加下面的方法。
def has_add_permission(self, request):
return False
同样,如果你想禁用“删除?”这个选项,也可以在管理界面的内联类中添加以下方法。
def has_delete_permission(self, request, obj=None):
return False
我觉得这个方法比你最后用的那个更简单直接。反正对我来说是有效的。
基本上,这个方法是在内联类中覆盖get_formset,就像你提到的覆盖ModelAdmin的get_form方法一样。我们从表单集合中获取表单,然后做完全相同的事情。至少在我使用的1.9版本中,这个方法运行得很好。
class VersionEntryInline(admin.TabularInline):
template = 'admin/edit_inline/tabular_versionentry.html'
model = VersionEntry
extra = 0
def get_formset(self, request, obj=None, **kwargs):
"""
Override the formset function in order to remove the add and change buttons beside the foreign key pull-down
menus in the inline.
"""
formset = super(VersionEntryInline, self).get_formset(request, obj, **kwargs)
form = formset.form
widget = form.base_fields['project'].widget
widget.can_add_related = False
widget.can_change_related = False
widget = form.base_fields['version'].widget
widget.can_add_related = False
widget.can_change_related = False
return formset