Django REST Framework that doesn't alter the model data - python

I am trying to create a middleware web app that will allow users to control some services on our servers. To that end, I have several models created in Django that are used to track things like the current state of the server, or a list of which inputs are valid for any given service.
The API needs to be able to:
List all instances of a model
Show detailed information from one instance of a model
Accept JSON to be converted into instructions for the software (i.e. "This list of outputs should source from this input")
I don't need to have any further access to the data - Any changes to the details of the models will be done by a superuser through the Django admin interface, as it will only change if the software configuration changes.
So far all the DRF documentation I've found assumes that the API will be used to create and update model data - How can I use DRF for just GET calls and custom actions? Or should I forego DRF and just use plain Django, returning JSON instead of HTML?
Edit: I've realised where my confusion was coming from; I was misunderstanding the purpose/function of serializers vs viewsets. Serializers will always have create + update methods because they turn incoming data into a model object. Viewsets determine what can be done with that object, so that's where you enable different access methods.

If you are using ModelViewSet, you can use the http_method_names class variable.
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
http_method_names = ['get']

you can try to use readonlymodelviewset, example from docs
class AccountViewSet(viewsets.ReadOnlyModelViewSet):
"""
A simple ViewSet for viewing accounts.
"""
queryset = Account.objects.all()
serializer_class = AccountSerializer

Related

Django queryset permissions

I am building a quite complex Django application to be used on top of and email scanning service. The Django application is written using Python 3.5+
This application primarily uses Django Rest Framework to handle communication with the frontend in the browser.
The issue that I am currently having is that I try to implement the concept of a System Administrator, Domain Administrator and Application User
The System Administrator is basically the "normal" django superuser and is therefore capable of doing everything and see every record in the system.
The Domain Administrator is user who manages one or more email domains. I keep track of this using a Many2Many relationship between the users and the domains. The idea would then be to predefine a filter, so that the log of messages processed, will be automatically filtered to show only messages where the sender domain or the recipient domain equal a domain in the list of domains that the given user is assigned to.
The same would be true for blacklisting/whitelisting policies.
If the Domain Administrator is not assigned to any domains, then no data is shown.
The Application User is basically any authenticated user with one or more domains assigned to them, using the same Many2Many relationship as the Domain Administrator. If no domains are assigned, then no data is shown.
I have found some other solution here on Stackoverflow on making the request.user available to the QuerySet in the ModelManager, but that does not seem like the correct way to handle it.
I have looked at django-guardian, django-authority and django-permissions, but none of them seem to be affecting the QuerySet or the resulting list of objects.
Does anyone have a suggestion for Django package/addon that can be used to handle this or maybe an idea for how this could be handled?
I'm the author of django-cancan library https://github.com/pgorecki/django-cancan which strives to solve the exact problem you are describing.
The philosophy is as following: first, you determine per-user abilities, then in a view, you can check user abilities for a given object, model, or you can retrieve a queryset based on those abilities.
The declaration part looks like this:
def declare_abilities(user, ability):
if not user.is_authenticated:
# Allow anonymous users to view only published articles
ability.can('view', Article, published=True)
else:
# logged in user can view any article...
ability.can('view', Article)
# ... and change his own
ability.can('change', Article, author=user)
# ... and add new ones
ability.can('add', Article)
if user.is_superuser:
# Allow superuser to view and change any article
ability.can('view', Article)
ability.can('change', Article)
Then you can you can check for abilites on a per-object level:
def article_detail_view(request, pk):
article = Article.objects.get(pk=pk)
if request.ability.can('view', article):
...
or on a model level:
def article_create_view(request, pk):
if request.ability.can('add', Article):
...
or get a queryset with accessible objects:
def another_list_view(request, pk):
articles = request.ability.queryset_for('view', Article)
...
DRF's GenericAPIView has a get_queryset method that you can override to perform custom filtering:
def get_queryset(self):
qs = super(YourView, self).get_queryset()
return self.filter_queryset_for_user(qs, request.user)
def filter_queryset_for_user(self, qs, user):
pass # Your logic here
This is not necessarily a bad idea; DRF docstrings recommend overriding this:
You may want to override this if you need to provide different
querysets depending on the incoming request.
I think you are misunderstanding the concept of permission in Django. django-guardian, django-authority and django-permissions these all packages are for handling permission inside your Django application. What permission does is it checks certain model or instance of model, if the user has permission to view that particular model or object, otherwise it will return 403 Unauthorized response. Permission does not change or filter your queryset to return only the valid results.
Rather if you want to apply filter your queryset, you can do so by the above answer, or you can move that code to a Mixin to follow DRY Style. For Mixin Reference you can see this link:
https://reinout.vanrees.org/weblog/2015/06/03/10-mixins.html
https://www.youtube.com/watch?v=0513I6_f2Tc
My answer to this question also provides an alternative to filter your queryset by subclassing rest_framework.filters.BaseFilterBackend and implement filter_queryset() based on your permission pattern , which would be suitable for more complicated use cases.

