Django Rest Framework - best way to an endpoint? - django

I'm learning Django + DRF + React and I reached the phase where it's time to protect some endpoints. Some are easier to protect, just a permission so only the user which created a particular object (and the admins) are able to see it. There is one endpoint which is tricky for me. It's a GET call and returns something like:
{book: "Who am I and how come",
id: "whatever31",
reading: ["user1", "user2"]}
I want to protect this endpoint based on the user making the request (Session auth) so only calls coming from user1 and user2 can access this object (also, not exposing reading field, but that's probably a different discussion). Should I use a custom permission on DRF view? Should I use a filter instead in queryset method? Maybe both?

Custom permission just like creating an decorator, both of them match what you need:
class InReader(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj):
return request.user in obj.reading

Related

How do I know what functions are run in Generic Views?

I am new to Django and scratching my head with the following question
how do I know which function is run in specific processes?
for example in below view, retrieve function is executed. I flipped the documentation, however, could not find anything named retrieve to overwrite
class ProfileRetrieveAPIView(RetrieveAPIView):
serializer_class = ProfileSerializer
def retrieve(self, request, username, *args, **kwargs):
print("hey yo i am running 01")
try:
profile = Profile.objects.select_related('user').get(
user__username=username
)
except Profile.DoesNotExist:
raise
serializer = self.serializer_class(profile)
return Response(serializer.data)
In general the generic views in Django Rest Framework are made by inheriting from various mixins. It is also documented which mixin a view is inheriting from. For example the documentation for RetrieveAPIView which you use is as follows:
Used for read-only endpoints to represent a single model instance.
Provides a get method handler.
Extends:
GenericAPIView,
RetrieveModelMixin
So you can override get here if needed (it actually calls the retrieve method here). If you further follow the link for RetrieveModelMixin you will see that it's documentation has this line:
Provides a .retrieve(request, *args, **kwargs) method, that implements
returning an existing model instance in a response.
Here we can see it mentions that it provides a retrieve method. So you just need to check the documentation for the super classes for a respective class if you want to know what method is used by it.
Note: If all else fails when searching the documentation consider simply looking at it's the source code on GitHub or in your own IDE.
;)
According to DRF documentation, there is a default mapping the views and the router:
list is for GET on a list of objects, like /users/
retrieve is for GET on a single object, like /users/3/
create is for POST on a non-detailed endpoint, like /users/
update is for PUT on a detailed endpoint, like /users/3/
partial_update is for PATCH on a detailed endpoint, like /users/3/
destroy is for DELETE on a detailed endpoint, like /users/3/
Those are the expected/standard mappings within DRF views and viewsets
You can view some examples here: https://www.django-rest-framework.org/api-guide/generic-views/#mixins

Returning related fields of a model instance

I am creating an app with a rest API that should return values for instances of objects based on the url given. Right now I have the API working using ModelViewSets of my objects for the API.
For example I have three objects, user, transactions, and goals.
As it stands I can go to /mysite/api/users and return a list of all users
I can also go to /mysite/api/users/1 to return just the user with the id '1'.
I can do something similar with transactions and goals.
What I'm looking to do is go to url /mysite/api/users/1/transaction/1/goal
to find the goal associated with the transaction for that user.
I've been scouring tutorials and am not sure what the right question is to ask in order to find something useful to learn how to do this. What is the correct way to go about setting up my rest api like this?
If I understand correctly, you want to create nested ressources.
If you are using Viewsets, then the ExtendedRouter class of the drf-extensions package will allow you to achieve this.
Drf-extensions documentation about this feature: https://chibisov.github.io/drf-extensions/docs/#nested-routes
There is also this module, who also offer the same features.
You can either use url params or query params to solve your issue. I will explain the URL params solution here,
serializers.py
#Write a Goal Serializer
urls.py
#change the URL according to your environment
url(r'^users/(?P<uid>[0-9]+)/transaction/(?P<tid>[0-9]+)/goal/$', GoalViewSet.as_view({'get': 'user_transaction_goal',}), name='user-transaction-goal'),
views.py
class GoalViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
queryset = Goal.objects.all()
def user_transaction_goal(self, request, uid, tid):
#assuming user is FK in transaction and transaction is a FK in goal
#modify the filter rule according to your model design
goals = Goal.objects.filter(transaction=tid, transaction__user=uid)
serializer = GoalSerializer(goals, many=False)
return Response(serializer.data)
As #clement mentioned you can also use plugins to handle this situation.

