I am new Django, I try make REST API. Now face one issue. I created 2 models Account & Transaction
class Account(models.Model):
id = models.UUIDField(default=uuid.uuid4, unique=True,primary_key=True,editable=False)
user = models.ForeignKey(User,on_delete=models.CASCADE)
account_name = models.CharField(max_length=100)
Account have ForeignKey with user model
class Transaction(models.Model):
id = models.UUIDField(default=uuid.uuid4(),primary_key=True,editable=False)
account = models.ForeignKey(Account,on_delete=models.CASCADE,related_name='account')
transaction_no = models.CharField(default=str(uuid.uuid4())[:8],max_length=100)
Transaction have ForeignKey with Account model. then get JWT token & pass on API. In view.py I filtered by requested user
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getAccount(request,pk):
account = Account.objects.filter(user=request.user).get(id=pk)
serializer = AccountSerializer(account, many=False)
return Response(serializer.data)
now how will filter Transaction only by auth User
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getTransactions(request,account_id):
transactions = Transaction.objects.filter(account=account_id)
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
You filter with:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getTransactions(request):
transactions = Transaction.objects.filter(account__user=request.user)
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
Here we thus retrieve Transactions for which the account refers to an Account object with request.user as user.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
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?
How to use POST to retrieve single data on models?
My idea, at the beginning, was to pass a map of parameters. Then the view, on the server side, would take care of reading the needed parameters in the map and return the response.
When I tested this in Postman, I send request body with email and password, but then it returns an error: 'name is required'
I want this api to work like generics. Retrieve but not with url, but with POST instead
Models.py
class Member(models.Model):
name = models.CharField(max_length=100)
password = models.CharField(max_length=100)
email = models.EmailField(unique=True)
phone = models.IntegerField(default=9999)
serializer.py
class LoginMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields =[
'name',
'password',
'email',
'phone',
]
view.py
class LoginMemberAPI(APIView):
def get_queryset(self):
return Member.objects.all()
def post(self, request, format=None):
serializer = LoginMemberSerializer(data=request.data)
if serializer.is_valid():
print(serializer.validated_data['email'])
member = Member.objects.get(name = str(serializer.validated_data['name']))
# serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
Well, indeed the serializer is not valid, as it is supposed to be a complete representation of a model and you're only sending a single field.
It doesn't really make sense to use a serializer for this. Just use the data to query the db and then create a serializer for the response:
member = Member.objects.get(**request.POST)
serializer = LoginMemberSerializer(member)
return Response(serializer.data)
I would say, the fact that you are struggling with this should be able indication that this isn't the right thing to do. POST is meant for sending data that updates the db, not for retrieving data.
Your serializer is using fields of Model and in your 'Member' model, all fields are required. You can not do this with same Serializer. You can create separate serializers for validation and for returning serialized response. Something like this.
Serializer for response:
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
# '__all__' will include all fields of models
fields = '__all__'
Serializer for validation of this Api:
class LoginMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields =[
'name',
'password',
]
I want to create an API that allows to send JSON through a POST request.
The JSON should then get passed on to a serializer which takes care of creating a new object and populate it with the existing data.
It works fine for the 'simple' cases such as basic character-only inputs like usernames and alike, but I am seriously stuck when it comes to creating a OneToOne relation. Here's the sample code.
Function called employee_list in views.py - data['account'] is a valid username, a User instance is successfully being selected!
data = JSONParser().parse(request)
user_object = User.objects.get(username=data['account'])
data['account'] = user_object # this is now a valid User object
serializer = EmployeeSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
The model
class Employee(models.Model)
id = models.AutoField(primary_key=True)
name = models.CharField(...)
surname = models.CharField...
account = models.OneToOneField(User)
role = ...
salary = ...
picture = ...
And the serializer
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = (...) # all fields of the `Employee` model
So far so good, however, the serializer never validates! When I remove the need for a OneToOne relation, it works..
How can I create a new Employee objects with a working OneToOne relationship to a User object?
Thanks in advance.
You need to pass the pk of the User object, rather than the User object.
Solution:
data['account'] = user_object.pk
The issue is because a user object is being passed to the serializer, when it can be just passed to the serializer, while saving the object. Try something like this:
data = JSONParser().parse(request)
user_object = User.objects.get(username=data['account'])
serializer = EmployeeSerializer(data=data)
if serializer.is_valid():
# pass object to save, instead of saving in the data dictionary
serializer.save(account=user_object)
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
My Category model is related to User, but I can't find a way to store the logged in user's id into user_id field in Category.
Category models.py:
class Category(models.Model):
user = models.ForeignKey(User, default=None)
name = models.CharField(max_length=32, unique=True)
views.py:
class CategoryList(APIView):
...
def post(self, request):
"""
Create a new Category
:param request:
:return: Category
"""
user_id = request.user.pk
serializer = CategorySerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
I can access request.user.pk and see it's correctly shown, but I can't figure out how I can store this value when creating a new category.
To add current user id to new record you can pass current user as save argument:
if serializer.is_valid():
serializer.save(user=request.user)
Or you can change serializer and use CurrentUserDefault:
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
from docs:
A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer.
So to use CurrentUserDefault you need to pass request to serializer in view:
serializer = CategorySerializer(data=request.data, context={'request': request})
Is it possible to get the current user in a model serializer? I'd like to do so without having to branch away from generics, as it's an otherwise simple task that must be done.
My model:
class Activity(models.Model):
number = models.PositiveIntegerField(
blank=True, null=True, help_text="Activity number. For record keeping only.")
instructions = models.TextField()
difficulty = models.ForeignKey(Difficulty)
categories = models.ManyToManyField(Category)
boosters = models.ManyToManyField(Booster)
class Meta():
verbose_name_plural = "Activities"
My serializer:
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
And my view:
class ActivityDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Activity.objects.all()
serializer_class = ActivityDetailSerializer
How can I get the model returned, with an additional field user such that my response looks like this:
{
"id": 1,
"difficulty": 1,
"categories": [
1
],
"boosters": [
1
],
"current_user": 1 //Current authenticated user here
}
I found the answer looking through the DRF source code.
class ActivitySerializer(serializers.ModelSerializer):
# Create a custom method field
current_user = serializers.SerializerMethodField('_user')
# Use this method for the custom field
def _user(self, obj):
request = self.context.get('request', None)
if request:
return request.user
class Meta:
model = Activity
# Add our custom method to the fields of the serializer
fields = ('id','current_user')
The key is the fact that methods defined inside a ModelSerializer have access to their own context, which always includes the request (which contains a user when one is authenticated). Since my permissions are for only authenticated users, there should always be something here.
This can also be done in other built-in djangorestframework serializers.
As Braden Holt pointed out, if your user is still empty (ie _user is returning None), it may be because the serializer was not initialized with the request as part of the context. To fix this, simply add the request context when initializing the serializer:
serializer = ActivitySerializer(
data=request.data,
context={
'request': request
}
)
A context is passed to the serializer in REST framework, which contains the request by default. So you can just use self.context['request'].user inside your serializer.
I had a similar problem - I tried to save the model that consist user in, and when I tried to use
user = serializers.StringRelatedField(read_only=True, default=serializers.CurrentUserDefault()) like on official documentation - but it throws an error that user is 'null'. Rewrite the default create method and get a user from request helped for me:
class FavoriteApartmentsSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = FavoriteApartments
exclude = (
'date_added',
)
def create(self, validated_data):
favoriteApartment = FavoriteApartments(
apartment=validated_data['apartment'],
user=self.context['request'].user
)
favoriteApartment.save()
return favoriteApartment
I modified the request.data:
serializer = SectionSerializer(data=add_profile_data(request.data, request.user))
def add_profile_data(data, user):
data['user'] = user.profile.id
return data