Using infinite scroll with Django Rest Framework?

I am creating a REST API using Django Rest Framework. The API will serve large amount of data and I want to use infinite scrolling on the page. I would like to use Angular for the frontend. I am not sure how to serve the data such that not all data has to be sent once but only when the user scrolls down.
I am using serializer class -
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('company_name', 'location', 'founded_year')
I am not sure about how to implement this. Should i use Django Endless Pagination or could it be done by using the pagination provided by django-rest-framework. I am also not sure about how the frontend of this would work. Web development newbie here, please help.
Create a ListAPIView subclass
from rest_framework import pagination, generics
class CompanyPagination(pagination.PageNumberPagination):
page_size = 20 # the no. of company objects you want to send in one go
# Assume url for this view is /api/v1/companies/
class CompanyListView(generics.ListAPIView):
queryset = Company.objects.all()
serializer_class = CompanySerializer
pagination_class = CompanyPagination
Once this is done, you can get first 20 by calling the http://your_domain.com/api/v1/companies/?page=1, ?page=2 will give 21st company to 40th company and so on. (Not specifying ?page= is like doing ?page=1)
On your AngularJS side, you will maintain some variable which will hold which page number to fetch next. Now you can bind your API request to either click event on some Load More type of button, or may be you can detect whether user has scrolled to the bottom, and then do the API request and fetch next set of Company objects.
Note:
It is not compulsory to use PageNumberPagination, you can even use LimitOffsetPagination or CursorPagination to achieve your objective. Read more about various pagination styles here
You should give CursorPagination a try.
I don't know exactly whether it's infinite or not but it's definitively for huge sets.

Django Rest Framework filter by PK

I am new to Django Rest Framework and am trying to figure out how to properly filter by PK. I tried redefining query set, but it didn't work out. I currently have a Video class and have created several Video instances. Per Video instance I have a quiz and then quiz questions. When I go to list my quiz questions for a certain video quiz all questions that have been made for all quizzes are listed.
Example Video/1/Quiz/Questions will list its questions and all of Video/2/Quiz/Questions. I just want Video/1/Quiz/Questions to be listed. Is there a simple way to do this?
Views.py:
# questions
class QuizQuestionsList(generics.ListCreateAPIView):
queryset = QuizQuestions.objects.all()
serializer_class = QuizQuestionSerializer
class QuizQuestionsDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = QuizQuestions.objects.all()
serializer_class = QuizQuestionSerializer
Urls.py:
url(r'^video/(?P<pk>[0-9]+)/quiz/questions/list',
views.QuizQuestionsList.as_view(),
name='quizquestions-list'),
url(r'^video/(?P<pk>[0-9]+)/quiz/questions/detail$',
views.QuizQuestionsDetail.as_view(),
name='quizquestions-detail'),
Serializer.py
class QuizQuestionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = QuizQuestions
fields = ('url', 'quiz_id', 'question_text', 'answer_one', 'answer_two', 'answer_three', 'answer_four',
'correct_answer')
As I know, DRF don't allow nested routing as you want.
You will need an extension like that one cited in the docs.
DRF Routers Doc
Yes, i am agree with #Luiz Django Rest Framework don't allow nested routing. You need to just change your routing rules and your code is running perfectly.
Acc. to me your url.py is .....
url(r'^video/quiz/questions/list/(?P<pk>[0-9]+)',
views.QuizQuestionsList.as_view(),
name='quizquestions-list'),
url(r'^video/quiz/questions/detail/(?P<pk>[0-9]+)',
views.QuizQuestionsDetail.as_view(),
name='quizquestions-detail'),

How to think about Django's normal class based views vs. using a REST API

