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.
Related
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.
I am trying to do a survey application in django. My model is as follows:
class mymodel(models.Model):
resptype = models.ForeignKey(Response)
ques = models.ForeignKey(Question)
response = models.CharField(max_length=5, blank=True)
Here i am using rest framework to send data to my front end. Right now i have my api defined as follows:
class mymodelList(APIView):
def get(self, request, format=None):
surveydata = mymodel.objects.all()
serialized_surveydata = mymodelSerializer(surveydata, many=True)
return Response(serialized_surveydata.data)
In my app, I have a standard set of 16 questions with multiple choice responses and the choice is saved in the response column in the model.
Now what I am trying to achieve is to calculate the count of responses for each question . ie. For question 1, what is the count that a person responded with 1 or 2 or etc.
Also i would like to know how to send the calculated counts through another json field from the rest framework because I don't have any model defined separately for this data.
EDIT:
This command did the trick for my query but i still not able to figure out how to send it to the front end as a serialized object.
x = mymodel.objects.values('ques','response').order_by().annotate(number_of_responses=Count('response'))
That's not really a great structure for your model, it would probably be easier to create separate Question and Choice classes. The Django tutorial actually uses this type of application as an example... take a look at that for some guidance
Check #detail_route or #list_route from viewsets depending on if you want to show this info per question o for all questions at once.
This will allow you to define a custom endpoint to request the information you are asking for. To do so, you may also need to define a custom serializer to pass extra data or a filter if you want to filter by question, user, etc.
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.
I want write a custom form field (and possibly widget too) and I'm not sure about how the form instances are shared between requests. For example, if I render a form with data from a model instance, is that instance still available when I am validating data? If so, does that mean that there is another database hit to look up the model again between requests?
Similarly, if I write a custom field that takes in a list of data to display in its __init__ method, will that list of data be available to validate against when the user POSTs the data?
It would be really helpful if someone could point me to parts of the django source where this occurs. I've been looking at the models.py, forms.py, fields.py and widgets.py from django.forms, but I'm still not 100% sure how it all works out.
Eventually, what I want to do is have a field that works something like this (the key part is the last line):
class CustomField(ChoiceField):
def __init__(self, data_dict, **kwargs):
super(CustomField, self).__init__(**kwargs)
self.data_dict = data_dict
self.choices = data_dict.keys()
def validate(self, value):
if value not in self.data_dict:
raise ValidationError("Invalid choice")
else:
return self.data_dict[value]
Will that data_dict be available on the next request? If I create a custom forms.Form and initialize it with the data_dict, will that be available on the next request? (e.g. with a factory method or something...).
Side note: I'm doing this because I want to (eventually) use something like Bootstrap's typeahead and I'd like to pass it "pretty values" which I then convert server-side (basically, like how option values in a select can have a different submitted value). I've done this with client-side javascript in the past, but it would be nice to consolidate it all into a form field.
There's nothing magical about forms. Like everything else in Django (or just about any web framework), objects don't persist between requests, and need to be reinstantiated each time. This happens in the normal view pattern for form handling: you instantiate it once for a POST, and a separate time for a GET. If you have data associated with the form, it would need to be passed in each time.
Hey everyone, I am pretty sure this is a fairly common problem.
So in order to register an account my site you need an email address from certain school domain (like facebook). This wouldn't be that big a problem until you start integrating other apps, like django-notification and django-registration and django-socialregistration into your site where they are sending email via user.email.
I have asked my users and most of them want an 'active_email' option - that means that they can change the email to their designated gmail or whatever.
I have come up with the following solution which isn't the cleanest of all:
First, I inherit from User in django.contrib.auth and call this new class MultipleEmailUser, with email=active_email and official_email=sch_email.
Then I override django.contrib.auth's UserManager to change the API slightly,
And the most painful part is to change all the source code that has User.object.find() to MultipleEmailUser.find().
Can someone suggest me a cleaner way? (My biggest headache arise from other apps only permitting to send email to User.email.)
You don't need - or want - to modify the User class. Just set up a UserProfile class with a OneToOneField back to User, and set the AUTH_PROFILE_MODULE setting. See the documentation.
Rather than a true datastore attribute, you could use a property called 'email' to expose access to whatever data you want.
Basic property syntax (from the python docs):
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
In your case, you could create true datastore attributes named, for instance, auth_email and active_email. Then, the User.email property could perform some logic in its getter function, to determine which one to return (i.e. if active_email is set, return it; otherwise, return auth_email)
It's worth noting that the syntax for property has undergone some flux. As of python 2.7, it can be implemented in a more readable way, as a decorator:
class User(BaseModel):
#property # email
def email(self):
if self.active_email:
return self.active_email
return self.auth_email