Two roles with admin in project #28

Closed
SalahAdDin opened this Issue Aug 25, 2014 · 21 comments

Projects

None yet

2 participants

@SalahAdDin
Contributor

Good Nights, this is a question, not a issue.

I need to do two types of users, and default admin and author user.
An aditional permission for two users, Admin can publish news, author can't.
Author only can created news, and only edit and delect his own news.

A friend say me that for the second restrictions i need per object permissions, but i don't know how do all of this.

Can you help me?

@lambdalisue
Owner

OK. Let say there is an Article model which have 1) title, 2) body, 3) author, 4) published like:

class Article(models.Model):
    title = models.CharField(...)
    body = models.TextField(...)
    author = models.ForeignKey(User, ...)
    published = models.BooleanField(...)

And you want to 1) add, 2) change, 3) delete, 4) publish the news article. So you need the following permissions.

  1. news.add_article (django's default, Non object permission)
  2. news.change_article (django's default)
  3. news.delete_article (django's default)
  4. news.publish_article (you have to add, Non object permission)

Then, use the followings to do what you want

  1. AuthorPermissionLogic
  2. StaffPermissionLogic
  3. permission_required decorator

Like:

class Article(models.Model):
    ...

from permission import add_permission_logic
from permission.logics import AuthorPermissionLogic, StaffPermissionLogic
add_permission_logic(Article, AuthorPermissionLogic(
    field_name='author',
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))
add_permission_logic(Article, StaffPermissionLogic(
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))

Then use permission_required decorator on create, change, delete, publish view like

@permission_required('news.add_article') # It might no be required if anyone can create the article
class ArticleCreateView(CreateView):
    # staff and users (may be everybody?) should have 'news.add_article' permission
    ...

@permission_required('news.change_article')
class ArticleUpdateView(UpdateView):
    # the permission is handled either AuthorPermissionLogic or StaffPermissionLogic
    ...

@permission_required('news.delete_article')
class ArticleDeleteView(DeleteView):
    # the permission is handled either AuthorPermissionLogic or StaffPermissionLogic
    ...

@permission_required('news.publishe_aritcle')
class ArticlePublishView(View):
    # staff users should have 'news.publish_article' permission
    # in the post method, change the 'published' attribute of the specified article
    ....

Finally, give "news.add_article" and "news.publish_article" permissions (non object permissions) to users and admin in django's admin interface or whatever.

@SalahAdDin
Contributor

But, i have a model author how a OnetoOneField with User, you know, custom user, but, i want that default admin user and Author User don't be the same.
And mi model for nes is news :D

And i want that the autor only can edit and delete his own news, no other author's news.

And i don't want create another views, i want use the default admin system for all users.

And, your apps will integrated with de default admin system?

@lambdalisue
Owner

But, i have a model author how a OnetoOneField with User, you know, custom user, but, i want that default admin user and Author User don't be the same.
And mi model for nes is news :D

I'm sorry but I'm a bit confuse with this sentence. What were you trying to say exactly?

And i want that the autor only can edit and delete his own news, no other author's news.

Check the source code of AuthorPermissionLogic. Even you are using a bit different user system, the source code would help you to convert the strategy to fit on the your case.

And i don't want create another views, i want use the default admin system for all users.

Then read the django's document. https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.has_add_permission

class ArticleAdmin(Admin):
    def has_change_permission(self, request, obj=None):
        return request.has_perm('news.change_article', obj)

However, similar codes were written in default method.

https://github.com/django/django/blob/master/django/contrib/admin/options.py#L465

Thus what you need to do is basically nothing.

And, your apps will integrated with de default admin system?

django-permission will extend the default authentication system (django.contrib.auth) but default admin system (django.contrib.admin).
However, django admin (offcourse) use the default authentication system and you want to do some special permission things on the admin system, thus the answer should be YES.

@SalahAdDin
Contributor

First, can you say me please how do yo do this?
pregunta
Put quote and code? I'm searching how do this but i don't find anything, i don't know do this.

Now, my Author model is this: authors.models.py

from django.contrib.auth.models import User
from django.db import models

    # Create your models here.

   class Author(models.Model):

    #Campos del Usuario
    user = models.OneToOneField(User)
    biography = models.TextField(verbose_name='Biografía', blank=True)
    age = models.PositiveIntegerField(verbose_name='Edad', blank=True )
    link_own = models.URLField(verbose_name='Enlace Propio', blank=True)
    is_superuser = False
    can_publish = False


    class Meta:
        ordering=['last_name']
        verbose_name = 'Autor'
        verbose_name_plural='Autores'

        permissions = {

        }

    def __str__(self):
        return '%s %s' % (self.first_name,self.last_name)

That's all, for now, thank you very much for your help!

@lambdalisue
Owner

First, can you say me please how do yo do this?

https://help.github.com/articles/markdown-basics
https://help.github.com/articles/github-flavored-markdown#syntax-highlighting

You are using Extending the existing user model strategy right? Then several codes are not necessary.

class Author(models.Model):
    user = models.OneToOneField(User)
    biography = models.TextField(verbose_name='Biografía', blank=True)
    age = models.PositiveIntegerField(verbose_name='Edad', blank=True )
    link_own = models.URLField(verbose_name='Enlace Propio', blank=True)
    # `is_superuser` is provided in auth.User,
    is_superuser = False
    # `auth.User.is_staff` can be used instaed of `can_publish`
    can_publish = False


    class Meta:
        ordering=['last_name']
        verbose_name = 'Autor'
        verbose_name_plural='Autores'

        permissions = {

        }

    def __str__(self):
        return '%s %s' % (self.first_name,self.last_name)

Ok. I'm guessing but you create author.Author model to represent author, so the auth.User who have auth.User.author are author and others are not right?
If so, the strategy is quite bad strategy. The strategy make your database and other strategy quite complicated and most of other django app which use auth.User (or custom user) won't work.
Extending user model strategy should only be for extending user informations, not for adding different types of user model. There should be the only one user model in the whole project to keep the things simple.

So there are two options.

The first one is re-consider about your current strategy and follow the general strategy.
Usually we represent Admin and Author like

  1. Admin is a auth.User whom is_staff is True (not is_superuser)
  2. Author is a auth.User who is stored in news.Article.author field (see previous comment)

This is quite common way so if you chose this strategy, django-permissin (and other plugins which use authentication system) works without patient.
In this strategy, use the code what I wrote in this discussion previously.

If somehow you have to use the strategy, you should write your special permission logic like the below. Remember that django-permission is powerful enough to do this kind of hackish things but most of other plugins won't, mean that you might face to other problems in the future.

class SpecialPermissionLogic(PermissinoLogic):
    def has_perm(self, user_obj, perm, obj=None):
        if not user_obj.is_authenticated():
             return False
        add_permission = self.get_full_permission_string('add')
        change_permission = self.get_full_permission_string('change')
        delete_permission = self.get_full_permission_string('delete')
        publish_permission = self.get_full_permission_string('publish')

        try:
            author = Author.objects.get(user=user_obj)
            if obj is None:
                # Non object permission. Author should have add/change/delete
                # but publish
                if author.is_superuser:
                    # Admin user should have publish permission as well, mean
                    # Admin user have all permissions
                    return True
                else:
                    return perm in (add_permission,
                                    change_permission,
                                    delete_permission)
            else:
                # Object permission
                if obj.author == user_obj:
                    # Author of the object should have change/delete
                    return perm in (change_permission, delete_permission)
            return False
        except ObjectDoesNotExist:
            return False
@SalahAdDin
Contributor

Well, i don't speak english very good, but step by step i understand you.

First, i need the author user can other fields, can you see in my model, is very important.

If i understand you, my model is very useful, but I must not use my model to set the permissions, right?
But, i don't need aditional fields for the admin, the aditional fields is for Author only.

My news model is this: news.models.py

from django.db import models
from django.template.defaultfilters import slugify
from django.core.urlresolvers import reverse

from authors.models import Author
from subtopic.models import Subtopic
from topic.models import Topic
from keywords.models import KeyWord

#Global Vars


# Create your models here.

class New(models.Model):
    title = models.CharField(verbose_name='Título', max_length=255, unique=True)
    topic = models.ForeignKey(Topic, verbose_name='Tema', related_name='news', )
    subtopic = models.ForeignKey(Subtopic, verbose_name='Subtema', related_name='news') #Filtramos por Opinion para sacar todas las columnas
    author = models.ForeignKey(Author, verbose_name='Autor', related_name='news')
    keywords = models.ManyToManyField(KeyWord, blank=True, verbose_name='Palabras Clave', related_name='news')
    created_date = models.DateTimeField(auto_now_add=True, verbose_name='Fecha y Hora')
    place = models.CharField(max_length=255, verbose_name='Lugar')
    content = models.TextField(verbose_name='Contenido')
    source = models.URLField(verbose_name='Fuente', blank=True)
    slug =  models.SlugField(verbose_name='Slug', max_length=100, unique=True)
    is_published = models.BooleanField(verbose_name='Publicada', default=False,) #Nueva, para verificar si se publica o no
    times_viewed = models.PositiveIntegerField(default=0, editable=False, verbose_name='Veces Vista' )

    class Meta:
        ordering=['-created_date']
        verbose_name_plural='Noticias'

    #@models.permalink
    #def get_absolute_url(self):
    #    return ('news.views.New_view', None, {'year': self.dateTime.year,
    #    'month': self.dateTime.strftime('%m'),
    #    'day': self.dateTime.strftime('%d'),
    #    'slug': self.slug})

    def first_image(self):
        return self.images.first() # Siendo images el related_name en Image

    def first_video(self):
        return self.video.first()

    def get_absolute_url(self):
        return reverse ('NewsDefaultView', args = [str(self.created_date.strftime("%Y")), str(self.created_date.strftime("%m")), str(self.created_date.strftime("%d")), str(self.slug)])

    def get_all_keys(self):
        keys_list = self.keywords.values_list('name', flat=True)
        return str(keys_list).strip("'[]'").replace("'",'')

    #def is_published(self): devuelve si la noticia ha sido pulicada para que salga en la lista de noticias

    def __str__(self):
        return self.title

    def save(self):  #Definir metodo para guardar, validar y otros metodos del Slug
        super(New, self).save() #Solo con este me funciona correctamente
        if not self.id:
            self.slug = slugify(self.title)
        super(New, self).save()

from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver

from django.contrib.sessions.models import Session
@receiver(post_save)
def clear_cache(sender, **kwargs):
    if sender != Session:
        cache.clear()
@lambdalisue
Owner

author = models.ForeignKey(Author, verbose_name='Autor', related_name='news') is wrong. You should link to auth.User (or custom user model) while your Author model is a just extra information of the user model.

But, i don't need aditional fields for the admin, the aditional fields is for Author only.

Then I would like to create AuthorInfo model to extend user information and use auth.User in everywhere. Then you can get the user instance from request.user in views, and author information with request.user.authorinfo in views.

Remember that there should be only one model which represent Users in your project. In your case, there are two models (auth.User and Author) and that makes things a little bit complicated.

@SalahAdDin
Contributor

I have to do a custom User Model for my Author?
Now, i'm understanding now.
I have to my permission in a permission.py file in my app, truht?

Remember that there should be only one model which represent Users in your project. In your case, there are two models (auth.User and Author) and that makes things a little bit complicated.

I don't understand this, i need create other model user for Author? ca't be auth.User.Author?

author = models.ForeignKey(User, verbose_name='Autor', related_name='news')

In my news model write this, right?

Then my author model have to be so:

class Author(models.Model):

    #Campos del Usuario
    photo = models.ImageField(verbose_name = 'Profile photo', blank = True, null=True)
    user = models.OneToOneField(User)
    biography = models.TextField(verbose_name='Biografía', blank=True, null=True)
    age = models.PositiveIntegerField(verbose_name='Edad', blank=True, null=True )
    link_own = models.URLField(verbose_name='Enlace Propio', blank=True, null=True)


    class Meta:
        ordering=['last_name']
        verbose_name = 'Autor'
        verbose_name_plural='Autores'

    def __str__(self):
        return '%s %s' % (self.first_name,self.last_name)

    def photo_display(self):
        if self.photo:
            return '<img src="%s" alt="%s" title="%s">' % (get_thumbnail(self.photo, '100x100').url, self.first_name, self.first_name)
        else:
            return '/statics/static/tiempo_turco/images/no-profile.png'

right?

@lambdalisue
Owner

I have to do a custom User Model for my Author?

Basically no but probably yes in your case while you are trying to use your Author model as a user model in news.Article model.
It is mainly because django use auth.User (or a custom user model) in everywhere. For example, request instance of views' attribute has user attribute thus you can access "accessed user" with the following code in your view

def view(request, *args, **kwargs):
    if request.user.is_authenticated():
        # do thins for authenticated user
    else:
        # do thins for anonymous user

This is a general idea of django so most of django app, including django-permission, follow this rule.
Thus thins would get be complicated if you use your non custom user model, Author, to specify author of your news.Article. I said non because the Author model you showed me did not follow the inheritance rule which is explained in django documentation.

I don't understand this, i need create other model user for Author? ca't be auth.User.Author?

I said AuthorInfo because the definition of Author is

a person who writes a novel, poem, essay, etc.; the composer of a literary work, as distinguished from a compiler, translator, editor, or copyist. -- Dictionary.com"

but your Author model actually mean the extra informations for author. Thus I renamed that to AuthorInfo to re-define your model more clear. May be it makes you confused, sorry.
It is OK if you want to use the word "Author" for representing the extra informations, that's not my business.

In my news model write this, right?

Your model should be following if you don't use custom user model strategy.

class Author(models.Model):

    #Campos del Usuario
    photo = models.ImageField(verbose_name = 'Profile photo', blank = True, null=True)
    # user = models.OneToOneField(User)
    # primary_key=True should be specified
    user = models.OneToOneField(User, primary_key=True)
    biography = models.TextField(verbose_name='Biografía', blank=True, null=True)
    age = models.PositiveIntegerField(verbose_name='Edad', blank=True, null=True )
    link_own = models.URLField(verbose_name='Enlace Propio', blank=True, null=True)


    class Meta:
        ordering=['last_name']
        verbose_name = 'Autor'
        verbose_name_plural='Autores'

    def __str__(self):
        # return '%s %s' % (self.first_name,self.last_name)
        # the line above should be ...
        return '%s %s' % (self.user.first_name,self.user.last_name)

    def photo_display(self):
        if self.photo:
            # you should use `self.user.first_name`
            return '<img src="%s" alt="%s" title="%s">' % (get_thumbnail(self.photo, '100x100').url, self.first_name, self.first_name)
        else:
            return '/statics/static/tiempo_turco/images/no-profile.png'
@lambdalisue
Owner

Ok make it simple. I have a simple advice. Follow the steps below

  1. Use auth.User everywhere. Forget about your Author model. Forget about the extra informations for instance.
  2. Apply django-permission or other authentication apps (like django-gurdian).
  3. After all, add Author (or AuthorInfo or whatever) model which have user field to link auth.User to add extra informations about author (Like a model you post previously).
  4. DO NOT WRITE author = models.ForeginKey(Author, ...) anywhere.

With these steps, you will be forced to follow the django's authentication rules. So things will get more simple.

@SalahAdDin
Contributor

Yes, i'm understand you now,
Wich's better, django-permission o django-guardian?

Ok, my author model is now AuthorInfo :D, now, what have to do?

@lambdalisue
Owner

Wich's better, django-permission o django-guardian?

Depends. django-permission is a logic based permission system while django-gurdian is a database based permission system.

django-permission is much more flexible than django-gurdian. If you need complicated permission system, probably you would prefer django-permission. However, a permission logic will be executed on user.has_perm() (django-permission) which might be slower than finding a corresponding permission from database (django-gurdian). The basic strategy is completely different thus you actually cannot compare. Additionally, you can use both in a project theoretically (I haven't try thus theoretically).

Ok, my author model is now AuthorInfo :D, now, what have to do?

Well I already explained.

@SalahAdDin
Contributor

Yes but i don't understand you.
Ready installed django-permission, but, what have i'll do? i don't know,
Where i have to created perms.py?
In news, right?

I'm foolish newie.

@lambdalisue
Owner

Preparation

https://github.com/lambdalisue/django-permission#configuration

Usage

Simple

Write the following code in your news/models.py

class Article(models.Model):
    ...

from permission import add_permission_logic
from permission.logics import AuthorPermissionLogic, StaffPermissionLogic
add_permission_logic(Article, AuthorPermissionLogic(
    field_name='author',
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))
# It will use django's staff attribute. While you said you want to distinguish admin and
# django's staff, you might want to use GroupInPermissionLogic or your own permission
# logic here.
add_permission_logic(Article, StaffPermissionLogic(
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))

Done while you don't need your own views.

Advanced

Follow https://github.com/lambdalisue/django-permission#autodiscovery
perms.py should be on the application directory that mean news/perms.py.

Examples in real world

#26 (comment)

@SalahAdDin
Contributor

Ok, but, this works with django admin? and how can created publish_permission? :D

Now i have this

from django.db import models
from django.template.defaultfilters import slugify
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User

from authors.models import Author
from subtopic.models import Subtopic
from topic.models import Topic
from keywords.models import KeyWord

#Global Vars


# Create your models here.

class New(models.Model):
    author = models.ForeignKey(User, verbose_name='Autor', related_name='news')
    content = models.TextField(verbose_name='Contenido')
    created_date = models.DateTimeField(auto_now_add=True, verbose_name='Fecha y Hora')
    is_published = models.BooleanField(verbose_name='Publicada', default=False,) #Nueva, para verificar si se publica o no
    keywords = models.ManyToManyField(KeyWord, blank=True, verbose_name='Palabras Clave', related_name='news')
    place = models.CharField(max_length=255, verbose_name='Lugar')
    source = models.URLField(verbose_name='Fuente', blank=True)
    subtopic = models.ForeignKey(Subtopic, verbose_name='Subtema', related_name='news') #Filtramos por Opinion para sacar todas las columnas
    times_viewed = models.PositiveIntegerField(default=0, editable=False, verbose_name='Veces Vista' )
    title = models.CharField(verbose_name='Título', max_length=255, unique=True)
    slug =  models.SlugField(verbose_name='Slug', max_length=100, unique=True)
    topic = models.ForeignKey(Topic, verbose_name='Tema', related_name='news', )

    class Meta:
        ordering = ['-created_date']
        verbose_name_plural = 'Noticias'
        verbose_name = 'Noticia'

    #@models.permalink
    #def get_absolute_url(self):
    #    return ('news.views.New_view', None, {'year': self.dateTime.year,
    #    'month': self.dateTime.strftime('%m'),
    #    'day': self.dateTime.strftime('%d'),
    #    'slug': self.slug})

    def first_image(self):
        return self.images.first() # Siendo images el related_name en Image

    def first_video(self):
        return self.videos #Devuelve el video de la imagen

    def get_absolute_url(self):
        return reverse ('NewsDefaultView', args = [str(self.created_date.strftime("%Y")), str(self.created_date.strftime("%m")), str(self.created_date.strftime("%d")), str(self.slug)])

    def get_all_keys(self):
        keys_list = self.keywords.values_list('name', flat=True)
        return str(keys_list).strip("'[]'").replace("'",'')

    #def is_published(self): devuelve si la noticia ha sido pulicada para que salga en la lista de noticias

    def __str__(self):
        return self.title

    def save(self):  #Definir metodo para guardar, validar y otros metodos del Slug
        super(New, self).save() #Solo con este me funciona correctamente
        if not self.id:
            self.slug = slugify(self.title)
        super(New, self).save()

from permission import add_permission_logic
from permission.logics import AuthorPermissionLogic, StaffPermissionLogic
add_permission_logic(New, AuthorPermissionLogic(
    field_name='author',
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))
add_permission_logic(New, StaffPermissionLogic(
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))

from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver

from django.contrib.sessions.models import Session
@receiver(post_save)
def clear_cache(sender, **kwargs):
    if sender != Session:
        cache.clear()
@lambdalisue
Owner

this works with django admin?

Yes but you have to create youw own "publish" command. Read the django's document

@SalahAdDin
Contributor

well, my publish commant is a boleean field, admin can push this in truth, but Author can't it.

@lambdalisue
Owner

well, my publish commant is a boleean field, admin can push this in truth, but Author can't it.

Well the publish permission is not object permission and you can do what you want with django's builtin permission system. Read the django's document carefully.

Or write your own permission logic. Read what I said and the links I posted carefully then you will know how.

@SalahAdDin
Contributor

Well, my models now is this:

from django.db import models
from django.template.defaultfilters import slugify
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User

from authors.models import Author
from subtopic.models import Subtopic
from topic.models import Topic
from keywords.models import KeyWord

#Global Vars


# Create your models here.

class New(models.Model):
    author = models.ForeignKey(User, verbose_name='Autor', related_name='news')
    content = models.TextField(verbose_name='Contenido')
    created_date = models.DateTimeField(auto_now_add=True, verbose_name='Fecha y Hora')
    is_published = models.BooleanField(verbose_name='Publicada', default=False,) #Nueva, para verificar si se publica o no
    keywords = models.ManyToManyField(KeyWord, blank=True, verbose_name='Palabras Clave', related_name='news')
    place = models.CharField(max_length=255, verbose_name='Lugar')
    source = models.URLField(verbose_name='Fuente', blank=True)
    subtopic = models.ForeignKey(Subtopic, verbose_name='Subtema', related_name='news') #Filtramos por Opinion para sacar todas las columnas
    times_viewed = models.PositiveIntegerField(default=0, editable=False, verbose_name='Veces Vista' )
    title = models.CharField(verbose_name='Título', max_length=255, unique=True)
    slug =  models.SlugField(verbose_name='Slug', max_length=100, unique=True)
    topic = models.ForeignKey(Topic, verbose_name='Tema', related_name='news', )

    class Meta:
        ordering = ['-created_date']
        verbose_name_plural = 'Noticias'
        verbose_name = 'Noticia'

        permissions = (
            ("can_publish", "Can publish news"),
        )

    #@models.permalink
    #def get_absolute_url(self):
    #    return ('news.views.New_view', None, {'year': self.dateTime.year,
    #    'month': self.dateTime.strftime('%m'),
    #    'day': self.dateTime.strftime('%d'),
    #    'slug': self.slug})

    def first_image(self):
        return self.images.first() # Siendo images el related_name en Image

    def first_video(self):
        return self.videos #Devuelve el video de la imagen

    def get_absolute_url(self):
        return reverse ('NewsDefaultView', args = [str(self.created_date.strftime("%Y")), str(self.created_date.strftime("%m")), str(self.created_date.strftime("%d")), str(self.slug)])

    def get_all_keys(self):
        keys_list = self.keywords.values_list('name', flat=True)
        return str(keys_list).strip("'[]'").replace("'",'')

    #def is_published(self): devuelve si la noticia ha sido pulicada para que salga en la lista de noticias

    def __str__(self):
        return self.title

    def save(self):  #Definir metodo para guardar, validar y otros metodos del Slug
        super(New, self).save() #Solo con este me funciona correctamente
        if not self.id:
            self.slug = slugify(self.title)
        super(New, self).save()

from permission import add_permission_logic
from permission.logics import AuthorPermissionLogic, StaffPermissionLogic
add_permission_logic(New, AuthorPermissionLogic(
    field_name='author',
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))
add_permission_logic(New, StaffPermissionLogic(
    any_permission=False,
    change_permission=True,
    delete_permission=True,
))

from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver

from django.contrib.sessions.models import Session
@receiver(post_save)
def clear_cache(sender, **kwargs):
    if sender != Session:
        cache.clear()
@lambdalisue
Owner

Again, read the django's document, or ask on google group django users

This is django-permission's issue (or questions). I'm welcome to answer the questions which related to django-permission but others. I'm sorry but your questions (how to create permissions, how to create admin commands, how to check permissions on admin, or so on) are completely not related to django-permission now. These questions are non of my business.

@lambdalisue lambdalisue closed this Nov 6, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment