2010-04-10 51 views
22

Supongo que un problema similar habría sido discutido aquí, pero no pude encontrarlo.Cómo limitar los campos en django-admin según el usuario?

Supongamos que tengo un Editor y un Supervisor. Quiero que el Editor pueda agregar nuevo contenido (por ejemplo, una publicación de noticias) pero antes de la publicación debe ser reconocido por el Supervisor.

Cuando el editor enumera todos los elementos, quiero establecer algunos campos en los modelos (como un campo 'ack') como de solo lectura (para que pueda saber qué se ha aprobado y qué espera la aprobación), pero El supervisor debe poder cambiar todo (list_editable sería perfect)

¿Cuáles son las posibles soluciones a este problema?

Respuesta

15

Creo que hay una manera más fácil de hacerlo:

de visitantes que tenemos el mismo problema del Blog -post

blog/models.py:

Class Blog(models.Model): 
    ... 
    #fields like autor, title, stuff.. 
    ... 

class Post(models.Model): 
    ... 
    #fields like blog, title, stuff.. 
    ... 
    approved = models.BooleanField(default=False) 
    approved_by = models.ForeignKey(User) 
    class Meta: 
     permissions = (
      ("can_approve_post", "Can approve post"), 
     ) 

Y la magia está en el administrador:

blog/admin.py:

... 
from django.views.decorators.csrf import csrf_protect 
... 
def has_approval_permission(request, obj=None): 
    if request.user.has_perm('blog.can_approve_post'): 
     return True 
    return False 

Class PostAdmin(admin.ModelAdmin): 
    @csrf_protect 
    def changelist_view(self, request, extra_context=None): 
     if not has_approval_permission(request): 
      self.list_display = [...] # list of fields to show if user can't approve the post 
      self.editable = [...] 
     else: 
      self.list_display = [...] # list of fields to show if user can approve the post 
     return super(PostAdmin, self).changelist_view(request, extra_context) 
    def get_form(self, request, obj=None, **kwargs): 
     if not has_approval_permission(request, obj): 
      self.fields = [...] # same thing 
     else: 
      self.fields = ['approved'] 
     return super(PostAdmin, self).get_form(request, obj, **kwargs) 

De esta manera puede usar la API de custom permission en django, y puede anular los métodos para guardar el modelo u obtener el conjunto de consulta si es necesario. En el metid has_approval_permission puede definir la lógica de cuándo el usuario puede o no hacer algo.

+0

que probablemente significaba self.exclude = [ 'aprobado'] en get_form() y también hay pequeño fallo en changelist_view() ;) Gracias, esto se ve muy bien y se combina con trozos de T. La respuesta de Stone, es exactamente lo que he estado buscando :) – minder

+0

¿y si obtengo 'el objeto no tiene ningún atributo 'COOKIES''? – andi

+0

¿los permisos deben registrarse en algún otro lugar para ser visibles en el módulo de administración? – andi

2

Tengo un sistema como este en un proyecto que estoy terminando. Habrá mucho trabajo para poner esto juntos, pero aquí están algunos de los componentes que he tenido que hacer mi trabajo del sistema:

  • Se necesita una manera de definir un editor y un supervisor. Las tres formas en que esto se puede hacer son 1.) al tener un campo M2M que define al supervisor [y suponiendo que todos los demás con permiso para leer/escribir son editores], 2.) crear 2 nuevos modelos de usuario que hereden del usuario [ probablemente más trabajo de lo necesario] o 3.) use la habilidad django.auth para tener una clase UserProfile. El método # 1 es probablemente el más razonable.

  • Una vez que pueda identificar qué tipo es el usuario, necesita una forma de aplicar genéricamente la autorización que está buscando. Creo que la mejor ruta aquí es probablemente un modelo de administrador genérico.

  • Por último, necesitará algún tipo de modelo "principal" que tenga los permisos para lo que necesite moderarse. Por ejemplo, si tenía un modelo de Blog y un modelo de BlogPost (suponiendo que hay varios blogs dentro del mismo sitio), Blog es el modelo principal (puede contener los permisos de quién aprueba qué). Sin embargo, si tiene un solo blog y no hay un modelo principal para BlogPost, necesitaremos un lugar donde almacenar los permisos. He encontrado que el ContentType funciona bien aquí.

Aquí hay algunas ideas en el código (no probadas y más conceptuales que las reales).

Crea una nueva aplicación llamada 'moderada' que contendrá nuestros elementos genéricos.

moderated.models.py

class ModeratedModelParent(models.Model): 
    """Class to govern rules for a given model""" 
    content_type = models.OneToOneField(ContentType) 
    can_approve = models.ManyToManyField(User) 

