Create user roles and permissions extending the existing User model - django

I want to create user roles (member, partner, admin, etc) extending the existing User model.
class Member(models.Model):
user = models.OneToOneField(User)
#custom fields
What is the best way to create and assign the permissions in this case?
I see many ways to do it:
Programmatically creating permissions:
permission = Permission.objects.create(codename='can_publish',
name='Can Publish Posts',
content_type=content_type)
Included in a model object:
class Task(models.Model):
...
class Meta:
permissions = (
("view_task", "Can see available tasks"),
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
)
is it harcoded? But I want to include the permissions in the Member class, I don't know if it's a good way to do it.
How can I add the Member class to member group? Or do I have to add permissions to member group?:
view_task = Permission.objects.get(name='view_task')
membergroup.permissions.add(view_task)
and after that add user by user from Member class to member group?:
user = User.objects.get(username='test')
membergroup = Group.objects.get(name='member')
user.groups.add(membergroup)
So, I want to put the permissions in Member class and put the class in a member group. I don't know if it's ok.
I want to know if a user is in a member group for example. I see a way but I'm not sure if it is good. Because I have to retrive all user from the group each time:
users_in_group = Group.objects.get(name="member").user_set.all()
if user in users_in_group:

I'm not an expert with permissions in Django, but as far as I know, the main tasks to do (not especially in this order, you do what you need when you need it) are the following:
You create permissions
You create groups
You link users to your groups
You link permissions to your groups
Using this "pattern" allows you to be flexible giving users the ability to be in 0->N groups and the groups being linked to 0->N permissions.
This part of the doc explains how to use permissions with examples :
https://docs.djangoproject.com/en/dev/topics/auth/default/#topic-authorization
That being said, be careful to not mix model extension with permissions:
You extends User model when you want to add special attributes on it. For example, ClientUser and BusinessSeller could be User model extensions because you do want to have different attributes on them. This is not related to permissions.
This means you can use permissions on users without extending the User model.
Not sure this answers totally your question, but I hope it makes things clearer to you.

Related

Django Wagtail dynamic Menu's based on User and User Role

Using Django-Wagtail and Wagtail Menu's. I want to build a system with the following types of characteristics.
I have a class of user (Vendor, Supplier)
Each created user would need to be one of these two classes.
Each class of user has different roles and the roles differ from class to class. e.g.
Vendor: finance, stock, admin
Supplier: finance, stock, admin, driver.
What I have done is create a class called UserType and both "Vendor" & "Supplier" inherit from this class. Then added a field to the User class that selects which type of user they are.
class UserType(model.Model):
common fields.......
class Vendor(UserType):
child_vendor_only_fields.....
class Supplier(UserType):
child_supplier_only_fields.....
class User(AbstractUser):
userVar = models.ForeignKey(UserType, on_delete=models.SET_NULL, null=True, blank=True)
I have also used the wagtail admin to create custom roles for each of these classes as described above.
Clearly what "Vendors" can create, read, update and delete varies from that of a "Supplier". And still within that what the finance role can do within the "Vendor" differs from what "Stock" can do.
How would I create a dynamic menu that displays different menu's for these permutations?
My initial thoughts were inspired by this. Simply adding a choicefield
USER_TYPE_CHOICES = (
('Vendor', 'Vendor'),
('Supplier', 'Suplier'))
class GenericPage(MenuPage):
"""
This model will gain the fields, methods and `setting_panels` attribute
from `MenuPage`, but `settings_panels` is being overridden to include
other fields in the `Settings` tab.
"""
availableTo = CharField(choices=USER_TYPE_CHOICES,)
# 'menupage_panel' is a collapsible `MultiFieldPanel` with the important
# fields already grouped together, making it easy to include in custom
# panel definitions, like so:
settings_panels = [
FieldPanel('custom_settings_field_one'),
menupage_panel
]
I have two questions:
Am I on the correct path for generating custom menu's for type of users? If so how would I then do the same for roles under type of users?
In terms of CRUD should I simply implement a check each time a user does a crud action whether or whether not they can commit that action pragmatically?
For CRUD, I'm not a fan of giving someone the option to do something then tell them that option isn't allowed after the fact. They might have spent half an hour creating/editing that page then find out they've wasted that time. Better to tell them on page load.
For user type, I'd just create those as roles that you add the users to. Then you just have a simple test for the user.groups to see what role they have.
You could easily develop a custom restricted panel for each of the panel types you need to restrict, easier if they're all FieldPanels. In the BoundPanel class you have access to the request object, so you can pull the user object from that at runtime and and decide what to do then.
The Panel.BoundPanel has a method is_shown(), so you can create a custom panel that inherits FieldPanel, override that method to test if the request user in an authorised list parameter and set true/false accordingly.
# restricted_field_panel.py
from wagtail.admin.panels import FieldPanel
class RestrictedFieldPanel(FieldPanel):
def __init__(self, field_name, authorised_groups, **kwargs):
self.field_name = field_name
self.authorised_groups = authorised_groups if isinstance(authorised_groups, list) else [authorised_groups]
super().__init__(self.field_name, **kwargs)
def clone_kwargs(self):
kwargs = super().clone_kwargs()
kwargs.update(
authorised_groups=self.authorised_groups
)
return kwargs
class BoundPanel(FieldPanel.BoundPanel):
def is_shown(self):
show_field = super().is_shown()
is_authorised = self.request.user.groups.get_queryset().filter(name__in=self.panel.authorised_groups).exists()
return (show_field and is_authorised)
In the page model:
content_panels = Page.content_panels + [
RestrictedFieldPanel('some_vendor_field', 'Vendors'),
RestrictedFieldPanel('some_supplier_field', 'Suppliers'),
....
]
For the authorised groups, you might want to append a site admin role by default so that site admins will always see any restricted panel regardless.
Other panel types you'd need to inherit and do test wherever it's doing the rendering.

