Django 1.7 eliminando el botón Agregar de la forma en línea

Estoy teniendo problemas para lograr una tarea (probablemente) bastante simple. Tengo modelos totalmente modificables (Prodotto, Comune) que se muestran como campos “agregados”, como se muestra en la imagen a continuación. Lo que preferiría no ver es el botón + (agregar) para dichos campos, por lo tanto, para eliminar su propiedad “agregable” de esta forma. He intentado configurar has_add_permission = False dentro de los dos modelos, pero haría imposible agregar nuevos objetos a dichos modelos completamente, no solo de esta forma.

¿Cómo puedo hacer eso?

EDITAR: Para aclarar mi necesidad, me gustaría que NO tenga los “+” al lado de los campos de los modelos FK, pero aún así quiero poder agregar nuevas líneas en línea. Para ser lo más claro posible, como escribí en un comentario, considerando un escenario como este: https://code.djangoproject.com/attachment/ticket/20367/django_custom_user_admin_form.png Solo necesito tener el “+” s Junto a Grupos y País eliminado.

CÓDIGO EXISTENTE:

models.py (de la aplicación específica involucrada):

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', 'Snc'), ('SRL', 'Srl'), ('SPA', 'SpA'), ('SAS', 'Sas'), ('COOP', 'Coop.Arl'), ('DI', 'DI'), ('SCARL', 'Scarl'), ('SCPA', 'Scpa'), ) 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 (de la aplicación específica involucrada):

 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) 

La interfaz de administración de esta aplicación produce esto:

Interfaz de administrador

Los enlaces de acceso directo 1 y 2 son los que necesito eliminar, ya que se refieren a columnas (FK) dentro de mis clases en línea. Los enlaces de acceso directo 3 y 4 deben mantenerse, ya que se refieren a las propias líneas .

Creo que esta es una solución menos intrépida que la que terminaste. Funcionó para mí, de todos modos.

Básicamente, es el equivalente en línea de lo que sugirió hacer con la modificación del método get_form de ModelAdmin. Aquí anulamos get_formset en la clase en línea, obtenemos el formulario fuera del formset y hacemos exactamente lo mismo. Parece funcionar bien, al menos en 1.9, que estoy usando.

 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 

Para eliminar la opción “Agregar otro”, agregue el siguiente método en la clase en línea del administrador.

 def has_add_permission(self, request): return False 

Del mismo modo si desea desactivar “¿Eliminar?” Opción, agregue el siguiente método en la clase en línea del administrador.

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

Después de un par de días intensos, finalmente logré encontrar una manera de lograrlo.

Un truco simple como este es más que suficiente cuando se trata este problema dentro de las subclases ModelAdmin (ver ClienteAdmin en mi código anterior), así que aquí está la versión de la clase sin agregar capacidades para el campo “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 

El dolor real se produce cuando se trata de clases en línea (TabularInline, StackedInline), ya que la función get_form () parece que no se llama en absoluto, por lo que la forma anterior no funcionará.

Explicar todos mis bashs anteriores llevaría demasiado tiempo, y probablemente ni siquiera soy lo suficientemente bueno con Django para decir por qué no funcionaron. Así que vamos directamente a la solución, que de hecho no es tan complicada.

Subclasificé el widget django.contrib.admin.widgets.RelatedFieldWidgetWrapper y sobrescribí su método de render, para que no agregue el ancla “add-another” a la salida. Se hace fácilmente comentando unas pocas líneas. Después de hacerlo, hizo el truco.

Claramente, para que funcionara tuve que agregar la línea de importación en admin.py :

desde .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:https://stackoverflow.com/questions/26425818/django-1-7-removing-add-button-from-inline-form/%s_https://stackoverflow.com/questions/26425818/django-1-7-removing-add-button-from-inline-form/%s_add' % info, current_app=self.admin_site.name) url_params = '?https://stackoverflow.com/questions/26425818/django-1-7-removing-add-button-from-inline-form/%s=https://stackoverflow.com/questions/26425818/django-1-7-removing-add-button-from-inline-form/%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(' ' % (related_url, url_params, name)) output.append('http://sofes.miximages.com/python/%s' % (static('admin/img/icon_addlink.gif'), _('Add Another'))) ''' return mark_safe(''.join(output)) # Monkeypatch django.contrib.admin.widgets.RelatedFieldWidgetWrapper = NoAddingRelatedFieldWidgetWrapper 

Para completar, aquí está la versión final del admin.py relacionado:

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 

Si alguien sale con una solución mejor, la aceptaré gustosamente en lugar de la mía.