Eventure helps people create events. We want to provide the flexibility of creating both Public (everybody can see) and Private (no one can see except creator and guests). Private event should only be seen by owner and guests. Public event is opened for everyone. These accessibility features can be extended to other types of objects such as Account, Album, Photo, Video… The implementation of these features is based on two core concepts: one is query set filter, the other is object permission. Both are provided by Django Rest Framework (DRF).
DRF allows us to add filter by modifying get_queryset() of the view, and add manual permission class to the view.
Account Privacy/Permissions
Say, we want only logged in users to be able to access account overview. All we need to do is to add permissions.IsAuthenticated to permission_classes of the view.
1 2 3 4 5 |
class AccountDetail(generics.RetrieveAPIView): "Show detailed information for the given account." queryset = Account.objects.all() serializer_class = AccountSerializer permission_classes = (permissions.IsAuthenticated, ) |
Event Privacy/Permissions
Event privacy and permission requirements are a bit more involved. Users should only able to see Public events or Private event that they are guest of. We have to create a permission class which will deny read access to Private Event (except guests, owner) and reserve write access to owner/creator of event.
1 2 3 4 5 6 7 |
class IsGrantedAccessToEvent(permissions.BasePermission): def has_object_permission(self, request, view, obj): # Read access Denied for Private Event, all else Granted if request.method in permissions.SAFE_METHODS: return obj.privacy != Event.PRIVATE or _is_event_member(obj, request.user) # Write access Granted for owner, all else Denied return obj.owner == request.user |
Notice that we use request.method in permissions.SAFE_METHODS to decide if this is a read or write access, also we need the _is_event_member(event, account) to decide the user is member of an event. Notice that the EventGuest data table stores on each row the event_id, guest_id, and the invitation status of each guest.
1 2 |
def _is_event_member(event, account): return event.owner == account or EventGuest.objects.filter(event=event.id, guest=account.id).exists() |
This permission class can be used in the Event Detail View like so:
1 2 |
class EventDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticated, IsGrantedAccessToEvent,) |
For the list view, we use filter to filter out events that a specific user should not be able to see (Private events that they are not guest of).
1 2 3 4 5 |
class EventListView(): def get_queryset(self): return events.exclude(Q(privacy=Event.PRIVATE) & ~Q(owner=self.request.user) & ~Q(eventguest__guest=self.request.user)) |
Media Privacy/Permissions
Photo and Video that user uploads to an Event should follow the privacy and permission settings of that Event. That is to say, if a user cannot see a Event, he or she should not be able to see the media that was uploaded to that event either.
To do this, the permission class has to grant read access to albums of Private events that user is member of and read access to any Public event albums; but grant write access to owner only. For media in albums, since media can belongs to multiple albums, should a user be granted access to one of the albums, he or she can access that media. We can use isinstance() to check if object is Album or Media and divide this into two cases.
1 2 3 4 5 6 7 8 9 10 11 |
class IsGrantedAccessToMedia(permissions.BasePermission): def has_object_permission(self, request, view, obj): if isinstance(obj, Album): if request.method in permissions.SAFE_METHODS and obj.event is not None: return obj.event.privacy == Event.PRIVATE and _is_event_member(obj.event, request.user) or obj.event.privacy == Event.PUBLIC return obj.owner == request.user or (obj.event is not None and obj.event.owner == request.user) elif isinstance(obj, Media): for album in obj.albums.all(): if self.has_object_permission(request, view, album): return True return False |
We then use this permission class in the all the views that need it by adding permission_classes = (IsGrantedAccessToAlbum,).
With the combination of query set filter and DRF permission class, we have implemented a series of privacy and permission policies that would otherwise be very cumbersome to code.
*The mentioned features are for demonstration purposes only. It might or might not be available in the final product.
Leave a Reply