Remover “adicionar outro” na tela de administração do Django
-
19-09-2019 - |
Pergunta
Sempre que eu estou editando objeto A com uma chave estrangeira para objeto B, um plus opção "adicionar outro" está disponível ao lado das escolhas de objeto B. Como faço para remover essa opção?
Eu configurei um usuário sem direitos para adicionar objeto B. O sinal de adição ainda está disponível, mas quando eu clicar nele, ele diz que "Permissão negada". É feio.
Eu estou usando Django 1.0.2
Solução
DEPRECADO RESPOSTA
Django, desde então, tornou isso possível.
Você considerou em vez de usar CSS simplesmente não mostrar o botão? Talvez isso é um pouco hacky.
Esta é testado, mas eu estou pensando ...
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 para fazer isso - mídia como uma definição estática
Nota / Edit: A documentação diz que os arquivos serão precedidos com o MEDIA_URL senão em minha experimentação não é. Sua milhagem pode variar.
Se você encontrar este é o caso para você, há uma solução rápida para este ...
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',) }
Outras dicas
(stop upvoting esta resposta errada !!!)
ERRATA : Esta resposta é basicamente errado, e não responder à pergunta de OP. Veja abaixo.
(este é apenas aplicável a formas em linha, campos de chave não estrangeiros como OP perguntou)
solução mais simples, sem corte CSS, nenhuma edição Django codebase:
Adicione isto a sua classe em linha:
max_num=0
Atualizar
Isto não responder à pergunta de OP, e só é útil para ocultar a opção "Adicionar relacionado" botão para formulários em linha, e não chaves estrangeiras, conforme solicitado.
Quando escrevi esta resposta, IIRC a resposta hide aceita ambos, que é por isso que eu fiquei confuso.
Os links a seguir parece fornecer uma solução (embora escondendo usando CSS parece que as coisas mais viáveis ??para fazer, especialmente se a "acrescentar mais" botões de FKs em formas in-line):
Embora a maioria das soluções mencionadas aqui trabalho, não há outra maneira mais limpa de fazê-lo. Provavelmente foi introduzida em uma versão posterior do Django, após as outras soluções foram apresentadas. (Estou usando no momento Django 1.7)
Para remover o "Adicionar outra opção",
class ... #(Your inline class)
def has_add_permission(self, request):
return False
Da mesma forma, se você quiser desativar "Excluir?" opção, adicione o seguinte método na classe inline.
def has_delete_permission(self, request, obj=None):
return False
NB. Trabalha para Django 1.5.2 e possivelmente mais velho. A propriedade can_add_related
apareceu cerca de 2 anos atrás.
A melhor maneira que eu encontrei é para substituir a função get_form do seu ModelAdmin. No meu caso eu queria forçar o autor de um post para ser o usuário conectado no momento. Código abaixo com comentários copiosas. O bit realmente importante é a definição de 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
A parte interessante de fazer as mudanças aqui em get_form
é que author.widget
é uma instância de django.contrib.admin.widgets.RelatedFieldWidgetWrapper
onde, como se você tentar e fazer mudanças em uma das funções formfield_for_xxxxx
, o widget é uma instância do widget forma actual, neste típico ForeignKey caso é um django.forms.widgets.Select
.
Olhe para django.contrib.admin.options.py
e confira a classe BaseModelAdmin
, método formfield_for_dbfield
.
Você vai ver isso:
# 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)
Eu acho que sua melhor aposta é criar subclasse de ModelAdmin
(que por sua vez é uma subclasse de BaseModelAdmin
), basear o seu modelo em que a nova classe, formfield_fo_dbfield
override e torná-lo para que ele não vai / ou vai condicionalmente embrulhar o widget em RelatedFieldWidgetWrapper
.
Pode-se argumentar que, se você tiver um usuário que não tem direitos para a adição de objetos relacionados, o RelatedFieldWidgetWrapper
não devem exibir o link add? Talvez isso é algo que é merecedor de menção no Django trac ?
Eu uso aproxima o seguinte para Form e 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 Formulário
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
A resposta por @Slipstream mostra como para implementar a solução, viz. , substituindo os atributos para o widget do formfield, mas, na minha opinião, get_form
não é o lugar mais lógico para fazer isso.
A resposta por @cethegeek mostra , onde para implementar a solução, viz. numa extensão de formfield_for_dbfield
, mas não fornece um exemplo explícito.
formfield_for_dbfield
Por quê? Sua docstring sugere que é o gancho designado para mexer com os campos do formulário:
Gancho para especificar a forma instância de campo de uma instância do campo determinado banco de dados.
Ele também permite a (ligeiramente) mais limpo e código mais claro, e, como um bônus, podemos facilmente definir forma adicional Field
atributos , tais como valor initial
e / ou disabled
(exemplo aqui ), adicionando-os ao kwargs
(antes de chamar super
).
Assim, combinando as duas respostas (assumindo modelos do OP são ModelA
e ModelB
, eo ForeignKey
campo modelo b
é nomeado):
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)
NOTA: Se o campo modelo ForeignKey
tem on_delete=models.CASCADE
, o atributo can_delete_related
é False
por padrão, como pode ser visto na fonte para RelatedFieldWidgetWrapper
.
Eu estou usando Django 2.x e eu acho que encontrei melhor solução, pelo menos para o meu caso.
O arquivo HTML para o "Salvar e adicionar outro" botão está na your_python_installation\Lib\site-packages\django\contrib\admin\templates\admin\subtmit_line.html
.
- Copiar esse arquivo html e cole ao seu projeto como assim
your_project\templates\admin\submit_line.html
. - Abra-o e comentário / apagar o código do botão conforme desejado:
{#{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}#}
Eu sei que este problema já está respondido. Mas talvez alguém no futuro ter um caso semelhante comigo.
Com base na resposta cethegeek Eu fiz esta:
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
django.contrib.admin.widgets.py
(Django Instale Dir) /django/contrib/admin/widgets.py: Comentário tudo entre Linha 239 e Linha 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')))