Django Restful API Design Validation Logic - django

Here I have an endpoint to create media content for users. The endpoint works, but I have a feeling my design implementation is incorrect.
Should validation logic be contained in serializers create? Is this bad practice? I attempted to move validation logic to models.py, but ran into issues with accessing the model, specifically this line - self.model(user=user, category=category).
view.py
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import UserMedia
from .renderers import UserMediaSerializerJSONRenderer
from .serializers import UserMediaSerializer
class UserMediaCreateAPIView(APIView):
permission_classes = (IsAuthenticated,)
renderer_classes = (UserMediaSerializerJSONRenderer,)
serializer_class = UserMediaSerializer
def post(self, request):
userMedia = request.data.get('userMedia', {})
serializer = self.serializer_class(data=userMedia)
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user, category=userMedia['category'])
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializers.py
from rest_framework import serializers
from .models import UserMedia
class UserMediaSerializer(serializers.ModelSerializer):
category = serializers.CharField(allow_blank=False, required=True)
class Meta:
model = UserMedia
fields = ('category',)
read_only_fields = ('category',)
def get_category(self, obj):
if obj.category:
return obj.category
return 'N/A'
def create(self, validated_data):
if validated_data['user'] is None:
raise TypeError('User media must have a user')
if validated_data['category'] is None:
raise TypeError('User media must have a category.')
if validated_data['category'] not in dict(UserMedia.CATEGORY_CHOICES):
raise TypeError('User media category is not available.')
userMedia = UserMedia(**validated_data)
userMedia.save()
return userMedia
models.py
from django.db import models
class UserMedia(models.Model):
user = models.ForeignKey('authentication.User', on_delete=models.CASCADE, related_name='media')
MUSIC = 'M'
VIDEO = 'V'
CATEGORY_CHOICES = (
(MUSIC, 'Music'),
(VIDEO, 'Video'),
)
category = models.CharField(max_length=1, choices=CATEGORY_CHOICES, blank=False)

The validation should be done in your view. The serializers should just be for serializing data. The validation should be done in your view then the serializer is called from your view. As far as this line self.model(user=user, category=category) is concerned it does not appear that you ever import user any where.

Related

Django Rest Framework, request POST, update if exists, create if not exists from mass data of POST request