What url structure should I use when retrieving users via a token?

I'm using Django Rest Framework to handle token authentication for a mobile application for a school. In particular, when a student logs in, the mobile application sends a token to my Django backend, which then combines data from its database and some external data from another source. I found it easiest to use a generic RetrieveAPIView to accomplish what I needed.
My code is working, and my main question is around the url. For most retrievals, we usually have the primary key as well (e.g. /students/SOME-ID), but in this case, I'm using the token to retrieve the user rather than the primary key. In fact, if SOME-ID passed in was different from the Token, the user associated with the Token would be returned anyway (which seems kinda strange).
I'm wondering whether it is better to have my url route be just (/students) instead though this seems to be a list rather than a retrieve operation.
WHAT I HAVE NOW
http://localhost:8000/api/v1/revision/students/1
IS THIS BETTER
http://localhost:8000/api/v1/revision/students/
CODE
class StudentView(generics.RetrieveAPIView):
model = Student
serializer_class = StudentSerializer
# combines data from both current and legacy database
def retrieve(self, request, pk=None):
obj = get_object_or_404(Student, user=request.user)
# KIV -> unsure if this is the best way to combine data from legacy and current database
# or should it be done in the serializer
data = StudentSerializer(obj).data
# combines existing data stored in database with legacy data from database
legacy_data = SOME_EXTERNAL_API_SERVICE.get_student_info(obj)
data['avatar'] = legacy_data['avatar']
data['coins'] = legacy_data['coins']
return Response(data)
I would definitely not use /students/id/ with the behaviour you're describing: This URL should always return the student with the given id of error (depending on whether the user fetching this resource is allowed to do so). You might want to use this URL for admins to view students in the future.
And for the same reason, I wouldn't use /students/ because I'd expect it to return a list of all students, or at least the list of all students the particular logged in user is allowed to see. This might fit your purpose now (where the logged in user can only see himself), but maybe not in the future if you create new roles that can view more students.
There are two approaches here:
Either you treat this as a filter on all the students: /students/?current=true which I personally find ugly because you're not actually filtering on the total set of students.
Or you treat this as a special case: /students/current using a special keyword for fetching this one specific student.
I would choose the latter one because it is more descriptive and easier to understand when looking at the API. Note of course that id can never be 'current' in this case, which is why some people discourage this kind of special resource queries and opt for the first option.
Definitely, the url http://localhost:8000/api/v1/revision/students/ looks better.
But you don't need to write this in a RetrieveAPIView, you could always do this in base APIView,
class StudentView(APIView):
def get(self, request, *args, **kwargs):
obj = get_object_or_404(Student, user=request.user)
data = StudentSerializer(obj).data
legacy_data = SOME_EXTERNAL_API_SERVICE.get_student_info(obj)
data['avatar'] = legacy_data['avatar']
data['coins'] = legacy_data['coins']
return Response(data)
By using like this, you can avoid the extra pk keyword argument from your url.

Setting the authenticated user on a Django model

