Extend django Groups and Permissions - django

I've been searching Google and Stackoverflow for the last 3 days now and couldn't find anything similar. I'd like to extend or use the django groups and permissions.
Let's say we got some Projects with different Teams and Users.
class Project(models.Model):
owner = models.ForeignKey(User)
name = models.CharField(max_length=100)
class Team(models.Model):
project = models.ForeignKey(Project)
members = models.ManyToManyField(User)
name = models.CharField(max_length=100)
permission = models.ManyToManyField(Permission)
Now - everything's fine. Now what I want is to extend the existing Auth.Group delivered by django so I could use request.user.has_perm... the direct way.
So, let's change the Team-Model into
class Team(Group):
project = models.ForeignKey(Project)
members = models.ManyToManyField(User)
This extends the existing Group. The name and permissions field comes direct from the Group-Model. If I create a new Team, there will be a Group added, too. That's great.
With a custom save method I could also add, that the user listed in members, will be added to the created Group and gets the permissions granted to the Team/Group.
Now my problem is, the name field of the Group is unique - so I couldn't add an Admins-Team for each Project.
I thought about adding another name-field for the frontend and generate a unique string for the name field of the Group-Model. But hmmm....is there a better solution for multiple custom Groups? Maybe one got an idea? Thank you in advance!

You need to make group names unique. So one way that you found out is having another name field, making it unique for all subgroups.
Another way would be to make use of existing name field and add special chars of yours. For example group name with admin#project1, members#project1 etc. (Note: I'm not sure '#' is allowed in group name, you can choose any special character that is allowed).
So whenever you create Team you update the name field with suffix #<project_name> in Team models save() method.
To display it more sanely, you can add __unicode__() method to return only first part of the group name. Example:
class Team(Group):
...
def __unicode__(self):
try:
return self.name.split('#')[0]
except:
#something wrong!
return self.name

Related

django model attribute field empty list

I'm trying to build an online forum. Right now, my forum model has several attributes and one of it is "owner", which is a ForeignKey to the user that created this forum. It also has another attribute "passcode" which makes sure that whenever an owner creates a forum, he/she has to type a passcode so that only others with the right passcode can join the forum. Now, I am trying to implement a new function such that users can choose to join existing forums; however, I am stuck.
1) My first issue is that in order to create a custom permission, I first need another model attribute that contains a list of the permissioned users. I was thinking of having a model attribute as an empty list, permissioned_users = [], so that whenever a user requests to join a forum and has the right passcode, his/her username will be appended to the list and then in my views.py I can use #user_passes_test to check if the request.user.username is in the list. However, i'm not sure if "students = []" will work such that i can do "anyparticularinstance".students.append("his name") will work.
2) How do i create a join forum function? I have fully implemented a create forum function but how do I allow users to join an existing forum? Thank you!
One Way you can achieve the permissions is by defining a Boolean field in your model, for example:
class Forum(AbstractBaseUser):
username=models.CharField(max_length=20,unique=True)
name = models.CharField(max_length=20)
email = models.EmailField(max_length=254,null=True,blank=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
By extending the AbstractBaseUser in Django you can define custom permissions for users.Either from the default Admin provided by Django or may be your own custom admin, you can add or remove permissions to a particular user. For more information you can see the following link Django AbstractBaseUser
You can achieve your objective by using textfield, and then appending at the end of the textfield everytime you update the field.
Model:
permissioned_users = models.TextField(blank=True, null=True)
View:
foo = Forum.objects.get(id=id)
temp = foo.permissioned_users
temp = temp+" username"
foo.permissioned_users = temp
foo.save()
Obviously you have to do some more work, ex. when you want to check which user is given permission, you split the string using whitespace hence str.split(), then you can easily iterate through it and make your checks.

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

Django check if object in ManyToMany field

I have quite a simple problem to solve. I have Partner model which has >= 0 Users associated with it:
class Partner(models.Model):
name = models.CharField(db_index=True, max_length=255)
slug = models.SlugField(db_index=True)
user = models.ManyToManyField(User)
Now, if I have a User object and I have a Partner object, what is the most Pythonic way of checking if the User is associated with a Partner? I basically want a statement which returns True if the User is associated to the Partner.
I have tried:
users = Partner.objects.values_list('user', flat=True).filter(slug=requested_slug)
if request.user.pk in users:
# do some private stuff
This works but I have a feeling there is a better way. Additionally, would this be easy to roll into a decorator, baring in mind I need both a named parameter (slug) and a request object (user).
if user.partner_set.filter(slug=requested_slug).exists():
# do some private stuff
If we just need to know whether a user object is associated to a partner object, we could just do the following (as in this answer):
if user in partner.user.all():
#do something

Django admin site -- list ForeignKey items as change-list on change page

In (a toy version of) my project, there are Owners who own any number of Objects. My models.py file looks like
class Owner(models.Model)
name = models.CharField(max_length=50)
date_of_birth = models.DateField()
class Object(models.Model)
name = models.CharField(max_length=50)
price = models.models.DecimalField(max_digits=9, decimal_places=2)
owner = models.ForeignKey(Owner)
My question relates to the change page for an Owner on the admin site, e.g.
http://mysite.com/admin/myapp/owner/1/.
Now I know that if I register Object as a TabularInline or a StackedInline, then I get an editable list of the Objects this Owner owns. However, in the real version of my project, an Object has something like 25 fields, not 2, and so neither of those options is really desirable aesthetically.
What I would really like instead is to essentially have a change-list of all the Objects an Owner owns appear on the Owner's change-page. (That way I get a nice compact listing of all the Owner's Objects, and if I need to edit the details of one, I can click on its link and edit it in its own page.) Basically I want the contents of
http://mysite.com/admin/myapp/object/?owner__id__exact=1
to appear within
http://mysite.com/admin/myapp/owner/1/.
Is there a way to do this?
PS: I'm using Django 1.4 and Python 2.7.
You can define what form class and/or fields to use in each InlineModelAdmin using these attributes, and limit the amount of input fields per object that way.