class ModeratedModel(models.Model): 
    """Class to implement a model that is moderated by a supervisor""" 
    is_approved = models.BooleanField(default=False) 

    def get_parent_instance(self): 
     """ 
     If the model already has a parent, override to return the parent's type 
     For example, for a BlogPost model it could return self.parent_blog 
     """ 

     # Get self's ContentType then return ModeratedModelParent for that type 
     self_content_type = ContentType.objects.get_for_model(self) 
     try:    
      return ModeratedModelParent.objects.get(content_type=self_content_type) 
     except: 
      # Create it if it doesn't already exist... 
      return ModeratedModelParent.objects.create(content_type=self_content_type).save() 

    class Meta: 
     abstract = True 

Así que ahora debería tener un poco reutilizable genérico, de código que podemos identificar el permiso para un determinado modelo (que se identificará el modelo por su tipo de contenido).

A continuación, se puede poner en práctica nuestras políticas en el admin, de nuevo a través de un modelo genérico:

moderated.admin.py

class ModeratedModelAdmin(admin.ModelAdmin): 

    # Save our request object for later 
    def __call__(self, request, url): 
     self.request = request 
     return super(ModeratedModelAdmin, self).__call__(request, url) 

    # Adjust our 'is_approved' widget based on the parent permissions 
    def formfield_for_dbfield(self, db_field, **kwargs): 
     if db_field.name == 'is_approved': 
      if not self.request.user in self.get_parent_instance().can_approve.all(): 
       kwargs['widget'] = forms.CheckboxInput(attrs={ 'disabled':'disabled' }) 

    # Enforce our "unapproved" policy on saves 
    def save_model(self, *args, **kwargs): 
     if not self.request.user in self.get_parent_instance().can_approve.all(): 
      self.is_approved = False 
     return super(ModeratedModelAdmin, self).save_model(*args, **kwargs) 

Una vez que estos son de configuración y de trabajo, podemos volver Úselos en muchos modelos como he encontrado una vez que agrega permisos estructurados para algo como esto, lo desea fácilmente para muchas otras cosas.

Digamos, por ejemplo, que tiene un modelo de noticias, simplemente tendría que heredarlo del modelo que acabamos de hacer y está listo.

# in your app's models.py 
class NewsItem(ModeratedModel): 
    title = models.CharField(max_length=200) 
    text = models.TextField() 


# in your app's admin.py 
class NewsItemAdmin(ModeratedModelAdmin): 
    pass 

admin.site.register(NewsItem, NewsItemAdmin) 

estoy seguro que he cometido algunos errores de código y errores en allí, pero espero que esto le puede dar algunas ideas para actuar como una plataforma de lanzamiento para lo que decida implementar.

Lo último que tienes que hacer, que te dejo, es implementar el filtrado para los artículos is_approved. (. Es decir, usted no desea que los elementos no aprobados que se enumeran en la sección de noticias, ¿verdad?)

2

El problema con el enfoque descrito por @diegueus9 es que al ModelAdmin le gusta un singleton y es no instanciado para cada solicitud. Esto significa que cada solicitud modifica el mismo objeto ModelAdmin al que otras solicitudes acceden, lo que no es ideal. A continuación se presenta las soluciones propuestas por @ diegueus9:

# For example, get_form() modifies the single PostAdmin's fields on each request 
... 
class PostAdmin(ModelAdmin): 
    def get_form(self, request, obj=None, **kwargs): 
     if not has_approval_permission(request, obj): 
      self.fields = [...] # list of fields to show if user can't approve the post 
     else: 
      self.fields = ['approved', ...] # add 'approved' to the list of fields if the user can approve the post 
... 

Un enfoque alternativo sería pasar fields como palabra clave arg con el método de los padres get_form() así:

... 
from django.contrib.admin.util import flatten_fieldsets 

class PostAdmin(ModelAdmin): 
    def get_form(self, request, obj=None, **kwargs): 
     if has_approval_permission(request, obj): 
      fields = ['approved'] 
      if self.declared_fieldsets: 
       fields += flatten_fieldsets(self.declared_fieldsets) 

      # Update the keyword args as needed to allow the parent to build 
      # and return the ModelForm instance you require for the user given their perms 
      kwargs.update({'fields': fields}) 
     return super(PostAdmin, self).get_form(request, obj=None, **kwargs) 
... 

De esta manera, no se está modificando el singleton PostAdmin en cada solicitud; simplemente está pasando los argumentos de palabra clave apropiados necesarios para compilar y devolver ModelForm desde el elemento primario.

Es probable que vale la pena mirar el método get_form() sobre la base de ModelAdmin para más información: https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L431

2

A partir de Django 1.7, ahora puede usar el gancho get_fields lo que hace que sea mucho más sencillo de implementar campos condicionales.

class MyModelAdmin(admin.ModelAdmin): 
    ... 

    def get_fields(self, request, obj=None): 
     fields = super(MyModelAdmin, self).get_fields(request, obj) 
     if request.user.is_superuser: 
      fields += ('approve',) 

     return fields 
+0

Gran técnica pero 'fields = self.fields' resultó en' None' para mí.Cambié esa línea a 'fields = super (MyModelAdmin, self) .get_fields (request, obj)' y funcionó como un amuleto. – PaulR

+0

Buena captura. Actualicé mi respuesta. – mixxorz

Cuestiones relacionadas