In Django with Tastypie, is there a way to configure a resource such that it only shows object details?
I want to have a url /user which returns the details of the authenticated user, as opposed to a list containing a single user object. I don't want to have to use /users/<id> to get the details of a user.
Here's the relevant portion of my code:
from django.contrib.auth.models import User
from tastypie.resources import ModelResource
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
allowed_methods = ['get', 'put']
serializer = SERIALIZER # Assume those are defined...
authentication = AUTHENTICATION # "
authorization = AUTHORIZATION # "
def apply_authorization_limits(self, request, object_list):
return object_list.filter(pk=request.user.pk)
I was able to do this by using a combination of the following resource methods
override_urls
apply_authorization_limits
Example user resource
#Django
from django.contrib.auth.models import User
from django.conf.urls import url
#Tasty
from tastypie.resources import ModelResource
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'users'
#Disallow list operations
list_allowed_methods = []
detail_allowed_methods = ['get', 'put', 'patch']
#Exclude some fields
excludes = ('first_name', 'is_active', 'is_staff', 'is_superuser', 'last_name', 'password',)
#Apply filter for the requesting user
def apply_authorization_limits(self, request, object_list):
return object_list.filter(pk=request.user.pk)
#Override urls such that GET:users/ is actually the user detail endpoint
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
]
Using something other than the primary key for getting the details of a resource is explained in more detail in the Tastypie Cookbook
Related
I am very new to django and currently trying to generate api calls to localhost:8000/stateapi/id where id is an id for a single "State" object in a json (like 1, 2, etc).
It is using token authentication by passing a username and password to the "get-token" endpoint and then using that token for calls to the stateapi endpoint.
I mostly followed this tutorial https://scotch.io/tutorials/build-a-rest-api-with-django-a-test-driven-approach-part-2
and keep getting a "detail": "You do not have permission to perform this action."
Here are the views where CreateView handles the "localhost:8000/stateapi" endpoint and DetailsView handles the localhost:8000/stateapi/id endpoint.
class CreateView(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
def perform_create(self, serializer):
"""Save the post data when creating a new State."""
serializer.save(owner=self.request.user)
class DetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
I can't seem to figure out why the authenticated user has permission to access information from CreateView but not DetailsView.
Here is the permissions code:
class IsOwner(BasePermission):
"""Custom permission class to allow only bucketlist owners to edit them."""
def has_object_permission(self, request, view, obj):
# Our problem is that we do not have a owner property for the object
"""Return True if permission is granted to the bucketlist owner."""
return obj.owner == request.user
Upon testing what happens when DetailsView is called, i've found that obj.owner is "None" when DetailsView is called and obj.owner is correctly equal to request.user whenever CreateView is called which would explain why the authenticated user can make get requests to the endpoint without an id while it cannot for the endpoint with an id.
Are there an suggestions as to how I could either:
a) make sure obj has the correct "owner" property (something that CreateView is doing but DetailsView is not)
b) change my permissions in some way
c) something else I cannot think of.
Thanks!
Can you share your State model and StateSerializer – Iain Shelvington Jun 18 at 3:26
State Model:
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from django.dispatch import receiver
# Create your models here.
# 1 is /, 2 is -, 3 is (, 4 is ), 5 is .
class State(models.Model):
STATE = models.CharField(max_length=30,blank=True,null=True)
Team_Contact = models.CharField(max_length=100,blank=True,null=True)
CONTACT_INFORMATION = models.TextField(blank=True,null=True)
LEGISLATION1EXECUTIVE_ORDER = models.TextField(blank=True,null=True)
TESTING = models.TextField(blank=True,null=True)
TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4 = models.TextField(blank=True,null=True)
PRE2EMPTION = models.TextField(blank=True,null=True)
owner = models.ForeignKey('auth.User', related_name='statelists', on_delete=models.CASCADE,blank=True,null=True)
OVERSIGHT_DEPARTMENT = models.TextField(blank=True,null=True)
INFRASTRUCTURE_DEVELOPMENTS = models.TextField(blank=True,null=True)
CRASHES1SAFETY_INCIDENTS = models.TextField(blank=True,null=True)
DATA1PRIVACY_CONCERNS = models.TextField(blank=True,null=True)
PUBLIC_EDUCATION_FOR_AVS = models.TextField(blank=True,null=True)
LIABILITY1INSURANCE_REQUIREMENTS = models.TextField(blank=True,null=True)
HEALTH1EQUITY_CONCERNS = models.TextField(blank=True,null=True)
MISC5 = models.TextField(blank=True,null=True)
def __str__(self):
"""Return a human readable representation of the model instance."""
return "{}".format(self.STATE)
# This receiver handles token creation immediately a new user is created.
#receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Serializers:
from rest_framework import serializers
from .models import State
from django.contrib.auth.models import User
class StateSerializer(serializers.ModelSerializer):
"""Serializer to map the Model instance into JSON format."""
# understand exactly what this line does
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
"""Meta class to map serializer's fields with the model fields."""
model = State
fields = ('id','STATE','Team_Contact','CONTACT_INFORMATION','LEGISLATION1EXECUTIVE_ORDER','TESTING',
'TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4','PRE2EMPTION','OVERSIGHT_DEPARTMENT','INFRASTRUCTURE_DEVELOPMENTS',
'CRASHES1SAFETY_INCIDENTS','DATA1PRIVACY_CONCERNS','PUBLIC_EDUCATION_FOR_AVS','LIABILITY1INSURANCE_REQUIREMENTS',
'HEALTH1EQUITY_CONCERNS','MISC5', 'owner')
read_only_fields = ('STATE', 'Team_Contact','CONTACT_INFORMATION')
class UserSerializer(serializers.ModelSerializer):
"""A user serializer to aid in authentication and authorization."""
states = serializers.PrimaryKeyRelatedField(
many=True, queryset=State.objects.all())
class Meta:
"""Map this serializer to the default django user model."""
model = User
fields = ('id', 'username', 'states')
All things here seem to be normal. I think the problem comes from other parts of your code? Or your checked object actually doesn't have any owner linked to it?
As stated in DRF documentation http://www.django-rest-framework.org/api-guide/parsers/#multipartparser, in order to parse multipart/form-data, the MultiPart and form parser must be used. I have a supiscion this is a problem in the Django Rest Framework because I saw a solution on their github issue saying it work using APIView.
from django.contrib.auth.models import User
from rest_framework import viewsets
from api.serializers import UserSerializer,
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework import status
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
parser_classes = (MultiPartParser, FormParser)
serializer_class = UserSerializer
Picture of me sending request to Postman with result:
Edit: Adding UserSerializer class
class UserSerializer(serializers.HyperlinkedModelSerializer):
snapcapsules = SnapCapsuleSerializer(
many=True,
read_only=True,
allow_null=True,
)
class Meta:
model = User
fields = ('snapcapsules', 'url', 'username', 'email', 'password', )
write_only_fields = ('password',)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
capsules_data = validated_data.pop('snapcapsules')
for capsule in capsules_data:
SnapCapsule.objects.create(user=instance, **capsule)
return instance
This is might not the issue with the ContentType. The error response says that,the UserSerializer excpect a payloadordata which include username and password field
So, Please try to add those fields to the request body and try again
UPDATE
The problem with the Orginal Post was, he added extra headers in POSTMAN (see the pic below) .
Postman will add appropriate Headers aromatically for you. So, you don't have to explicitly mention it
I removed one line and then it worked!
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
Also, the serializer can be rewrite as follows:
class UserSerializer(serializers.HyperlinkedModelSerializer):
...
def create(self, validated_data):
return User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)
...
I am designing a REST API for an application that has both companies and departments. Some number of users can be members of a company. This leads to the following API structure:
/companies/ - Can GET, POST.
/companies/<pk>/ - Can GET, POST, PUTCH, PATCH, DELETE.
/companies/<pk>/membership/ - Can GET (gives all users that are members of a company), POST.
/companies/<pk>/membership/<pk>/ - Can DELETE.
I've managed to implement the first 3 of the endpoints, but am having trouble with implementing the last one -- how do I implement an endpoint that has multiple <pk> values in the URL? Here's what I have so far:
Currently have a urls.py file in the api app that looks as follows:
...
url(r'^company', include(company_urls.company_router.urls,
namespace="company")),
...
urls.py in the company application.
from rest_framework import routers
from .views import CompanyViewSet
company_router = routers.DefaultRouter()
company_router.register(r'^', CompanyViewSet)
serializers.py file:
from rest_framework import serializers
from .models import Company, CompanyMembership
from My_App.users.models import Profile
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('pk', 'name', 'departments', 'members')
read_only_fields = ('pk', 'departments', 'members')
class CompanyMembershipSerializer(serializers.Serializer):
user = serializers.PrimaryKeyRelatedField(queryset=Profile.objects.all())
def create(self, validated_data):
pass
def delete(self, instance, validated_data):
pass
And the views.py file:
from .models import Company, CompanyMembership
from .serializers import CompanySerializer, CompanyMembershipSerializer
from My_Appc.users.models import Profile
class CompanyViewSet(viewsets.ModelViewSet):
queryset = Company.objects.all()
serializer_class = CompanySerializer
#decorators.detail_route(methods=['get', 'post', 'delete'])
def membership(self, request, pk):
company = self.get_object()
if request.method == 'GET':
serializer = CompanyMembershipSerializer(company)
elif request.method == 'POST':
serializer = CompanyMembershipSerializer(data=request.data)
if serializer.is_valid():
try:
user = Profile.objects.get(pk=request.data.get('user'))
user_company_membership = CompanyMembership(user=user,
company=company)
user_company_membership.save()
return Response({'status': 'User added to Company.'},
status=status.HTTP_201_CREATED)
except IntegrityError:
result = {
'status': 'Failed to add user to Company.',
'reason': 'User already part of Company.'
}
status=settings.ADDITIONAL_HTTP_STATUS_CODES[
'422_UNPROCESSABLE_ENTITY']
return Response(result, status)
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Use different names for parameters in you url:
/companies/<company_pk>/membership/<membership_pk>/
And in you ViewSet add lookup_field and lookup_url_kwarg to point to company pk field/parameter:
class CompanyViewSet(viewsets.ModelViewSet):
lookup_field = 'pk'
lookup_url_kwarg = 'company_pk'
get_object method uses this two lookups to filter queryset so you will get company based on first pk in url.
In your membership method and custom logic to manage membership object, you can access membership pk with:
membership_pk = self.kwargs.get('membership_pk', None)
I am trying to use the django rest framework to expose my models as APIs.
serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
viewset
class UserViewSet(viewsets.ModelViewSet):
"""
API end point for User details and list
"""
serializer_class = UserSerializer
queryset = User.objects.all()
routers
router.register(r'users',views.UserViewSet)
While this exposes /users/ and users/, I want my URLs to include a user-slug as well, something like /users/1/xyz-user-name.
Has anyone solved this problem? Does this need changes in both the viewset and router code or is it something that can be configured only in the router code? MY "slug" isn't really used for determining url routing, it is only for URL readability.
Any pointers?
I was able to get this to work by using the approach posted here.
django-rest-framework HyperlinkedIdentityField with multiple lookup args
The second error I was receiving was becuase I was including the url definition inside the meta section. It should be before the meta section instead. I also had to specify the lookup field in the viewset code. Here are the relevant parts of my code.
urls.py
from user.views import UserViewSet
user_list = UserViewSet.as_view({'get':'list'})
user_detail = UserViewSet.as_view({'get':'retrieve'})
urlpatterns= [
url(r'^users/$', user_list, name='user-list'),
url(r'^user/(?P<id>\d+)/(?P<slug>[-\w\d]+)/$', user_detail, name='user-detail'),
url(r'^api-auth/', include('rest_framework.urls',namespace = 'rest_framework'))
]
views.py:
class UserViewSet(viewsets.ModelViewSet):
"""
API end point for user details and user list
"""
lookup_field = 'id'
serializer_class = UserSerializer
queryset = user.objects.all()
serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
url = ParameterisedHyperlinkedIdentityField(view_name='user-detail', lookup_fields=(('id', 'id'), ('slug', 'slug')), read_only=True)
class Meta:
model = user
fields = ('url','name','cover_photo')
You should set the lookup_field property in the serializers and viewsets.
In the serializers.py:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'user_slug')
lookup_field = 'user_slug'
extra_kwargs = {
'url': {'lookup_field': 'user_slug'}
}
In the viewsets.py:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = ('user_slug')
I need to execute filter query inside tastypie resources. the input should be the headers of url for example
new Ext.data.Store({
proxy: {
url :'api/users/'
type: "ajax",
headers: {
"Authorization": "1"
}
}
})
I tried below
from tastypie.authorization import Authorization
from django.contrib.auth.models import User
from tastypie.authentication import BasicAuthentication
from tastypie import fields
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from tastypie.validation import Validation
from userInfo.models import ExProfile
class UserResource(ModelResource,request):
class Meta:
queryset = User.objects.filter(id=request.META.get('HTTP_AUTHORIZATION'))
resource_name = 'user'
excludes = ['email', 'password', 'is_active', 'is_staff', 'is_superuser']
authorization = Authorization()
authentication=MyAuthentication()
it is saying name 'request' is not defined. How to pass filters on ORM them?
Not sure why are you inheriting request in UserResource.
I needed to do something like this and the best solution I could come up was to overwrite the dispatch method. Like this
class UserResource(ModelResource):
def dispatch(self, request_type, request, **kwargs):
self._meta.queryset.filter(id=request.META.get('HTTP_AUTHORIZATION'))
return super(UserResource, self).dispatch(request_type, request, **kwargs)
Well , i found apply_filter very useful.
We can pass link like
http://localhost:8000/api/ca/entry/?format=json&userid=a7fc027eaf6d79498c44e1fabc39c0245d7d44fdbbcc9695fd3c4509a3c67009
Code
class ProfileResource(ModelResource):
class Meta:
queryset =ExProfile.objects.select_related()
resource_name = 'entry'
#authorization = Authorization()
#authentication = MyAuthentication()
filtering = {
'userid': ALL,
'homeAddress': ALL,
'email': ALL,
'query': ['icontains',],
}
def apply_filters(self, request, applicable_filters):
base_object_list = super(ProfileResource, self).apply_filters(request, applicable_filters)
query = request.META.get('HTTP_AUTHORIZATION')
if query:
qset = (
Q(api_key=query)
)
base_object_list = base_object_list.filter(qset).distinct()
return base_object_list