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?
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.
Faire cela correctement nécessite deux étapes:
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.
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.
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.
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:
/add
à l'URLEnfin, vous pouvez supprimer l'action "Supprimer les éléments sélectionnés " en modifiant le paramètre actions
.
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
Dans Django 1.7 et versions ultérieures, vous pouvez faire
class HitAdmin(admin.ModelAdmin):
list_display_links = None
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'))
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
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.
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.
ecrivez list_display_links = None
dans votre administrateur
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.
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
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.
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