django guardian, permissions and extending django auth groups to 'organization' models

django guardian https://github.com/lukaszb/django-guardian is a really well written object-level permissions app; and I have actually read up on and used quite a number of other django object level permissions app in various django projects.
In a recent project that I am working on, I decided to use django guardian but I have a model design question relating to the pros and cons of two possible approaches and their respective implications on sql query performance:-
using django.contrib.auth.models.Group and extending that to my custom organization app's models; or
using django.contrib.auth.models.User instead and creating an m2m field for each of the organization type in my organization app.
Approach #1
# Organisation app's models.py
from django.contrib.auth.models import Group
class StudentClass(models.Model):
name = models.CharField('Class Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
size = models.IntegerField('Class Size', blank=True)
class SpecialInterestGroup(models.Model):
name = models.CharField('Interest Group Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
description = models.TextField('What our group does!', blank=True)
class TeachingTeam(models.Model):
name = models.CharField('Teacher Team Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
specialization = models.TextField('Specialty subject matter', blank=True)
In this approach, when a user is added to a group (django group) for the first time, the group object is created and also assigned to one of these 3 classes, if that group object does not yet belong to the class it is added into.
This means that each StudentClass object, sc_A, sc_B etc, can possibly contain a lot of groups.
What that means is that for me to ascertain whether or not a specific user (say myuser) belongs to a particular organization, I have to query for all the groups that the user belong to, via groups_myuser_belongto = myuser.groups and then query for all the groups that are associated to the organization I am interested in, via groups_studentclass = sc_A.groups.all() and since I now have 2 lists that I need to compare, I can do set(groups_myuser_belongto) && set(groups_studentclass), which will return a new set which may contain 1 or more groups that intersect. If there are 1 or more groups, myuser is indeed a member of sc_A.
This model design therefore implies that I have to go through a lot of trouble (and extra queries) just to find out if a user belongs to an organization.
And the reason why I am using m2m to groups is so as to make use of the Group level permissions functionality that django guardian provides for.
Is such a model design practical?
Or am I better off going with a different model design like that...
Approach #2
# Organisation app's models.py
from django.contrib.auth.models import User
class StudentClass(models.Model):
name = models.CharField('Class Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
size = models.IntegerField('Class Size', blank=True)
class SpecialInterestGroup(models.Model):
name = models.CharField('Interest Group Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
description = models.TextField('What our group does!', blank=True)
class TeachingTeam(models.Model):
name = models.CharField('Teacher Team Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
specialization = models.TextField('Specialty subject matter', blank=True)
Obviously, this model design makes it really easy for me to check if a user object belongs to a particular organization or not. All I need to do to find out if user john is part of a TeachingTeam maths_teachers or not is to check:
user = User.objects.get(username='john')
maths_teachers = TeachingTeam.objects.get(name='Maths teachers')
if user in maths_teachers.users.all():
print "Yes, this user is in the Maths teachers organization!"
But what this model design implies is that when I add a user object to a group (recall that I want to use django guardian's Group permissions functionality), I have to make sure that the save() call adds the user object into a "Maths Teachers" group in django.contrib.auth.models.Group AND into my custom TeachingTeam class's "Maths Teachers" object. And that doesn't feel very DRY, not to mention that I have to somehow ensure that the save calls into both the models are done in a single transaction.
Is there a better way to design my models given this use case/requirement - use django groups and yet provide a way to "extend" the django's native group functionality (almost like how we extend django's user model with a "user profile app")?
My take on this (having developed django apps for a long time) is that you should stick with the natural approach (so a StudentClass has Users rather than Groups). Here "natural" means that it correspond to the actual semantics of the involved objects.
If belonging to a specific StudentClass must imply some automatic group (in addition to those granted to the user), add a groups m2m to the StudentClass model, and create a new authentication backend (extending the default one), which provides a custom get_all_permissions(self, user_obj, obj=None) method. It will be hooked by https://github.com/django/django/blob/master/django/contrib/auth/models.py#L201
In this method query for any group associated to any Organization the user belongs to. And you don't need to do 1+N queries, correct use of the ORM will navigate through two *-to-many at once.
The current ModelBackend method in https://github.com/django/django/blob/master/django/contrib/auth/backends.py#L37 queries get_group_permissions(user_obj) and adds them to the perms the user has assigned. You could add similar behavior by adding (cached) get_student_class_permission and other corresponding methods.
(edited for clearer prologue)
Obs: There is another approach which is to use generic relationships, in this approach you would have the User model instance pointing to it's resources through the contenttypes framework. There is a nice question here on SE explaining this approach.
About the performance: There are some clues that a single select with JOIN is cheaper than many simple select (1,2,3). In this case opition 2 would be better.
About the usability: The first approach is hard to explain, hard to understand. IMHO go for no. 2. Or try the generic relations.