Wagtail per page user permission

My task is to enable users to edit their own page.
I see how to give permissions to a particular page to a group, but how do I do this for the user?
I'd tried use django guardian:
UserObjectPermission.objects.assign_perm('change_custompage', user, obj=custompage)
but this wasn't given me results
I also found the wagtailcore_grouppagepermission table and realized that the permissions principle is not the same as used in django auth_permission
I want to give permissions to edit, and access wagtail admin, I do not need anything else
In this particular task, wagtail I need because of the struct_blocks, I think this is a terrific concept.
Now I see two options: either an override template and the ability to use all the power of the struct_block outside the wagtail, or something to solve with permissions.
Two possibilities:
Create a group for each user - might not seem like a very sophisticated approach, but easily achievable with a bit of scripting...
Put your users in a group that has add permission, but not edit permission, on the parent page that contains all of your users' pages. In Wagtail's permission model http://docs.wagtail.io/en/v1.10.1/topics/permissions.html, 'add' permission also includes the ability to edit pages that you have created yourself. (If you'd rather have the pages created in advance, rather than having your users create them, then you need to set the owner field of the page record to the relevant user.)
Thanks to the help and advice of #gasman, I did this as follows:
Created two models:
first:
class PersonsIndexPage(RoutablePageMixin, Page):
...
#route(r'^$')
def index_view(self, request):
pages = PersonPage.objects.live().all()
return render(request, 'myapp/person_index_page.html', {
'page': self,
'pages': pages,
})
...
subpage_types = ['PersonPage']
second:
class PersonPage(Page):
...
parent_page_types = ['PersonsIndexPage']
subpage_types = []
For the group add access to wagtail admin, as well as gave permissions to create PersonsIndexPage
Since I create personal pages from Django admin, I created a simple action that creates a page for selected users:
I do not know if bulk_create is possible
def create_person_page(modeladmin, request, queryset):
root_page = PersonIndexPage.objects.first()
for user in queryset:
page = PersonPage(title=username), owner=user)
root_page.add_child(instance=page)
create_person_page.short_description = 'Create person page'

ViewFlow and django-guardian

