2016-06-30 11 views
9

Diciamo che ho un modello in cui la riga che ha un ID di 1 è speciale e non dovrebbe essere cancellata, ma tutte le altre righe vanno bene per cancellarle. Qui è il mio tentativo di attuazione di tale logica:Admin Django: has_delete_permission Ignorato per azione "Elimina"

models.py

from django.db import models 


class Widget(models.Model): 
    name = models.CharField(max_length=255) 

    class Meta: 
     ordering = ('name',) 

    def __unicode__(self): 
     return self.name 

admin.py

from django.contrib import admin 

from .models import Widget 


class WidgetAdmin(admin.ModelAdmin): 
    def has_delete_permission(self, request, obj=None): 
     return obj is None or obj.pk != 1 

admin.site.register(Widget, WidgetAdmin) 

Il codice di cui sopra rimuove il pulsante "Cancella" dalla forma cambiamento quando obj.pk è 1 , che è quello che voglio. Tuttavia, nell'elenco delle modifiche, se spunta la casella di controllo per la riga con un ID di 1 e poi utilizzo l'azione "Elimina widget selezionati", sono in grado di eliminare quella riga. Voglio impedirlo, ma permetto comunque che tutte le altre righe vengano eliminate con l'azione "Elimina widget selezionati". Come posso fare questo?

+1

Stavo per rispondere al tuo commento per dire che questo era probabilmente un ottimizzazione delle prestazioni in modo che l'azione possa usare 'queryset.delete()' invece di recuperare ogni istanza. Osservando il [codice sorgente] (https://github.com/django/django/blob/64aba7a8aba06b8be52a1a099b44e1d3be4bdd26/django/contrib/admin/actions.py#L46), tuttavia, si scopre che il queryset è iterato comunque! Ulteriori indagini rivelano che la tua preoccupazione esatta è stata [accettata, ma non trattata, bug] (https://code.djangoproject.com/ticket/11383) negli ultimi 7 anni. –

+0

@KevinChristopherHenry Grazie per aver trovato quell'insetto! È bello sapere che questo è un problema noto. – Nick

risposta

8

Secondo has_delete_permission's docstring:

def has_delete_permission(self, request, obj=None): 
    """ 
    Returns True if the given request has permission to change the given 
    Django model instance, ... 
    """ 

Questo significa has_delete_permission viene eseguita per ogni richiesta, non per oggetto. Per un'azione collettiva, obj non è impostato. Tuttavia si può esaminare request:

def has_delete_permission(self, request, obj=None): 
    if request.POST and request.POST.get('action') == 'delete_selected': 
     return '1' not in request.POST.getlist('_selected_action') 
    return obj is None or obj.pk != 1 

Nota che le opere di cui sopra, perché the delete_selected action takes has_delete_permission into account.

Si consiglia inoltre di fornire alcuni dettagli circa l'errore:

from django.contrib import messages 

def has_delete_permission(self, request, obj=None): 
    if request.POST and request.POST.get('action') == 'delete_selected': 
     if '1' in request.POST.getlist('_selected_action'): 
      messages.add_message(request, messages.ERROR, (
       "Widget #1 is protected, please remove it from your selection " 
       "and try again." 
      )) 
      return False 
     return True 
    return obj is None or obj.pk != 1 

Credo has_delete_permission è chiamato per ogni richiesta, piuttosto che per oggetto per motivi di prestazioni. Nel caso generale, è inutile creare una query SELECT e ripetere il ciclo su has_delete_permission (che può richiedere molto tempo in base a ciò che fa) prima di eseguire la query DELETE. E quando è rilevante farlo, spetta allo sviluppatore fare i passi necessari.

+1

Ottima soluzione! Grazie! Come nota a margine, una versione semplificata di 'messages.add_message (request, messages.ERROR, 'Oh no!')' È 'messages.error (request, 'Oh no!')' – Nick

7

È possibile sostituire l'implementazione dell'amministratore dell'azione delete_selected con la propria. Qualcosa di simile:

from django.contrib.admin import actions 

class WidgetAdmin(admin.ModelAdmin): 
    actions = [delete_selected] 

    def delete_selected(self, request, queryset): 
     # Handle this however you like. You could raise PermissionDenied, 
     # or just remove it, and/or use the messages framework... 
     queryset = queryset.exclude(pk=1) 

     actions.delete_selected(self, request, queryset) 
    delete_selected.short_description = "Delete stuff" 

Vedere the documentation per ulteriori dettagli.