I think this is best explained with a code example.
Angular makes a POST request like this:
$http.post('/api/accounts/', {
username: username,
password: password,
email: email
}
This gets picked up by Django in urls.py:
router = routers.SimpleRouter()
router.register(r'accounts', AccountViewSet)
urlpatterns = [
url(r'^api/', include(router.urls)),
]
Here's AccountViewSet:
class AccountViewSet(viewsets.ModelViewSet):
serializer_class = AccountSerializer
queryset = Account.objects.all()
lookup_field = 'username'
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(
serializer.validated_data,
status=status.HTTP_201_CREATED
)
And here's the serializer:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
def create(self, validated_data):
return Account.objects.create_user(**validated_data)
At no point in this code can I see something which translates to "if Angular sends a POST request to api/accounts/ which has a username, password and e-mail address, go ahead and create an account".
So my question is this:
How does DRF know to call AccountViewSet.create(), and where exactly is AccountSerializer.create() getting called?
There seems to be a lot of magic happening here.
Thanks for your help.
Generally, right REST API for each resource in most cases consists of 2 endpoints:
List/Create endpoint
Details (Retrieve/Update/Destroy) endpoint.
POST-request is used in the first one and is used for item creation.
More detailed on the endpoints, described above. URLs for the endpoints usually are almost the same, except of ID which is present in the second endpoint. For example:
/api/items/ — List/Create endpoint, with GET-request it will return list of items, with POST-request it will create a new item and return it serialized.
/api/items/<item_id>/ — Details endpoint, which allows us to deal with some item, identified by <item_id>. GET-request returns the item, PUT request updates the whole item (all required fields should be present in payload, if some non-required field is omitted — it's value will be set to None), PATCH-request allows to update item partially, only the provided in payload fields will be updated, all other fields will stay untouched) and DELETE request, obviously, deletes the item.
Such approach applies some limitations sometimes, but if you follow it — your code will stay slim and clear, because a lot of things will be done in generic way. Both on back-end and front-end parts of your project.
Related
I am building a REST Api on DRF. I have a ModelViewSet endpoint from rest_framework.viewsets.
I have a Post and a Comment model. Each comment belongs to a post. So I have defined 2 endpoints, sort of:
1) router.register(r"posts", views.PostView
2) router.register(r"(?P<pk>[^/.]+)/comments", views.CommentView
Both of them inherit from ModelViewSet so I can perform CRUD operations on them.
I have a question regarding the second endpoint. Since we create comments to posts, I am getting a post pk from posts/int:pk/comments. But the problem is when I do a GET request on this endpoint it'll return the list of all comments, but I need those to belong to a post (id in url). When I try make a POST request on this endpoint if the post does not exist it raises DoesNotExist error which is logical.
What I have done so far is:
redefined a get_queryset() to retrieve only objects belonging to a particular post. If the post does not exist it returns an empty list (though I think it should raise 404)
redefined validate() in my serializer to check if the post exists. Returns 404 if it does not
But when I check PUT, DELETE, PATCH methods on posts/int:pk/comments/int:pk it won't take the post instance into consideration either i.e. it'll allow these operation even if the post does not exist which wrong I believe.
Is there a method in ModelViewSet class that gets triggered on every request and checks if the post instance exists? So that I don't have to redefine every CRUD method just to check it.
EDIT
CommentView
class CommentView(ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
lookup_url_kwarg = 'pk2'
def get_queryset(self):
queryset = Comment.objects.filter(post=self.kwargs.get('pk'))
return queryset
def perform_create(self, serializer, **kwargs):
post = get_object_or_404(Post, pk=self.kwargs.get('pk'))
serializer.save(author=self.request.user, post=post)
Maybe take a look at drf-nested-routers package.
If it doesn't solve your problem, maybe what you want is standard router registration:
router.register(r"posts", views.PostView)
router.register(r"comments", views.CommentView)
and then use a package like django-filter to filter via query params when listing the comments: /comments/?post=<id>. When creating a comment just send the post id in the body of the request (don't forget to include the 'post' field in CommentSerializer)
I am using ModelViewSet and Modelserializer for a blog like project.
It could be my difficulty in understanding the implementation; I can't get the update action to work via calling it through router, only the list action is working with the route I have defined.
When I put the url : 127.0.0.1:8000/api/blogs/1, to return the blog with ID 1 to edit, it returns {"Detail": "Not Found."}.
This is my view:
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
I have also overridden the save and update methods in the serializer class, don't know whether it was needed for ModelViewSet in ModelSerializer.
class ArticleSerializer(serializers.ModelSerializer):
def create(self, validated_data):
article = Article.objects.create(
article_title = self.validated_data['article_title'],
article_content = self.validated_data['article_content'],
...
)
return article
def update(self, instance, validated_data):
instance.article_title = validated_data.get('article_title', instance.article_title)
instance.article_content = validated_data.get('article_content', instance.article_content)
...
instance.save()
return instance
class Meta:
model = Article
fields = ...
And the urls.py file:
router = DefaultRouter()
router.register(r'blogs', ArticleViewSet, basename='articles-list')
urlpatterns = router.urls
My question is:
1. How do I specify urls for the ModelViewSet actions (in my case the update action)?
2. Will defining only one url suffice all my needs with every ModelViewSet actions? if so how?
What am I doing wrong? I'm new to DRF.
Regarding your questions:
1) Upon registering ModelViewSet in api router, it will create all required urls for the following actions. In your case it would be following:
list (GET request to /api/blogs/)
retrieve (GET request to
/api/blogs/{pk}/)
create (POST request to /api/blogs/)
update (PUT request to /api/blogs/{pk}/) (it will validate all fields of the model)
partial update (PATCH request to /api/blogs/{pk}/) (it will run no validation - you can send only
fields you've decided to change)
delete (DELETE request to /api/blogs/{pk}/)
So, basically router does most of the job for you about registering viewset actions.
2) I don't completely get it, but if my guess is correct - answer is the same as to first question.
About what you are doing wrong - I am not sure, but did you try appending slash at the end of your request (i.e not /api/blogs/1 but /api/blogs/1/)
Found the issue. I was trying the url localhost/api/blogs/1. It was returning this: "Detail": "Not Found".
It was because there were no instance saved with the id 1. All my saved intance had different ids which i didn't notice before. After putting available ids, it returned desired result.
What I am trying to do:
I am trying to access request object in my django models so that I can get the currently logged in user with request.user.
What I have tried:
I found a hack on this site. But someone in the comments pointed out not to do it when in production.
I also tried to override model's __init__ method just like mentioned in this post. But I got an AttributeError: 'RelatedManager' object has no attribute 'request'
Models.py:
class TestManager(models.Manager):
def user_test(self):
return self.filter(user=self.request.user, viewed=False)
class Test(models.Model):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(Test, self).__init__(*args, **kwargs)
user = models.ForeignKey(User, related_name='test')
viewed = models.BooleanField(default=False)
objects = TestManager()
I trying to access request object in my Django models so that I can get the currently logged in user with request.user.
Well a problem is that models are not per se used in the context of a request. One for example frequently defines custom commands to do bookkeeping, or one can define an API where for example the user is not present. The idea of the Django approach is that models should not be request-aware. Models define the "business logic" layer: the models define entities and how they interact. By not respecting these layers, one makes the application vulnerable for a lot of problems.
The blog you refer to aims to create what they call a global state (which is a severe anti-patten): you save the request in the middleware when the view makes a call, such that you can then fetch that object in the model layer. There are some problems with this approach: first of all, like already said, not all use cases are views, and thus not all use cases pass through the middleware. It is thus possible that the attribute does not exist when fetching it.
Furthermore it is not guaranteed that the request object is indeed the request object of the view. It is for example possible that we use the model layer with a command that thus does not pass through the middleware, in which case we should use the previous view request (so potentially with a different user). If the server processes multiple requests concurrently, it is also possible that a view will see a request that arrived a few nanoseconds later, and thus again take the wrong user. It is also possible that the authentication middleware is conditional, and thus that not all requests have a user attribute. In short there are more than enough scenario's where this can fail, and the results can be severe: people seeing, editing, or deleting data that they do not "own" (have no permission to view, edit, or delete).
You thus will need to pass the request, or user object to the user_test method. For example with:
from django.http import HttpRequest
class TestManager(models.Manager):
def user_test(self, request_or_user):
if isinstance(request_or_user, HttpRequest):
return self.filter(user=request_or_user.user, viewed=False)
else:
return self.filter(user=request_or_user, viewed=False)
one thus has to pass the request object from the view to the function. Even this is not really pure. A real pure approach would only accept a user object:
class TestManager(models.Manager):
def user_test(self, user):
return self.filter(user=user, viewed=False)
So in a view one can use this as:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
# return Http response
For example if we want to render a template with this queryset, we can pass it like:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
return render(request, 'my_template.html', {'some_tests': some_tests})
I'm learning how to implement Django channels into my website but I have problems understand the documentation (http://channels.readthedocs.io/en/latest/binding.html). There are not many examples on the internet. Can someone provide me a working source code of data binding with decent commenting?
For django-channels 1 - https://bitbucket.org/voron-raven/chat/src/aec8536dba2cc5f0faa42305dcd7a49d330a8b54/core/models.py?at=master&fileviewer=file-view-default#models.py-242
It's simple chat site with a chatbot - http://chat.mkeda.me
Example of data binding:
class MessageBinding(WebsocketBinding):
model = Message
stream = 'messages'
fields = ['__all__']
#classmethod
def group_names(cls, instance):
"""
Returns the iterable of group names to send the object to based on the
instance and action performed on it.
"""
return ['thread-{}'.format(instance.thread.pk)]
def has_permission(self, user, action, pk):
"""
Return True if the user can do action to the pk, False if not.
User may be AnonymousUser if no auth hooked up/they're not logged in.
Action is one of "create", "delete", "update".
"""
if action == 'create':
return True
return user.is_superuser
In django-channels 2 binding was removed - http://channels.readthedocs.io/en/latest/one-to-two.html?highlight=binding#removed-components
You can use signals to send updates to your groups.
I have an Adobe Air mobile application that communicates with Django via TastyPie. To use the app people have to register first. Therefore they have to supply their email and password. Afterwards they will be able to "login". I thought it would be the best idea that after entering a successful username/password combination, the api-key will be sent back to the mobile app where it will be cached, so the user is "logged in".
Please tell me if you think there is a better way for registering and "logging in" users.
Inside Django I have a UserRessource class that I use to register new users when sending data via POST:
class UserResource(ModelResource):
class Meta:
allowed_methods = ['get', 'post']
queryset = User.objects.all()
resource_name = 'auth'
authentication = Authentication()
authorization = Authorization()
fields = ['username', 'email']
def obj_create(self, bundle, request=None, **kwargs):
username, email, password = bundle.data['username'], bundle.data['password'], bundle.data['password'],
try:
bundle.obj = User.objects.create_user(username, email, password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
That works very well.
But now I'm struggling with the actual login process. In my opinion it would be best to send username and password via GET (and https) to this ressource and if those are valid, return the users api key. But would that be possible? And is it clean? Usually TastyPie would show all users currently in the DB if you send a GET request to that ressource. But I dont need that data, so I could overwrite that somehow. I already checked http://django-tastypie.readthedocs.org/en/v0.9.9/resources.html but I don't get it to work. Is it even possible to overwrite that behaviour?
So the actual questions are Whats the best way to "sign in" a user using ApiKeyAuthentication?
And Is my approach right and clean or do you have a better method? and Do you have any examples for this case?
Thanks alot in advance!
I'm using BasicAuth so it may be slightly different. But my solution is basicaly an empty resource that requires authentication. If the authentication is a success the service returns response code 200 and the authenticated user, I override obj_get_list and stuff the authenticated user in there. If the credentials are wrong the service returns response code 401.
class LoginResource(ModelResource):
class Meta:
allowed_methods = ['get']
resource_name = 'login'
include_resource_uri = False
object_class = User
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
def obj_get_list(self, bundle, **kwargs):
return [bundle.request.user]
Okay I'll try to explain my point of view on the topic:
First the UserResource example on the tastypie page for me has one significant issue:
The User Objects should not be presented at any time to the single User, they should be able to see they're own "profile" or whatever but never browse and see others. Of course that can be done and with UserResource by clearing the main "list view" of that resource and applying the APIKeyAuth to the individual profiles, but still I don't like the idea of UserResource.
Second in the form when you are developing an API(such as tastypie usage) the APIKey is the actual "password" so what should be send on request is not the username and password but the username and APIKey, which is obtained in other manners(normally an e-mail or some kind of website based UI). Than they are recommended to be send via Authorization Header and not in GET parameters.
Third when we are talking about API there is no such thing as sign-in - at least not in the RESTFULL APIs - it is in some sense connectionless, so you actually going to send the Authorization header with each request.
As to the question yes you can overwrite the data. Look at the hydrate/dehydrate cycle in the Tastypie docs to understand how does it renders the content and if you have more question go ahead and ask.