9š
I have worked on my issue, tried solutions with permissions and other relations. I have a Relationship
Model and all other relationship lists are derived from the Relationship
model, so I donāt want to maintain a separate list of Relationships.
So my pick was to go with a Postgres JSONField or HStoreField. Since Django has good support for postgres freatures, I found these points pro for the choice I made.
- JSON/HashStore can be queried with Django ORM.
- The configurations are plain JSON/HashStore which are easy to edit and maintain than permissions and relations.
- I found database query time taken are larger with permissions than with JSON/HStore. (hits are higher with permissions)
- Adding and validating permissions per field are complex than adding/validating JSON.
- At some point in future if comes a more simple or hassle free solution, I can migrate to it having whole configuration at a single field.
So My choice was to go with a configuration model.
class UserConfiguration(models.Model):
user = # link to the user model
configuration = #either an HStore of JSONFeild
Then wrote a validator to make sure configuration data model is not messed up while saving and updating. I grouped up the fields to minimize the validation fields. Then wrote a simple parser that takes the users and finds the relationship between them, then maps with the configuration to return the allowed field data (logged at 2-4ms in an unoptimized implementation, which is enough for now). (With permissionās I would need a separate list of friends to be maintained and should update all the group permissions on updation of privacy configuration, then I still have to validate the permissions and process it, which may take lesser time than this, but for the cost of complex system).
I think this method is scalable as well, as most of the processing is done in Python and database calls are cut down to the least as possible.
Update
I have skinned down database queries further. In the previous implementation the relations between users where iterated, which timed around 1-2ms, changing this implementation to .value_list('relations', flat=True)
cut down the query time to 400-520Āµs.
8š
I also donāt want these privacy options to be saved on another model, but the same so that with one query I could get the user object along with the privacy options.
I would advice you to decouple the privacy objects from the UserModel
, to not mess your users data together with those options. To minimize the amount of database queries, use djangos select_related
and prefetch_related
.
The requirements you have defined IMO lead to a set of privacy related objects, which are bound to the UserModel
. django.contrib.auth
is a good point to start with in this case. It is build to be extendable. Read the docs on that topic.
If you expect a large amount of users and therefore also an even larger amount of groups you might want to consider writing the permissions resolved for one user in a redis based session to be able to fetch them quickly on each page load.
UPDATE:
I thought a little more about your requirements and came to the conclusion that you need per object permission as implemented in django-guardian. You should start reading their samples and code first. They build that on top of django.contrib.auth
but without depending on it, which makes it also usable with custom implementations that follow the interfaces in django.contrib.auth
.
- Apache with virtualenv and mod_wsgi : ImportError : No module named 'django'
- WebSocket + Django python WebService
- Setting up New Relic for Django development server
6š
What about something like this?
class EditorList(models.Model):
name = models.CharField(...)
user = models.ForeignKey(User)
editor = models.ManyToManyField(User)
class UserPermission(models.Model):
user = models.ForeignKey(User)
name = models.BooleanField(default=False)
email = models.BooleanField(default=False)
phone = models.BooleanField(default=False)
...
editor = models.ManyToManyField(User)
editor_list = models.ManyToManyField(EditorList)
If a user wants to give āemailā permissions to public
, then she creates a UserPermission
with editor=None
and editor_list=None
and email=True
.
If she wants to allow user ārivadizā to edit her email, then she creates a UserPermission
with editor='rivadiz'
and email=True
.
If she wants to create a list of friends that can edit her phone, then she creates and populates an EditorList
called āmy_friendsā, then creates a UserPermission
with editor_list='my_friends'
and phone=True
You should then be able to query all the users that have permission to edit any field on any user.
You could define some properties in the User
model for easily checking which fields are editable, given a User
and an editor
.
You would first need to get all the EditorLists
an editor belonged to, then do something like
perms = UserPermissions.objects.filter(user=self).filter(Q(editor=editor) | Q(editor_list=editor_list))
- Django: MEDIA_URL not set in template
- TypeError: āDoesNotExistā object is not callable
- Error when creating a custom response message
6š
First of all, in my opinion you should go for multiple models and for making the queries faster, as already mentioned in other answers, you can use caching or select_related or prefetch_related as per your usecase.
So here is my proposed solution:
User model
class User(models.Model):
name = models.CharField()
email = models.EmailField()
phone = models.CharField()
...
public_allowed_read_fields = ArrayField(models.IntegerField())
friends_allowed_read_fields = ArrayField(models.IntegerField())
me_allowed_read_fields = ArrayField(models.IntegerField())
friends = models.ManyToManyField(User)
part_of = models.ManyToManyField(Group, through=GroupPrivacy)
Group(friends list) model
class Group(models.Model):
name = models.CharField()
Through model
class GroupPrivacy(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey(Group)
allowed_read_fields = ArrayField(models.IntegerField())
User Model fields mapping to integers
USER_FIELDS_MAPPING = (
(1, User._meta.get_field('name')),
(2, User._meta.get_field('email')),
(3, User._meta.get_field('phone')),
...
)
HOW DOES THIS HELPS??
-
for each of
public
,friends
andme
, you can have a field in the User model itself as already mentioned above i.e.public_allowed_read_fields
,friends_allowed_read_fields
andme_allowed_read_fields
respectively. Each of this field will contain a list of integers mapped to the ones inside USER_FIELDS_MAPPING(explained in detail below) -
for
friend_list_1
, you will have group named friend_list_1. Now the point is the user wants to show or hide a specific set of fields to this friends list. Thatās where thethrough
model, GroupPrivacy comes into the play. Using this through model you define a M2M relation between a user and a group with some additional properties which are unique to this relation. In this GroupPrivacy model you can seeallowed_read_fields
field, it is used to store an array of integers corresponding to the ones in the USER_FIELDS_MAPPING. So lets say, for groupfriend_list_1
and userA
, theallowed_read_fields
= [1,2]. Now, if you map this to USER_FIELDS_MAPPING, you will know that userA
wants to show only name and email to the friends in this list. Similarly different users infriend_list_1
group will have different values inallowed_read_fields
for their corresponding GroupPrivacy model instance.
This will be similar for multiple groups.
- Django Model Auto Increment Primary Key Based on Foreign Key
- Django how to set request user in client test
- How to force application's stdout logs through uwsgi?
5š
This will be much more cumbersome without a separate permissions model. The fact that you can associate a given field of an individual userās profile with more than one friend list implies a Many to Many table, and youāre better off just letting Django handle that for you.
Iām thinking something more like:
class Visibility(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
field = models.CharField(max_length=32)
public = models.BooleanField(default=False)
friends = models.BooleanField(default=False)
lists = models.ManyToManyField(FriendList)
@staticmethod
def visible_profile(request_user, profile_user):
"""Get a dictionary of profile_user's profile, as
should be visible to request_user..."""
(Iāll leave the details of such a method as an exercise, but itās not
too complex.)
Iāll caution that the UI involved for a user to set those permissions is likely to be a challenge because of the many-to-many connection to friend lists. Not impossible, definitely, but a little tedious.
A key advantage of the M2M table here is that itāll be self-maintaining if the user or any friend list is removed ā with one exception. The idea in this scheme is that without any Visibility records, all data is private (to allow everyone to see your name, youād add a Visibility record with user=(yourself), field=ānameā, and public=True. Since a Visibility record where public=False, friends=False, and lists=[] is pointless, Iād check for that situation after the user edits it and remove that record entirely.
Another valid strategy is to have two special FriendList records: one for āpublicā, and one for āall friendsā. This simplifies the Visibility model quite a bit at the expense of a little more code elsewhere.