I have a number of models that need to refer back to the user that created/updated them. Generally this just involves passing request.user to the relevant attribute, however I'd like to make this automatic if possible.
There's an extension for Doctrine (a PHP ORM) called Blameable that will set a reference to the currently authenticated user when persisting a model instance, e.g.:
class Post
{
/**
* Will set this to the authenticated User on the first persist($model)
* #ORM\ManyToOne(targetEntity="User", inversedBy="posts")
* #Gedmo\Blameable(on="create")
*/
private $createdBy;
/**
* Sets this to the authenticated User on the first and subsequent persists
* #ORM\ManyToOne(targetEntity="User")
* #Gedmo\Blameable(on="update")
*/
private $updatedBy;
}
To get the same functionality in Django, my first thought was to try and use pre_save signal hooks to emulate this - however I'd need to access the request outside of a view function (looks possible with some middleware but a bit hacky).
Is there something similar already available for Django? Am I better off explicitly passing the authenticated user?
The level of decoupling Django has makes it impossible to automatically set the user in a model instance.
The middleware solution is the way to go. When I need to do this, I just add to the save() method, like so:
class MyObject(models.Model):
def save(self, *args, **kwargs):
if not self.created_by:
self.created_by = get_requests().user
super(MyObject, self).save(*args, **kwargs)
as for the "hackyness" of storing the requests in a global dictionary, I think you'll get over it. Someone once said of this pattern, "It's the worst one, except for all the others".
P.S. You'll also find it really useful if you want to use django.contrib.messages from deep within your code.

How to restrict editing of records to the logged-in user?

Sorry, I am still new to Django, hopefully the question isn't out of place.
When I have the following in my template:
<td>{{ item.last_name }}</td>
By clicking on last name the user will be redirected to the following link in order to edit it.
http://127.0.0.1:8000/contact/edit/?id=1
But then what prevents any logged in user to just inject a different id in there on the browser and edit a record that doesn't belong to him?
Update
I just had an idea when I read the comment and answer below. Rather than using a third party app, couldn't I just create a UserProfile for each user and attach a unique company wide uuid.uuid1(). Each time a loggedin user attempts to edit something, his unique company uuid will be also passed in the link as an additional parameter.
On the edit side, it would harvest this guid and compare it against the logged in user and see if they match. If they do match, he is authorized to proceed with the editing, otherwise he will be redirected.
What do you think? Any weaknesses?
If you use Django's new class based views, e.g. the generic UpdateView, you can extend the dispatch handler.
def dispatch(self, request, *args, **kwargs):
handler = super(MyEditView, self).dispatch(request, *args, **kwargs)
# Only allow editing if current user is owner
if self.object.author != request.user:
return HttpResponseForbidden(u"Can't touch this.")
return handler
In this case, the code verifies that the author field of the model object corresponds to the currently logged in user, before even handling the rest of the request.
You can see a reallife example of this in a project of mine.
When you're using Django auth, always rely on the session mechanism to identify an user instead of making some other id such as uuid1() (except, for example, when you need extra sessions in an e-commerce site under HTTPS).
For the permission part, you could check the ownership directly, mainly as described by Koliber Services. The relationships between Company, User and Contact are crucial for the logic of permission checking. There are many ways to model the relationships and thus the code would differ much. For example:
# a modeling way
User.company -> Company : an user belongs to a company
Contact.contributor -> User : a contact is contributed by an user, would be invalid is user is leaving the company
# could check accessibility by
can_view = contact.contributor.company_id == current_user.company_id
# another modeling way
User.company -> Company : an user belongs to a company
Contact.company -> Company : a contact info is owned by a company, does not share globally
# could check accessibility by
can_view = contact.company_id == current_user.company_id
When can_view is False, user should get a 403 for his unauthorized attempting and get logged.
Normally the above method is enough for content protection(not yet in Django Admin). However, when you have many different types of permission checking and even row-permission checkings, it's better to use some uniform permission API.
Take Django-guardian for example, you could simply map companies to groups and assign can_view permission for a contact for the group representing the user's company. Or, assign the can_view permission to all users in a company when a contact is created by using signal or celery task.
Furthermore, you could use /contact/1/edit/ instead of /contact/edit/?id=1. In this way the int(request.GET('id')) part is moved to urlconf like r'^contact/(?P<pk>\d+)/$', less code and much clearer.
There are some third-party apps that does what you want, its called "row-level permission" where you can give different users different access to specific objects, "Row level" comes from SQL where each object is a row in the database
I use django-guardian to do the job
In the function handling the saving of the data, check to see if the object being edited has the same ID as the presently logged in user.
For example, if the object in question is called EmailPrefs and it has a field called user_id:
Load the EmailPrefs object with the ID of the object being edited
If the user_id does not match the current user, stop further processing
Modify the EmailPrefs object
Save the EmailPrefs object to the database