[Solved]-Sharing Objects with other users in Django

17👍

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.

👤Thomas

Leave a comment