Django permissions - django

I would love to have more granular permission in my Django project, but can't decide what app to use.
What I have is something like:
class Item(models.Model):
name = models.CharField(max_length=64, unique=True)
description = models.CharField(max_length=128, default='')
logo = ImageField(upload_to=/tmp, blank=True, null=True)
Now with Django standard permissions I have the possibility to choose between add, change and delete, what I want to have is an extended change permission, to offer the ability to give group rights only to change the logo for example, but disallow that same group to modify the item description. I don't want or need a user to entry relation, but simply give the possibility to different groups to edit single fields of a model using the standard admin interface. I'm even not sure if I am talking about per-object permission?
Does anyone know what's best to use or how I would implement it myself? I could also imagine to have read-only users who can access/read everything but won't be able to modify, this isn't possible neither.
Thanks for any help.

The most flexible but way would be to:
write some custom permissions (i.e. can_modify_descr)
write yur own Forms or ModelForms
write Views to render your specified forms.
finally you'd have to override some django admin templates and render your Forms in templates that extend some standard django admin templates.
As far as I can see this is the only way to achieve what you want, but also requires a lot of work.

One simple way to achieve that is to create many ModelAdmin for the same model (one for each "group"). To do that you need to create one Proxy Models for each "group" like this:
models.py
class Item(models.Model):
name = models.CharField(max_length=64, unique=True)
description = models.CharField(max_length=128, default='')
logo = ImageField(upload_to=/tmp, blank=True, null=True)
class ItemGroup1(Item):
class Meta:
proxy = True
admin.py
class ItemAdmin(models.ModelAdmin):
...
class ItemGroup1Admin(models.ModelAdmin):
readonly_fields = ('logo', 'description')
And then you just need to set the permissions of group 1 to only have access to ItemGroup1, etc.
See this post for more info: Using Proxy Models to Customize the Django Admin

If you want to handle this sort of thing beyond your admin site, take a look at django-logical-rules, where you can write your rules in python and access them from views or within a template;

Related

Automatic creation of database view from Django

