Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sharing Objects with other users in Django

I'm modeling a quite complex system in Django. I will post here only the relevant part of it and I will show simplified use cases diagrams to better express my ideas.

I basically have 2 type of users: Seller and Customer.

  1. A Seller "acquires" a Customer, that means that the seller now has the customer's personal information and can interact with him/her.
    A seller cannot interact with a customer that he didn't acquire. enter image description here

  2. A Seller creates a model hierarchy of related objects (each model in a sublevel is connected with a Foreign key to its parent)
    enter image description here

  3. A Seller shares the created box object and all its related objects with some customers enter image description here

  4. The Authorized Customers can:

    • R-ead some attributes of the Box model
    • R-ead and U-pdate some attributes of the Item model
    • Can't access some attributes of the Item model

    enter image description here

Questions:

  • A: What is the best approach to create relationship between users?
    I've seen that django-relationship could be useful.
  • B: What is the best approach to share Model instances in Django with other users?
    In my case, by default a Customer can't access any model created by the Seller if not explicitly shared.
  • C: If I share only the Box model, does it implicitly mean that also all its related model (Foreign keys to Box model) will be shared?
  • D: What is the best approach to control field level permission per user?
    Can django-guardian be useful here?
like image 810
Leonardo Avatar asked Jul 15 '13 02:07

Leonardo


People also ask

How to give permission to user in Django?

With Django, you can create groups to class users and assign permissions to each group so when creating users, you can just assign the user to a group and, in turn, the user has all the permissions from that group. To create a group, you need the Group model from django. contrib. auth.

How permissions work in Django?

By default, Django automatically gives add, change, and delete permissions to all models, which allow users with the permissions to perform the associated actions via the admin site. You can define your own permissions to models and grant them to specific users.

Can I have two user models in Django?

1. No matter what strategy you pick, or what is your business model, always use one, and only one Django model to handle the authentication. You can still have multiple user types, but generally speaking it's a bad idea to store authentication information across multiple models/tables.

How many types of user in Django?

Initially, there are two types of users in a Django application: superuser accounts and regular users. Superusers have every attribute and privilege of regular accounts, but also have access to the Django admin panel, a powerful application that we'll explore in detail in a future article.


1 Answers

A: What is the best approach to create relationship between users?

All users are not created equal. django-relationship creates arbitrary relations between arbitrary users, which is likely not what you want. What you really want is to constrain this relationship to be strictly Seller -> Customer

# This example assumes that both customers and sellers have user table entries.
from django.contrib.auth.models import User

class Customer(User): pass

class Seller(User):
    acquired_customers = ManyToManyField(Customer,
        related_name="acquired_sellers")

    def acquire(customer):
        " A convenience function to acquire customers "
        self.acquired_customers.add(customer.id)

B: What is the best approach to share Model instances in Django with other users?

You can use custom "through" model of a ManyToManyField to add extra information that you want to track. In this case, we're adding the seller, and an automatic timestamp when was shared. This allows you to do things like show the products that have been shared with you sorted by when they were shared, along with the name of the seller that sent it to you.

# Install mptt for heirararchical data.
from mptt.models import MPTTModel

class Box(MPTTModel):
    " Nestable boxen for your Items "
    owner       = ForeignKey(Seller)
    title       = CharField(max_length=255)
    shared_with = ManyToManyField(Customer,
        related_name='boxes_sharedwithme', through=SharedBox)

class Item(Model):
    " A shareable Item "
    box         = ForeignKey(Box)
    title       = CharField(max_length=255)

class SharedBox(Model):
    " Keeps track of who shares what to whom, and when "
    when        = DateTimeField(auto_now_add=True)
    box         = ForeignKey(Box)
    seller      = ForeignKey(Seller)
    customer    = ForeignKey(Customer)

#----------------------------

# share an Item with a Customer
def share_item(request, box_id, customer_id, **kwargs):
    # This will fail if the user is not a seller
    seller   = request.user.seller
    # This will fail if the seller is not the owner of the item's box
    box      = Box.objects.get(
        id=box_id, owner=seller)
    # This will fail if the seller has not acquired the customer
    customer = Customer.objects.get(
        id=customer_id, acquired_sellers=seller)
    # This will share the item if it has not already been shared.
    SharedBox.objects.create_or_update(
        box=box, seller=seller, customer=customer)
    return HttpResponse("OK") 

C: If I share only the Box model, does it implicitly mean that also all its related model (#Foreign keys to Box model) will be shared?

Implicit permissions are "business logic" which means you'll likely need to implement it yourself. Fortunately, Django's permission system is pluggable so that you can add your own rules that recurse up the hierarchy to check permissions. Or, you can create custom Managers that add the appropriate rules to the the query wherever used.

from django.db.models import Manager
from django.db.models.query import EmptyQuerySet

class ItemManager(Manager):

    def visible(user):
        iqs = self.get_query_set()
        oqs = EmptyQuerySet()
        # add all the items a user can see as a seller
        try: oqs |= iqs.filter(box__owner=user.seller)
        except Seller.DoesNotExist: pass
        # add all the items a user can see as a customer
        try: oqs |= iqs.filter(box__shared_with=user.customer)
        except Customer.DoesNotExist: pass
        # return the complete list of items.
        return oqs

class Item(Model): objects = ItemManager()

class ItemListView(ListView):
    model = Item

    def get_queryset(request):
        return self.model.objects.visible(request.user)

D: What is the best approach to control field level permission per user?

If this needs to be super granular or per-user then django-guardian is the way to go. If permissions are rule-based, then you may be better off with a simple field in order to keep the complexity of your database queries down.

class Property(Model):
    title = CharField(max_length=255)
    units = CharField(max_length=10,
        choices=UNIT_TYPES, null=True, blank=True)

# -- A simple field that toggles properties for all users

class ItemProperty(Model):
    item     = ForeignKey(Item)
    property = ForeignKey(Property)
    value    = CharField(max_length=100)
    customer_viewable = BooleanField(default=False)
    customer_editable = BooleanField(default=False)

# -- A simple field that defines user classes who can view/edit

from django.contrib.auth.models import Group

class ItemProperty(Model):
    item     = ForeignKey(Item)
    property = ForeignKey(Property)
    value    = CharField(max_length=100)
    viewable_by = ForeignKey(Group)
    editable_by = ForeignKey(Group)

Disclaimer: These are my opinions on the subject. Views on authorisation within the Django community are heavily fragmented. There will never be one "Best" way to do anything, so consider carefully whether you need something really generic and can afford the database hits, or whether you need something speedy & business-specific and can afford the loss of flexibility.

like image 125
Thomas Avatar answered Sep 28 '22 19:09

Thomas