I've been writing a webapp with Django to replace a clumsy, spreadsheet based sports picking game that I play with some friends. I've learned a lot, and had a great time getting to know Django and how to build something like this from scratch.
I recently realized that I wanted to use something more powerful on the frontend (Ember, Angular, etc) with the end goal being a single page app. To that end, I installed Django REST Framework (DRF) and started reading the docs and following the tutorial. It's super interesting, and I'm finally starting to see why a client-server model with an API is really the only way to achieve the smooth interactivity that's all over now.
I'm trying to implement one of my class based views as an API endpoint, and I've been having a lot of trouble conceptualizing it. I thought I'd start with a simple, GET-only endpoint- here's the simple CBV I'm trying to replicate in API form:
class MatchupDetail(DetailView):
template_name = 'app/matchups.html'
context_object_name = 'pick_sheet'
def get_object(self):
#logic to find and return object
def get_opponent(self,username,schedule,week, **kwargs):
#logic to find and return the opponent in the matchup
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#logic to pull the opponents details and set them in the context
I feel like I have a handle on this flow- a user clicks a link, and this view retrieves the object at the heart of the requested page, supplements it with content in the context, then renders it.
As I began thinking about turning this into an API endpoint, it didn't make a whole lot of sense. Should I be putting all the user-related data into a single JSON response? Or should the frontend basically handle the flow of this logic and the API simply be composed of a collection of endpoints- for example, one to retrieve the object, and one or more to retrieve what's now being passed in the context?
What prompted me to make this post was some trouble with my (super basic) API implementation of the above view:
class MatchupDetailApi(generics.ListAPIView):
queryset = Sheet.objects.all()
serializer_class = SheetSerializer
With serializer:
class SheetSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField()
class Meta:
model = Sheet
I added the user field when I noticed that without it, the returned serialized Sheet objects are literally just the row in the database- an integer ID, integer foreign key to the User object, and so on. With a 'traditional' CBV, the entire objects are returned to the template- so it's very intuitive to access related fields, and with Django it's also easy to traverse object relationships.
Does a REST implementation offer the same sort of thing? From what I've read, it seems like I'll need an extension to DRF (django-rest-multiple-models) to return more than one model in a single response, which leads me to think I should be creating endpoints for every model, and leaving presentation logic to when I take care of the frontend. Is that typical? Or is it feasible to have an API endpoint that does return something like an object and several related objects?
Note: the basic endpoint above stopped working when I added the user to the SheetSerializer. I realized I should have a UserSerializer as well, which is:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
However, when I try to browse the API, i get a TypeError that the first user isn't serializable. Specifically: <User: dkhaupt> is not JSON serializable. Isn't this what the UserSerializer is for?
Is it feasible to have an API endpoint that does return something like
an object and several related objects?
Yes!
And it sounds like you are off to a great start. I would structure it something like this:
class UserSerializer(serializers.ModelSerializer):
"""serializes a user"""
class Meta:
model = User
fields = ('id', 'first_name', 'last_name',)
class SheetSerializer(serializers.ModelSerializer):
"""serializes a sheet, and nests user relationship"""
user = UserSerializer(read_only=True)
class Meta:
model = Sheet
fields = ('id', 'sheet_name', 'user',)
I don't think you need django-rest-multiple-models for what you are trying to achieve. In my sketch (where I'm guessing fieldnames) you will serialize the sheet, and also the associated user object.
You can add fields from another related model using the source attribute.
for example:
class SheetSerializer(serializers.ModelSerializer):
user_id = serializers.ReadOnlyField(source='user.user_id')
username = serializers.ReadOnlyField(source='user.username')
class Meta:
model = Sheet
Here the serializer will return the information from the user model that is related to the Sheet model.

Django: Filter request results to only contain data related to the requesting user

I'm a Django beginner (though I do have experience in web development using Sails.js + Angular) so bear with me.
I have an existing application that uses REST API in communicating between Sails.js backend and AngularJS frontend. Now, we've found the backend to be unsuited for our purposes, and we're going to swap to using Django in near-future. Sails.js automatically creates the REST methods for the controllers while Django doesn't, so I suppose I'm going to use something like Django Rest Framework to create the API.
So yeah, I've found corresponding features for most things. The on thing I haven't found yet is a replacement for a Sails.js feature called "policies". They are functions that can be executed on queries to certain controller actions, and can be defined as model-specific, model-controller action-specific, and request type specific. For example, you can have an "authAccess" policy that checks that the user of a request is authenticated, and the policy gets executed before the actual requested controller method gets executed. You can also use these to modify request objects before passing them to the controller. Now to my actual problem:
Let's say I have a User model that has a many-to-one relation with another model, let's call it Book, meaning a user can own many books, but a book can only have one owner. Goody good. Now, we have a logged-in user that is making a query to find all of his books. He makes a GET request to /book. I want to ensure that the returned Book objects are filtered so that ONLY HIS BOOKS are returned.
So basically in Sails I was able to write a policy that altered the request parameters to be like {user: loggedInUser} so the resulting Book results were automatically filtered. This was handy, since I was able to use the same policy to filter other models, too, like DVD or Friend or whatnot. My question is - what would be the best way to implement the same functionality in Django?
Have a look at the documentation:
http://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-current-user
Most likely you are better off overwriting the get_queryset method in a model viewset. And you can make this a generic approach by creating a base class for your views, something like:
from rest_framework import generics, viewsets, mixins, generics
class OwnerModelViewSet(viewsets.ModelViewSet):
def get_queryset(self):
"""
This view should return a list of all the records
for the currently authenticated user.
"""
return self.model.objects.filter(user=self.request.user)
All your model viewset classes can inherit from that class. It would require the foreign key field to be always named "user" though. If that is not the case here is a slightly hacky way how you could find a foreign key field to the User table. Use with care.
from django.db.models.fields.related import ForeignKey
from accounts.models import User
def _get_related_user(self, obj):
'''
Search for FK to user model and return field name or False if no FK.
This can lead to wrong results when the model has more than one user FK.
'''
for f in self.model._meta.fields:
if isinstance(f, ForeignKey) and f.rel.to == User:
return f.name
return False

Categories

Resources