Maybe this question also is similar to something like "automatic execution of raw SQL code just before creating exact one special model in models.py with managed=False".
For example, I have 3 models in models.py (default User, UserTypes and relation between them):
from django.contrib.auth.models import User
class UserTypes(models.Model):
type = models.TextField(unique=True)
class Meta:
db_table = 'user_types'
class UsersHaveTypes(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
type = models.ForeignKey(UserTypes, on_delete=models.CASCADE)
class Meta:
db_table = 'users_have_types'
unique_together = (("user", "type"), )
I have few types of users one of which is clients. Now I want to create endpoints "/clients", which will work as a usual model (CRUD).
Right now, I just add this in models.py:
class Clients(models.Model):
first_name = models.TextField(blank=True, null=True)
last_name = models.TextField(blank=True, null=True)
# all another fields from User model, which duplicated in the database view
class Meta:
managed = False
db_table = 'clients'
and then I make this by hand in my database backend:
Create database view
Create a function to make Insert, Update
and Delete
Create a trigger to trigger this functions.
It's a bunch of code and there is nothing special, all worked fine.
This all is to make working CRUD on "/clients" endpoint, so, for example, on "Creation", it will create User and automatically add a correct row to users_have_types table, which marks this user as "client".
Is there some more elegant and automatical way to make this? I move my Django project, and I need to create view, function, and trigger in the database backend again, which takes a lot of time (all by hand for every "type of users") and is an ugly decision.
I'm surprised, that nobody asked this question before, cause it's a good style of code database to make some views, and hide real table behind them. I know that Django can't create database view by self, but there must be a way to describe custom SQL code, which is used to create a table in the database. Maybe something with managers, I don't know (I'm a newbie in Django). Of course, it will be specific only for one database backend, but it's fine.

Possibility to upload Wagtail documents to a subfolder of /documents/

I am serving my Django-Wagtail media files trough Amazon S3 and now I reached a point where I need to define a customized Document Class which creates "restricted" documents (only accesible if you are logged). This documents will have a special access that will say to my S3 bucket "hey! just deliver this files If they are requested from this "foo_url" but not from anywhere else", since they will be shown to logged users only. I thought of this to prevent restricted urls to spread out.
That's why I am trying to define this Wagtail Document class to be stored in a subfolder of /documents/ and just tell Amazon what to do with that subfolder.
dummy_code_that_doesnt_work:
class RestrictedDocument(Document):
def get_upload_to(self, filename):
folder_name = 'restricted'
filename = self.file.field.storage.get_valid_name(filename)
return os.path.join(folder_name, filename)
"""
Snippet containing restricted documents
"""
#register_snippet
#python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Python 2
class FooSnipet(models.Model):
rectricted_document_1 = models.ForeignKey(
'RestrictedDocument',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
)
rectricted_document_2 = models.ForeignKey(
'RestrictedDocument',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
)
....
and many more
....
Maybe I am overcomplicating all this and there is another way of doing. Any suggestion will be super welcome! Thank you veeery much :)
The Document model doesn't support a get_upload_to method like images do. However, as of Django 1.10 it's possible to override the file field of AbstractDocument:
from wagtail.wagtaildocs.models import AbstractDocument
class RestrictedDocument(AbstractDocument):
file = models.FileField(upload_to='restricted', verbose_name=_('file'))
I'm not sure that this will help much, though - the views in the Wagtail admin that handle document uploads have no way of knowing that they should save the document through the RestrictedDocument model, rather than the default Document class.
Implementing view restrictions on documents within Wagtail is currently a work in progress (https://github.com/wagtail/wagtail/pull/3245, https://github.com/wagtail/wagtail/issues/1420).

Django - Multiple User Profiles

Initially, I started my UserProfile like this:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
verified = models.BooleanField()
mobile = models.CharField(max_length=32)
def __unicode__(self):
return self.user.email
Which works nicely along with AUTH_PROFILE_MODULE = 'accounts.UserProfile' set in settings.py.
However, I have two different kinds of users in my website, Individuals and Corporate, each having their own unique attributes. For instance, I would want my Individual users to have a single user only, hence having user = models.OneToOneField(User), and for Corporate I would want them to have multiple users related to the same profile, so I would have user = models.ForeignKey(User) instead.
So I thought about segregating the model into two different models, IndivProfile and CorpProfile, both inheriting from UserProfile while moving the model-specific attributes into the relevant sub-models. Seems like a good idea to me and would probably work, however I would not be able to specify AUTH_PROFILE_MODULE this way since I'm having two user profiles that would be different for different users.
I also thought about doing it the other way around, having UserProfile inherit from multiple classes (models), something like this:
class UserProfile(IndivProfile, CorpProfile):
# some field
def __unicode__(self):
return self.user.email
This way I would set AUTH_PROFILE_MODULE = 'accounts.UserProfile' and solve its problem. But that doesn't look like it's going to work, since inheritance in python works from left to right and all the variables in IndivProfile will be dominant.
Sure I can always have one single model with IndivProfile and CorpProfile variables all mixed in together and then I would use the required ones where necessary. But that is just doesn't look clean to me, I would rather have them segregated and use the appropriate model in the appropriate place.
Any suggestions of a clean way of doing this?
You can do this in following way. Have a profile which will contains common fields which are necessary in both profiles. And you have already done this by creating class UserProfile.
class UserProfile(models.Model):
user = models.ForeignKey(User)
# Some common fields here, which are shared among both corporate and individual profiles
class CorporateUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Corporate fields here
class Meta:
db_table = 'corporate_user'
class IndividualUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Individual user fields here
class Meta:
db_table = 'individual_user'
There is no rocket science involved here. Just have a keyword which will distinguish between corporate profile or individual profile. E.g. Consider that the user is signing up. Then have a field on form which will differentiate whether the user is signing up for corporate or not. And Use that keyword(request parameter) to save the user in respective model.
Then later on when ever you want to check that the profile of user is corporate or individual you can check it by writing a small function.
def is_corporate_profile(profile):
try:
profile.corporate_user
return True
except CorporateUser.DoesNotExist:
return False
# If there is no corporate profile is associated with main profile then it will raise `DoesNotExist` exception and it means its individual profile
# You can use this function as a template function also to use in template
{% if profile|is_corporate_profile %}
Hope this will lead you some where. Thanks!
I have done it this way.
PROFILE_TYPES = (
(u'INDV', 'Individual'),
(u'CORP', 'Corporate'),
)
# used just to define the relation between User and Profile
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey('Profile')
type = models.CharField(choices=PROFILE_TYPES, max_length=16)
# common fields reside here
class Profile(models.Model):
verified = models.BooleanField(default=False)
I ended up using an intermediate table to reflect the relation between two abstract models, User which is already defined in Django, and my Profile model. In case of having attributes that are not common, I will create a new model and relate it to Profile.
Could be worth to try using a through field. The idea behind it is to use the UserProfile model as through model for the CorpProfile or IndivProfile models. That way it is being created as soon as a Corp or Indiv Profile is linked to a user:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey(Profile, related_name='special_profile')
class Profile(models.Model):
common_property=something
class CorpProfile(Profile):
user=models.ForeignKey(User, through=UserProfile)
corp_property1=someproperty1
corp_property2=someproperty2
class IndivProfile(Profile):
user=models.ForeignKey(User, through=UserProfile, unique=true)
indiv_property1=something
indiv_property2=something
I think that way it should be possible to set AUTH_PROFILE_MODULE = 'accounts.UserProfile', and every time you create either a CorpProfile or a IndivProfile that is linked to a real user a unique UserProfile model is created. You can then access that with db queries or whatever you want.
I haven't tested this, so no guarantees. It may be a little bit hacky, but on the other side i find the idea quite appealing. :)

Django: Adding Permission to an Specific Model Instance

I am looking for the best way to implement user permissions to allow users to edit specific model instances.
For instance, I have such two models:
model RadioChannel(models.Model):
name = models.CharField(max_length=150, unique= True)
number = models.IntegerField( unique= True)
model ProgramSchedule(models.Model):
channel = models.ForeignKey("RadioChannel")
name = models.CharField(max_length=150, unique= True)
start_time = models.DateTimeField()
Now my Operators are my build-in Django users. I want to make groups for these users so that they can only add/remove/edit ProgramSchedules that are allowed. In addition I want to add groups to these users to the admin panel.
Thanks.
You are looking for an object permission implementation. A good comparison is here:
http://djangopackages.com/grids/g/perms/
Shameless plug:
Heres my fork of a very popular per-object permission app: http://github.com/azizmb/django-authority
If I am getting you correct, what you need to implement is called row level permissions in Django. Have a look at this if it helps. http://code.djangoproject.com/wiki/RowLevelPermissionsDeveloper
I would recommend using Django Guardian for object-level permissions.

How to add a user to django-tagging

I'm looking for a way to add "user = models.ForeignKey(User, editable=False)" to django-tagging model with templatetags support but my django knowledge is too low to understand the code of django-tagging.
you need to use the tagging register function
import tagging
class UserModel(models.Model):
name = models.CharField(max_length=50)
tagging.register(UserModel)
Take a look http://code.google.com/p/django-tagging/source/browse/trunk/docs/overview.txt