I am building an API for users info data
I want to make that when the POST request, execute function "create", "update"
if from POST request user exists:
update (full_name, function, department, logon, system, lic_type )
if from POST request user doesn't exist:
create (user, full_name, function, department, logon, system, lic_type )
models.py
from django.db import models
class Users(models.Model):
user = models.CharField(max_length=50,blank=True, null=True)
full_name = models.CharField(max_length=200, blank=True, null=True)
function = models.CharField(max_length=300,blank=True, null=True)
department = models.CharField(max_length=300,blank=True, null=True)
logon = models.DateTimeField(blank=True, null=True)
system = models.CharField(max_length=300,blank=True, null=True)
lic_type = models.CharField(max_length=300,blank=True, null=True)
serizlizers.py
from rest_framework import serializers
from .models import Users
class UsersSerializer(serializers.ModelSerializer):
logon = serializers.DateTimeField(input_formats=settings.DATE_INPUT_FORMATS)
class Meta:
model = Users
# fields = '__all__'
fields = ['user', 'full_name', 'function', 'department', 'logon', 'system', 'lic_type']
views.py
from django.http.response import JsonResponse
from rest_framework.parsers import JSONParser
from rest_framework import status
from .models import Users
from .serializers import UsersSerializer
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from rest_framework.authentication import BasicAuthentication
#csrf_exempt
#api_view(['GET', 'POST'])
#authentication_classes([BasicAuthentication])
def users_list(request):
if request.method == 'GET':
users = Users.objects.all()
user = request.GET.get('user', None)
if user is not None:
users = users.filter(user__icontains=user)
users_serializer = UsersSerializer(users, many=True)
return JsonResponse(users_serializer.data, safe=False)
elif request.method == 'POST':
users_data = JSONParser().parse(request)
users_serializer = UsersSerializer(data=users_data, many=True)
if users_serializer.is_valid():
users_serializer.save()
return Response(users_serializer.data, status=status.HTTP_201_CREATED)
return Response(users_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('users/', views.users_list),
]
I could do like this, when POST request delete all from database and create data from POST request
elif request.method == 'POST':
users = Users.objects.all()
users.delete()
users_data = JSONParser().parse(request)
users_serializer = UsersSerializer(data=users_data, many=True)
if users_serializer.is_valid():
users_serializer.save()
return Response(users_serializer.data, status=status.HTTP_201_CREATED)
return Response(users_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
but instead I want to make update_or_create()
I tried like so, but this only creates empty row in database
user_name = request.POST.get('user')
user, created = Users.objects.update_or_create(user = user_name)
user.full_name = request.POST.get('full_name')
user.function = request.POST.get('function')
user.department = request.POST.get('department')
user.logon = request.POST.get('logon')
user.system = request.POST.get('system')
user.lic_type = request.POST.get('lic_type')
user.save()
return Response(user, status=status.HTTP_201_CREATED)
thanks for any help
The best way is when a POST is there, you first retrieve the item from the database (using django's utility get_object_or_404) and have restframework update all fields you set as non-readonly in the serializer.
Example:
# on top import
from django.shortcuts import get_object_or_404
....
elif request.method == 'POST':
# add here the query you determine is user already exists, usually a unique ID or UUID
user_object = get_object_or_404(Users, id=request.data.get('id'))
users_data = JSONParser().parse(request)
users_serializer = UsersSerializer(user_object, data=users_data, many=True)
if users_serializer.is_valid():
users_serializer.save()
return Response(users_serializer.data, status=status.HTTP_201_CREATED)
return Response(users_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Something like this ought to work. You still need to tweak it a little the get object

Django unit testing authentication token not accepted

I've checked the DRF official documentation, have read the below posts and copy-pasted code from some answers to avoid any typo, but I still can't authenticate in my unit tests. I always get a 401 response code.
I have a custom User model that just has a couple of none required fields so far.
Can anyone see any logic issue, or error in the code?
Could the issue originate from having a proper local DB and a testing DB and the authentication mixing both up somehow?
I've had to use self.client.force_authenticate(user=self.user, token=None) to bypass the authentication issue. But that defeats the purpose of the tests.
Checked posts that did not solve the problem:
DRF documentation
SO post 1
SO post 2
SO post 3
So here's my code.
Unit test
from django.test.testcases import SimpleTestCase
from django.urls import reverse, resolve
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework.authtoken.models import Token
from test_project.models.user import *
from test_project.views import UserViewSet
# from django.contrib.auth.models import User
from test_project.models import *
class UserAPIViewTests(APITestCase):
def setUp(self) -> None:
self.users_list_url = reverse('user-list')
self.users_detail_url = reverse('user-detail', args=[1])
self.user =User.objects.create(
username="admin",
password="admin",
email="test#necktie.com",
first_name="test first name",
last_name="test last name",
is_superuser=True,
is_staff=True,
is_active=True
)
self.token = Token.objects.create(user=user)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token.key)
# The testing DB is automatically torn down, no implementation required for those tests
def tearDown(self) -> None:
pass
def test_get_users_authenticated(self):
"""
Test that an authenticated user can get the users' list
"""
response = self.client.get(self.users_list_url)
self.assertEquals(response.status_code, status.HTTP_200_OK)
User model
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
"""auth/login-related fields"""
gender = models.CharField(_('gender'), max_length=50, null=True)
nationality = models.CharField(_('nationality'), max_length=50, null=True)
def __str__(self):
return "{} {}".format(self.first_name, self.last_name)
User View
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import permissions
from ..models.user import User
from ..serializers.user import *
from ..permissions import *
class UserViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
"""
Method to apply permissions depending on request type (GET, PUT etc.)
"""
if self.request.method == 'GET':
return [permissions.IsAuthenticated(), IsStaff()]
else:
return [permissions.IsAuthenticated(), IsSuperuser()]
User serializers
from rest_framework import serializers
from ..models.user import User
class UserSerializer(serializers.ModelSerializer):
"""
Serializer for all actions on User model
"""
class Meta:
model = User
fields = [
'id',
'username',
'first_name',
'last_name',
'is_staff',
'is_superuser',
'is_active',
'date_joined'
]
Thanks for the help!

How to POST multiple data in DRF and React with Axios

I have 2 models named Recipe and Step..
I have serialized both to make an API for GET request.. I want to know is there a way to create for POST request so that I can send both the data (steps and recipe) in the same request?
models.py:
from django.db import models
class Recipe(models.Model):
title = models.CharField( max_length=50)
uuid = models.CharField( max_length=100)
def __str__(self):
return f'{self.uuid}'
class Step(models.Model):
step = models.CharField(max_length=300)
uuid = models.ForeignKey(Recipe, on_delete=models.CASCADE)
def __str__(self):
return f'{self.step} - {self.uuid}'
serializers.py:
from rest_framework import serializers
from .models import *
class RecipeSerializer(serializers.ModelSerializer):
class Meta:
model = Recipe
fields = ['title', 'uuid']
class StepSerializer(serializers.ModelSerializer):
class Meta:
model = Step
fields = ['step', 'uuid']
views.py:
from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import *
from .models import *
#api_view(['GET'])
def apiOverview(request):
api_urls = {
'List':'/recipe-list/',
'Detail View':'/recipe-detail/<str:pk>/',
'Create':'/recipe-create/',
'Update':'/recipe-update/<str:pk>/',
'Delete':'/recipe-delete/<str:pk>/',
'Steps' : '/steps/<str:pk>'
}
return Response(api_urls)
#api_view(['GET'])
def recipeList(request):
recipes = Recipe.objects.all()
serializer = RecipeSerializer(recipes, many=True)
return Response(serializer.data)
#api_view(['GET'])
def recipeDetail(request, pk):
recipe = Recipe.objects.get(uuid=pk)
recipe_serializer = RecipeSerializer(recipe, many=False)
steps = Step.objects.filter(uuid=pk)
steps_serializer = StepSerializer(steps, many=True)
return Response({
'recipe' : recipe_serializer.data,
'steps' : steps_serializer.data
})
How can I create a view for POST and handle both the models?
Try:
from rest_framework import generics
from .models import *
class StepAndRecipe(generics.CreateAPIView):
queryset = Step.objects.all()
queryset = Recipe.objects.all()
serializer_class = StepSerializer
serializer_class = RecipeSerializer
Add in urls.py:
from django.urls import path
from .views import StepAndRecipe
urlpatterns = [
path('steprecipepost', StepAndRecipe.as_view(), name='steps_recipes')
This will only work with POST! And one more thing: take care with the raw data and the HTML form, maybe theses get a little confused since you are using two models in the same view.

How can I make sure that the data is from the login user in Django?

I'm trying to save the data I've received from Arduino in the DB. We have succeeded in receiving and storing temperature and humidity data, but failed to link this data with logged-in users. Can you help me?
Here is my code.
views.py
from .models import arduino
from .serializers import arduinoSerializers
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView
class arduinoToAndroidViewSet (ViewSet) :
def dataSend (self, request) :
user = self.request.user
queryset = arduino.objects.filter(name = user)
serializer = arduinoSerializers(queryset, many=True)
return Response(serializer.data)
class arduinoToDatabaseViewSet (CreateAPIView) :
serializer_class = arduinoSerializers
def get_queryset(self) :
user = self.request.user
return arduino.objects.filter(name = user)
def dataReceive(self, request) :
queryset = get_queryset()
serializer = arduinoSerializers(queryset, many=True)
if serializer.is_valid() :
serializer.save()
return Response(serializer.data)
serializers.py
from rest_framework import serializers
from .models import arduino
class arduinoSerializers (serializers.ModelSerializer) :
name = serializers.CharField(source='name.username', read_only=True)
class Meta :
model = arduino
fields = ('name', 'temp', 'humi')
If you post it like this,
I want you to know that this is root's data.
I log in to the test account, put the data in, and press the post button.
This will not Migrate to the 'test' account. I want to migrate this data in conjunction with the 'test' account.
you could use permissions to limit access
and for linking user to your data, add perform_create function (i assume name field is FK to user)
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import CreateAPIView
class arduinoToDatabaseViewSet(CreateAPIView):
permission_classes = [IsAuthenticated] # only logged in users has access to this view
def perform_create(self, serializer):
serializer.save(name=self.request.user)

Django 1.11.2 serializer nested json array

I am new to both Python and Django and I would appreciate some guidance with a problem I'm having with Django REST, nested JSON and the serializer.
I wish to post:
{ "Server": [
{
"serverSerialNumber": "0000",
"serverUniqueKey": "2222"
},
{
"serverSerialNumber": "0001",
"serverUniqueKey": "2223"
}
]
}
This is my serializer:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from .models import Api
class ApiSerializer(serializers.ModelSerializer):
"""Serializer to map the Model instance into JSON format."""
class Meta:
"""Meta class to map serializer's fields with the model fields."""
model = Api
fields = ('id', 'serverSerialNumber', 'serverUniqueKey', 'date_created', 'date_modified')
read_only_fields = ('date_created', 'date_modified')
depth = 1
I simply receive the following back:
{
"serverSerialNumber": [
"This field is required."
]
}
So I am not understanding how to use 'depth' or I'm doing something silly.
Adding View per request:
from django.shortcuts import render
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from rest_framework import generics
from .serializers import ApiSerializer
from .models import Api
class CreateView(generics.ListCreateAPIView):
"""This class defines the create behavior of our rest api."""
queryset = Api.objects.all()
serializer_class = ApiSerializer
def perform_create(self, serializer):
"""Save the post data when creating a new item."""
serializer.save()
Ok, so I've stumbled through some documentation and had another bash at this.
Still not working but the code seems to make more sense, here is the new code and error:
serializers.py
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from .models import Blade, Servers
class BladeSerializer(serializers.ModelSerializer):
class Meta:
model = Blade
fields = ('id', 'serverSerialNumber', 'serverUniqueKey', 'date_created', 'date_modified')
read_only_fields = ('date_created', 'date_modified')
class ServersSerializer(serializers.ModelSerializer):
Server = BladeSerializer(many=True)
class Meta:
model = Servers
fields = ['Server']
def create(self, validated_data):
servers_data = validated_data.pop('Server')
srv = Servers.objects.create(**validated_data)
for server_data in servers_data:
Blade.objects.create(srv=srv, **server_data)
return srv
views.py
from django.shortcuts import render
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from api.serializers import UserSerializer, GroupSerializer
from rest_framework import generics
from .serializers import BladeSerializer, ServersSerializer
from .models import Blade, Servers
class CreateView(generics.ListCreateAPIView):
queryset = Servers.objects.all()
serializer_class = ServersSerializer
def perform_create(self, serializer):
serializer.save()
models.py
from django.db import models
from inventory.models import Server
class Blade(models.Model):
instanceId = models.CharField(max_length=255, blank=True, unique=False)
chassisUniqueKey = models.CharField(max_length=255, blank=True, unique=False)
serverUniqueKey = models.CharField(max_length=255, blank=True, unique=False)
serverSerialNumber = models.CharField(max_length=255, blank=False, unique=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return "{}".format(self.name)
class Servers(models.Model):
Servers = models.CharField(max_length=10, blank=True, unique=False)
def __str__(self):
"""Return a human readable representation of the model instance."""
return "{}".format(self.name)
The error
Got AttributeError when attempting to get a value for field Server on serializer ServersSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Servers instance.
Original exception text was: 'Servers' object has no attribute 'Server'.
Try this,
class CreateView(generics.ListCreateAPIView):
queryset = Api.objects.all()
serializer_class = ApiSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()