web-dev-qa-db-fra.com

Désactiver le lien pour modifier l'objet dans l'admin de Django (liste d'affichage uniquement)?

Dans l'administration de Django, je veux désactiver les liens fournis sur la page "sélectionner l'élément à modifier" afin que les utilisateurs ne puissent aller nulle part pour modifier l'élément. (Je vais limiter ce que les utilisateurs peuvent faire avec cette liste à un ensemble d'actions déroulantes - pas de modification réelle des champs).

Je vois que Django a la capacité de choisir quels champs afficher le lien , cependant, je ne vois pas comment je peux avoir aucun d'entre eux.

class HitAdmin(admin.ModelAdmin):
    list_display = ('user','ip','user_agent','hitcount')
    search_fields = ('ip','user_agent')
    date_hierarchy = 'created'
    list_display_links = [] # doesn't work, goes to default

Des idées sur la façon d'obtenir ma liste d'objets sans aucun lien à modifier?

44
thornomad

Je voulais une visionneuse de journaux en tant que liste uniquement.

Je l'ai fait fonctionner comme ceci:

class LogEntryAdmin(ModelAdmin):
    actions = None
    list_display = (
        'action_time', 'user',
        'content_type', 'object_repr', 
        'change_message')

    search_fields = ['=user__username', ]
    fieldsets = [
        (None, {'fields':()}), 
        ]

    def __init__(self, *args, **kwargs):
        super(LogEntryAdmin, self).__init__(*args, **kwargs)
        self.list_display_links = (None, )

C'est une sorte de mélange entre les deux réponses.

Si vous faites simplement self.list_display_links = () il affichera le lien, de toute façon parce que le code template-tag (Templatetags/admin_list.py) vérifie à nouveau si la liste est vide.

61
Federico

Faire cela correctement nécessite deux étapes:

  • Masquez le lien d'édition, afin que personne ne trébuche sur la page de détail (changer de vue) par erreur.
  • Modifiez la vue de modification pour la rediriger vers la vue de liste.

La deuxième partie est importante: si vous ne le faites pas, les gens pourront toujours accéder à la vue des modifications en entrant directement une URL (ce que vous ne voulez probablement pas). Ceci est étroitement lié à ce que le terme OWASP désigne "Insecure Direct Object Reference" .

Dans le cadre de cette réponse, je vais créer une classe ReadOnlyMixin qui peut être utilisée pour fournir toutes les fonctionnalités affichées.

Masquage du lien d'édition

Django 1.7 rend cela très simple: il vous suffit de définir list_display_links Sur None.

class ReadOnlyMixin(): # Add inheritance from "object" if using Python 2
    list_display_links = None

Django 1.6 (et probablement plus tôt) ne rend pas cela si simple. De nombreuses réponses à cette question ont suggéré de remplacer __init__ Afin de définir list_display_links Après la construction de l'objet, mais cela rend sa réutilisation plus difficile (nous ne pouvons remplacer le constructeur qu'une seule fois) .

Je pense qu'une meilleure option est de remplacer la méthode get_list_display_links De Django comme suit:

def get_list_display_links(self, request, list_display):
    """
    Return a sequence containing the fields to be displayed as links
    on the changelist. The list_display parameter is the list of fields
    returned by get_list_display().

    We override Django's default implementation to specify no links unless
    these are explicitly set.
    """
    if self.list_display_links or not list_display:
        return self.list_display_links
    else:
        return (None,)

Cela rend notre mixin facile à utiliser: il masque le lien d'édition par défaut mais nous permet de l'ajouter si nécessaire pour une vue d'administration particulière.

Redirection vers la vue Liste

Nous pouvons changer le comportement de la page de détail (changer la vue) en remplaçant la méthode change_view. Voici une extension de la technique suggérée par Chris Pratt qui trouve automatiquement la bonne page:

enable_change_view = False

def change_view(self, request, object_id, form_url='', extra_context=None):
    """
    The 'change' admin view for this model.

    We override this to redirect back to the changelist unless the view is
    specifically enabled by the "enable_change_view" property.
    """
    if self.enable_change_view:
        return super(ReportMixin, self).change_view(
            request,
            object_id,
            form_url,
            extra_context
        )
    else:
        from Django.core.urlresolvers import reverse
        from Django.http import HttpResponseRedirect

        opts = self.model._meta
        url = reverse('admin:{app}_{model}_changelist'.format(
            app=opts.app_label,
            model=opts.model_name,
        ))
        return HttpResponseRedirect(url)

Encore une fois, cela est personnalisable - en basculant enable_change_view Sur True, vous pouvez réactiver la page de détails.

Suppression du bouton "Ajouter [~ # ~] élément [~ # ~] "

Enfin, vous souhaiterez peut-être remplacer les méthodes suivantes afin d'empêcher les utilisateurs d'ajouter ou de supprimer de nouveaux éléments.

def has_add_permission(self, request):
    return False

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

Ces changements:

  • désactiver le bouton "Ajouter l'élément "
  • empêcher les gens d'ajouter directement des éléments en ajoutant /add à l'URL
  • empêcher la suppression en masse

Enfin, vous pouvez supprimer l'action "Supprimer les éléments sélectionnés " en modifiant le paramètre actions.

Mettre tous ensemble

Voici le mixin terminé:

from Django.core.urlresolvers import reverse
from Django.http import HttpResponseRedirect