I want to make use of django-guardian's object permissions and grant specific rights for specific users to one or more Django users.
I have tried to add some permissions to my Process class like this:
class TestProcess(Process):
title = models.CharField(max_length=64)
something = models.ForeignKey(ForInheritage, null=True, on_delete=models.CASCADE)
no_approval = models.BooleanField(default=False)
approved = models.BooleanField(default=False)
def something_is_approved(self):
try:
return self.something.approved
except:
return None
class Meta:
permissions = (
('view_process', 'View Process'),
)
Unfortunately this causes viewflow to immediately throw an error after starting runserver:
File "/home/me/.virtualenvs/viewflow3/lib/python3.4/site-packages/viewflow/mixins.py", line 253, in ready
self.flow_class.process_class._meta.permissions.append(
AttributeError: 'tuple' object has no attribute 'append'
My initial plan was to subclass Start and View flow classes to change how the Permission function, that is inherited from the PermissionMixin, works. But this seems to be more work than just this, too.
django-guardian is already mentioned in one of the cookbook sections here but currently leads to a 404 page.
What would be the recommended/cleanest way to use guardian permissions on Processes and Tasks?
Your specific problem happens b/c you specify permissions like a tuple, try list instead
class Meta:
permissions = [
('view_process', 'View Process'),
]
Viewflow already adds the 'view' and 'manage' permissions so you can reuse them.
But further restriction per-process view permissions on the object level with django-guardian is not very practical. On each new process creation, in a start view, you will have to grant view permission to all process participant. That leads to hudge permission table grows and slow down the permissions lookup.
The reasonable use case for the object-level permission could be something like to restrict user access to a task based on a user department, for example.
deliver = flow.View(
views.deliver
).Permission(
'parcel.land_on_planet',
obj=lambda process: process.department
).Next(this.report)

User/Group permission class

I want to create a single class, UserGroup, that will contain Users and Groups. In the end I want to be able to give permission to some task based on whether the given User is in the Users M2M or is in the Groups M2M. I understand there are permissions in the Groups but I want to be able to give Permission to a single user (possibly) and I want a consistent interface. The class definition is (I have actually defined Member as User):
class UserGroup( models.Model ):
users = models.ManyToManyField( Member )
groups = models.ManyToManyField( Group )
And then in my views.py I think I will have something like:
def is_in( member ):
## Check to see if they are in the User m2m
if members.objects.filter(pk=member.id).exists():
return True
## Check to see if they are in the Group m2m
if <something>:
return True
## Otherwise return false.
return False
I think this makes sense. What I am not sure of is an efficient method of checking to see if the member is in one of the groups in the Groups M2M.
(And if anyone wants to give me constructive comments on the whole class I would be happy to takes those too).
Django's admin interface and base User model already allows for this. Users can be added to groups and both groups/users can be given permissions. If you have extra needs beyond what is built-in you can start to override User or Group model methods.
In Django, how do I check if a user is in a certain group?
User groups and permissions
When viewing the User object in your admin, see the User Permissions sections. These are settings you can set per user that override the permissions set for the user by their group memberships.

Dynamically created proxy models do not have listed permissions on admin site

Here is my models.py:
class Item(models.Model):
# ... some irrelevent fields ...
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_lenght=30)
category_id = models.IntegerField()
Tag is actually a general-purpose name. Each item has many different type of tags - currently there are four types: team tags, subject tags, admin tags and special tags. eventually there will probably be a few more.
The idea is, they all have basically the same fields, so instead of having like 4 tables with manytomany relationship, and instead of adding a new column for Item whenever adding a new type, everything is called a 'tag' and it's very easy to add new types without any change to the schema.
Now to handle this in the admin.py I'm using dyamically created proxy models (based on this), as such:
def create_modeladmin(modeladmin, model, name = None):
class Meta:
proxy = True
app_label = model._meta.app_label
attrs = {'__module__': '', 'Meta': Meta}
newmodel = type(name, (model,), attrs)
admin.site.register(newmodel, modeladmin)
return modeladmin
class TagAdmin(models.Model):
def queryset(self):
return self.model.objects.filter(category_id = self.cid)
class TeamAdmin(TagAdmin):
cid = 1
class SubjectAdmin(TagAdmin):
cid = 2
# ... and so on ...
create_modeladmin(TeamAdmin, name='Teams', model=Tag)
create_modeladmin(SubjectAdmin, name='Subject', model=Tag)
#... and so on ...
This works great for me. However, different staff members need different editing permissions - one guy shouldn't access admin tags, while the other should only have access to edit subject-tags and team-tags. But as far as the admin site is concerned - the dynamic models do not exist in the permission list, and I can't give anyone permissions regarding them.
i.e. a user given all permissions on the list will still not have access to edit any of the dynamic models, and the only way to let anyone access them at all is to give him a superuser which obviously defies the point
I searched SO and the web and I can't anyone with a similar problem, and the docs don't say anything about this not in the dynamic models section or the proxy models section. so I'm guessing this is a different kind of problem. Any help would be greatly appreciated
UPDATE
So after some research into it, the answer was simple enough. Since permissions in django are objects that are saved to the database, what I needed to do was simple - add the relevent permissions (and create new ContentType objects as well) to the db, and then I could give people specific pemissions.
However, this raised a new question - is it a good convention to put the function that creates the permissions inside create_modeladmin as a find_or_create sort of function (that basically runs every time) or should it be used as an external script that I should run once every time I add a new dynamic model (sort of like how syncdb does it)?
And is there a way to also create the permissions dynamically (which seems to me like the ideal and most fitting solution)?
of course you can create permissions, django have django.contrib.auth.management.create_permissions to do this