python - template - django-admin command
¿Deshabilitar el enlace para editar el objeto en el administrador de django(solo en la lista de visualización)? (9)
Como usuario, omat, mencionado en un comentario anterior, cualquier intento de simplemente eliminar los enlaces no impide que los usuarios sigan accediendo manualmente a la página de cambios. Sin embargo, eso también es bastante fácil de remediar:
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''))
En el administrador de Django, quiero desactivar los enlaces provistos en la página "seleccionar elemento para cambiar" para que los usuarios no puedan ir a ningún lado a editar el artículo. (Voy a limitar lo que los usuarios pueden hacer con esta lista a un conjunto de acciones desplegables, sin edición real de campos).
Veo que Django tiene la capacidad de elegir qué campos mostrar el enlace , sin embargo, no puedo ver cómo no puedo tener ninguno de ellos.
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
¿Alguna idea de cómo obtener mi lista de objetos sin ningún enlace para editar?
En Django 1.7 y posterior, puedes hacer
class HitAdmin(admin.ModelAdmin):
list_display_links = None
En tu conjunto de administrador modelo:
list_display_links = (None,)
Deberias hacer eso. (Funciona en 1.1.1 de todos modos)
Enlace a documentos: list_display_links
Hacer esto correctamente requiere dos pasos:
- Oculte el enlace de edición, de modo que nadie se tropiece con la página de detalles (cambio de vista) por error.
- Modifique la vista de cambio para redirigir a la vista de lista.
La segunda parte es importante: si no haces esto, las personas aún podrán acceder a la vista de cambio ingresando una URL directamente (que presumiblemente no deseas). Esto está estrechamente relacionado con lo que OWASP llama una "referencia insegura de objeto directo" .
Como parte de esta respuesta, construiré una clase ReadOnlyMixin
que se puede usar para proporcionar toda la funcionalidad que se muestra.
Ocultar el enlace de edición
Django 1.7 lo hace realmente fácil: acaba de establecer list_display_links
a None
.
class ReadOnlyMixin(): # Add inheritance from "object" if using Python 2
list_display_links = None
Django 1.6 (y presumiblemente antes) no lo hacen tan simple. Muchas respuestas a esta pregunta han sugerido invalidar __init__
para establecer list_display_links
después de que se haya construido el objeto, pero esto dificulta la reutilización (solo podemos anular el constructor una vez).
Creo que una mejor opción es anular el método get_list_display_links
de Django de la siguiente manera:
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,)
Esto hace que nuestra mezcla sea fácil de usar: oculta el enlace de edición de forma predeterminada, pero nos permite agregarlo de nuevo si es necesario para una vista de administrador en particular.
Redirigir a la vista de lista
Podemos cambiar el comportamiento de la página de detalles (cambiar la vista) anulando el método change_view
. Aquí hay una extensión de la técnica sugerida por Chris Pratt que encuentra automáticamente la página correcta:
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)
De nuevo, esto es personalizable: al alternar enable_change_view
a True
, puede volver a activar la página de detalles.
Eliminar el botón "Agregar elemento"
Finalmente, es posible que desee sobrescribir los siguientes métodos para evitar que las personas agreguen o eliminen nuevos elementos.
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False
Estos cambios:
- desactivar el botón "Agregar elemento "
- evitar que las personas agreguen elementos directamente añadiendo
/add
a la URL - evitar la eliminación masiva
Finalmente, puede eliminar la acción "Eliminar elementos seleccionados" modificando el parámetro de actions
.
Poniendolo todo junto
Aquí está el mixin completo:
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
No hay una forma compatible para hacer esto.
Al mirar el código, parece que establece automáticamente ModelAdmin.list_display_links
en el primer elemento si no lo configura en nada. Así que la manera más fácil podría ser anular el método __init__
en su subclase ModelAdmin
para ModelAdmin
ese atributo en la inicialización:
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 = []
Esto parece funcionar, después de una prueba muy superficial. No puedo garantizar que no rompa nada en otro lugar, o que no se romperá por cambios futuros en Django.
Editar después de comentar :
No es necesario parchar la fuente, esto funcionaría:
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 = []
Pero dudo mucho que cualquier parche sea aceptado en Django, ya que esto rompe algo que el código explícitamente hace en este momento.
Solo para las notas, puedes modificar 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)
Esto funciona bien para mi.
También podría ser ridículamente hacky al respecto (si no quería preocuparse por anular init
) y proporcionar un valor para el primer elemento que básicamente se ve así:
</a>My non-linked value<a>
Lo sé, lo sé, no es muy bonito, pero tal vez con menos ansiedad por romper algo en otro lado, ya que lo único que estamos haciendo es cambiar el marcado.
Aquí hay un código de muestra sobre cómo funciona esto:
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"
Nota al margen: también puede mejorar la legibilidad de la salida (ya que no desea que sea un enlace) devolviendo return u''%s'' % obj.get_full_name()
que podría ser un poco ordenado según su caso de uso.
Yo quería un visor de registro solo como una lista.
Lo tengo trabajando así:
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, )
Es una especie de mezcla entre ambas respuestas.
Si solo hace self.list_display_links = ()
, mostrará el enlace, De todos modos, porque el código de template-tag
plantilla (templatetags / admin_list.py) comprueba nuevamente para ver si la lista está vacía.
con django 1.6.2 puedes hacer esto:
class MyAdmin(admin.ModelAdmin):
def get_list_display_links(self, request, list_display):
return []
ocultará todos los enlaces generados automáticamente.