class ReadOnlyMixin(): # Add inheritance from "object" if using Python 2

    actions = None

    enable_change_view = False

    def get_list_display_links(self, request, list_display):
        """
        Return a sequence containing the fields to be displayed as links
        on the changelist. The list_display parameter is the list of fields
        returned by get_list_display().

        We override Django's default implementation to specify no links unless
        these are explicitly set.
        """
        if self.list_display_links or not list_display:
            return self.list_display_links
        else:
            return (None,)

    def change_view(self, request, object_id, form_url='', extra_context=None):
        """
        The 'change' admin view for this model.

        We override this to redirect back to the changelist unless the view is
        specifically enabled by the "enable_change_view" property.
        """
        if self.enable_change_view:
            return super(ReportMixin, self).change_view(
                request,
                object_id,
                form_url,
                extra_context
            )
        else:
            opts = self.model._meta
            url = reverse('admin:{app}_{model}_changelist'.format(
                app=opts.app_label,
                model=opts.model_name,
            ))
            return HttpResponseRedirect(url)

    def has_add_permission(self, request):
        return False

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

Dans Django 1.7 et versions ultérieures, vous pouvez faire

class HitAdmin(admin.ModelAdmin):
    list_display_links = None
18
Blaise

En tant qu'utilisateur, omat, mentionné dans un commentaire ci-dessus, toute tentative de supprimer simplement les liens n'empêche pas les utilisateurs d'accéder toujours à la page de modification manuellement. Cependant, cela aussi est assez facile à résoudre:

class MyModelAdmin(admin.ModelAdmin)
    # Other stuff here
    def change_view(self, request, obj=None):
        from Django.core.urlresolvers import reverse
        from Django.http import HttpResponseRedirect
        return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))
18
Chris Pratt

Dans votre jeu d'administrateurs modèle:

list_display_links = (None,)

Ça devrait le faire. (Fonctionne en 1.1.1 de toute façon.)

Lien vers les documents: list_display_links

7
Josh Ourisman

Juste pour les notes, vous pouvez modifier changelist_view:

class SomeAdmin(admin.ModelAdmin):
    def changelist_view(self, request, extra_context=None):
        self.list_display_links = (None, )
        return super(SomeAdmin, self).changelist_view(request, extra_context=None)

Cela fonctionne bien pour moi.

2
tinti

Il n'y a pas de méthode prise en charge pour ce faire.

En regardant le code, il semble qu'il définit automatiquement ModelAdmin.list_display_links au premier élément si vous ne le définissez sur rien. Le moyen le plus simple pourrait donc être de remplacer le __init__ méthode dans votre sous-classe ModelAdmin pour désactiver cet attribut lors de l'initialisation:

class HitAdmin(admin.ModelAdmin):
    list_display = ('user','ip','user_agent','hitcount')
    search_fields = ('ip','user_agent')
    date_hierarchy = 'created'

    def __init__(self, *args, **kwargs):
        super(HitAdmin, self).__init__(*args, **kwargs)
        self.list_display_links = []

Cela semble fonctionner, après un test très rapide. Je ne peux cependant pas garantir qu'il ne cassera rien ailleurs, ni qu'il ne sera pas cassé par les modifications futures de Django.

Modifier après commentaire:

Pas besoin de patcher la source, cela fonctionnerait:

    def __init__(self, *args, **kwargs):
        if self.list_display_links:
            unset_list_display = True
        else:
            unset_list_display = False
        super(HitAdmin, self).__init__(*args, **kwargs)
        if unset_list_display:
            self.list_display_links = []

Mais je doute fortement qu'un correctif soit accepté dans Django, car cela casse quelque chose que le code fait explicitement pour le moment.

2
Daniel Roseman

ecrivez list_display_links = None dans votre administrateur

2
Sameer Yadav

avec Django 1.6.2 vous pouvez faire comme ceci:

class MyAdmin(admin.ModelAdmin):

    def get_list_display_links(self, request, list_display):
        return []

il masquera tous les liens générés automatiquement.

1
truease.com

Dans les versions plus "récentes" de Django, depuis au moins 1.9, il est possible de déterminer simplement les autorisations d'ajout, de modification et de suppression sur la classe admin. Voir la documentation d'administration Django pour référence. Voici un exemple:

@admin.register(Object)
class Admin(admin.ModelAdmin):

    def has_add_permission(self, request):
        return False

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

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

Vous pourriez également être ridiculement hacky à ce sujet (si vous ne vouliez pas vous embêter avec la surcharge init ) et fournissez une valeur pour le premier élément qui ressemble à ceci:

</a>My non-linked value<a>

Je sais, je sais, pas très joli, mais peut-être moins d'anxiété à l'idée de casser quelque chose ailleurs car tout ce que nous faisons, c'est changer le balisage.

Voici un exemple de code sur la façon dont cela fonctionne:

class HitAdmin(admin.ModelAdmin):
    list_display = ('user_no_link','ip','user_agent','hitcount')

    def user_no_link(self, obj):
        return u'</a>%s<a>' % obj
    user_no_link.allow_tags = True
    user_no_link.short_description = "user"

Note latérale: Vous pouvez également améliorer la lisibilité de la sortie (puisque vous ne voulez pas que ce soit un lien) en retournant return u'%s' % obj.get_full_name() qui pourrait être un peu soigné selon votre cas d'utilisation.

1
T. Stone

Je remplace la méthode et l'action get list_display_links par None.

class ChangeLogAdmin(admin.ModelAdmin):
    actions = None
    list_display = ('asset', 'field', 'before_value', 'after_value', 'operator', 'made_at')

    fieldsets = [
        (None, {'fields': ()}),
    ]

    def __init__(self, model, admin_site):
        super().__init__(model, admin_site)

    def get_list_display_links(self, request, list_display):
        super().get_list_display_links(request, list_display)
        return None
0
Jake Jeon