List Serializer multiple object creation - django

My models are:
class User(models.Model):
id = models.AutoField(primary_key=True)
email = models.EmailField()
class Lawyer(models.Model):
user = models.OnetoOneField(User)
class Session(models.Model):
name = models.CharField()
lawyer = models.ForeignKey(Lawyer)
I am trying to create multiple objects with a list serializer for Session.
From the app side they don't have the id of lawyer, but have the email of each lawyer. How can I write a list serializer where I can take the following json input and use email to fetch lawyer to store multiple session objects?
The json input sent will be like:
[
{
"name": "sess1",
"email": "lawyer1#gmail.com",
},
{
"name": "sess1",
"email": "lawyer1#gmail.com",
}
]

You can do it in this way but I think email should be unique=True.
Then use a serializer like this:
from django.utils.translation import ugettext_lazy as _
class SessionCreateManySerializer(serializers.ModelSerializer):
email = serializers.SlugRelatedField(
source='lawyer',
slug_field='user__email',
queryset=Lawyer.objects.all(),
write_only=True,
error_messages={"does_not_exist": _('lawyer with email={value} does not exist.')}
)
class Meta:
model = Session
fields = ('name', 'email')
and a generic create view (just override get_serializer and place many=True in kwargs ):
from rest_framework.response import Response
from rest_framework import generics
class SessionCreateManyView(generics.CreateAPIView):
serializer_class = SessionCreateManySerializer
def get_serializer(self, *args, **kwargs):
kwargs['many'] = True
return super(SessionCreateManyView, self).get_serializer(*args, **kwargs)

You can use bulk creation as well:
# serializers.py
from myapp.models import Session
from rest_framework import serializers
class SessionSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
name = serializers.CharField(required=True)
def validate_email(self, email):
lawyer = Lawyer.objects.filter(user__email=email).first()
if not lawyer:
raise ValidationError(detail="user dose not exist.")
return lawyer
def create(self, validated_data):
return Session.objects.create(name=validated_data.get('name'), lawyer=validated_data.get('email'))
and in your api.py file allow bulk creation:
# api.py
from rest_framework import generics
class SessionCreateAPIView(generics.CreateAPIView):
"""Allows bulk creation of a resource."""
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)

Related

Suggestions for returning relational data in Django REST Framework

I'm new to Django/Django REST FW (and new to this community). I've spent a lot of time reading the documentation but I'm spinning my wheels at this point. I apologize in advance for being so long-winded here.
My back end DB is Postgres. I've got 3 Models, User, Item and ShoppingList. I need Item to contain a description (the item_name field) and its location. The user will select an item and add it to the ShoppingList for that day. The idea is that the user will then "check off" the item once acquired and it'll be "removed" from the view of the ShoppingList.
Here is where I'm having trouble: I don't want to duplicate the item_name and item_location fields in the shopping_list table, but I need to display those fields in the view of the shopping list (shopping_lists.py).
There is a one-to-many relationship between Item and ShoppingList respectively. The Item object is considered a "master items table" that stores descriptions and locations for each item. The ShoppingList object holds a temporary list of these "master items". I need a queryset that contains all fields from ShoppingList and 2 or more fields from Item.
I think this would be what Django REST FW considers a Reverse Relationship. I've tried a variety of changes to my Serialzer(s) and Models (including adding the Item Serializer to the ShoppingList Serializer) and gotten a variety of errors.
models/item.py:
from django.db import models
from django.contrib.auth import get_user_model
class Item(models.Model):
item_name = models.CharField(max_length=50, db_index=True)
item_location = models.CharField(max_length=10, blank=True, db_index=True)
item_class = models.CharField(max_length=20, blank=True)
# This is a relationship with User model
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE
)
def __str__(self):
return f"item_name: {self.item_name}, item_location: {self.item_location}, shopper_id: {self.shopper_id}"
models/shopping_list.py:
from django.db import models
from django.contrib.auth import get_user_model
from .item import Item
class ShoppingList(models.Model):
item_num = models.ForeignKey(
'Item',
on_delete=models.DO_NOTHING # we don't want to delete the item from the "master" item list, just from this shopping list
)
# This is a relationship with user model.
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE # ...but we do want to delete the item if the user goes away as items are user-specific
)
item_qty = models.PositiveIntegerField()
item_complete = models.BooleanField(default=False)
added_on = models.DateField(auto_now=True)
# setting list_num to blank=True for this version
list_num = models.PositiveIntegerField(blank=True)
def __str__(self):
return f"item_num: {self.item_num}, shopper_id: {self.shopper_id}, item_qty: {self.item_qty}, item_complete: {self.item_complete}"
serializers/item.py:
from rest_framework import serializers
from ..models.item import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'item_name', 'item_location', 'item_class', 'shopper_id')
serializers/shopping_list.py:
from rest_framework import serializers
from ..models.shopping_list import ShoppingList
class ShoppingListSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingList
fields = ('id', 'item_num', 'shopper_id', 'item_qty', 'item_complete', 'added_on', 'list_num')
Getting ERROR AttributeError: Manager isn't accessible via ShoppingList instances when I execute the GET method in class ShoppingListItemView in views/shopping_lists.py below:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from rest_framework.exceptions import PermissionDenied
from ..models.shopping_list import ShoppingList
from ..serializers.shopping_list import ShoppingListSerializer
from ..models.item import Item
from ..serializers.item import ItemSerializer
class ShoppingListsView(APIView):
def get(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
data = ShoppingListSerializer(shopping_list_items, many=True).data
return Response(data)
def post(self, request):
request.data['shopper_id'] = request.user.id
list_item = ShoppingListSerializer(data=request.data, partial=True)
if list_item.is_valid():
list_item.save()
return Response(list_item.data, status=status.HTTP_201_CREATED)
else:
return Response(list_item.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
response_data = shopping_list_items.delete()
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class ShoppingListsAllView(APIView):
def get(self, request):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
data = ShoppingListSerializer(shopping_items, many=True).data
return Response(data)
class ShoppingListItemView(APIView):
def get(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_entry = list_item.objects.select_related('Item').get(id=pk)
print(list_entry)
data = ShoppingListSerializer(list_item).data
return Response(data)
def delete(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def patch(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
request.data['shopper_id'] = request.user.id
updated_list_item = ShoppingListSerializer(list_item, data=request.data, partial=True)
if updated_list_item.is_valid():
updated_list_item.save()
return Response(updated_list_item.data)
else:
return Response(updated_item.errors, status=status.HTTP_400_BAD_REQUEST)
if you want to display only a few properties of an item in your ShoppingList you can use the SerializerMethodField method in your serializer
this would work as -
class ShoppingListSerializer(serializers.ModelSerializer):
itemProperty1 = serializers.SerializerMethodField()
itemProperty2 = serializers.SerializerMethodField()
class Meta:
model = ShoppingList
fields = ('id', "itemProperty1", "itemProperty2", 'more_fields')
def get_itemProperty1(self, instance):
return instance.item.anyPropertyOfItem if instance.item else ''
def get_itemProperty2(self, instance):
return instance.item.anyPropertyOfItem if instance.item.else ''
anyPropertyOfItem can be anything from item models.
Setting your serializer this way, your ShoppingList view will automatically show 2 new fields.
or you can also define read only fields with the help of #property in models to get the required field.
If you want to display all the properties of the item in the ShoppingList view, you can write here, will edit my answer. There you need to use related_name and get the item serializer in Shoppinglist serializer as the extra field.
For reverse relationship you should use related_name when defining the model or using the suffix _set.
The related_name attribute specifies the name of the reverse relation
from the User model back to your model. If you don't specify a
related_name, Django automatically creates one using the name of your
model with the suffix _set
Copied from What is related_name used for? by Wogan

Django Serializer Passing a arguments into model function

New to Django and DRF, I have a method in the model properties which accept arguments. I have managed to call it successful though a serializer class with default paramenters and getting a JSON response. My problem is I can't pass argument to that function named balance. I have successful pass my argument from view to serializer class but from serializer to model that where I have failed. I thought will be appreciated.
model.py
class Item(models.Model):
entered_by = models.ForeignKey(User, on_delete=models.PROTECT)
name = models.CharField(max_length=50, blank=True)
#property
def balance(self, stock_type='Retail'):
stock = Stock.objects.filter(item=self, type=stock_type, status='InStock').aggregate(models.Sum('quantity')).get('quantity__sum')
return stock or 0
views.py
def getItemInfo(request):
if request.is_ajax and request.method == "GET":
id = request.GET.get("item_id", None)
sell_type = request.GET.get("sell_type", None)
item = Item.objects.get(id=id)
if item:
serializer = ItemSerializer(item, context={'sell_type':sell_type})
return JsonResponse(serializer.data, status = 200, safe=False)
else:
return JsonResponse({"data":False}, status = 400)
serializer.py
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
balance = serializers.SerializerMethodField()
class Meta:
model = Item
fields = ('entered_by', 'name', 'balance', 'sell_mode')
def get_balance(self, object):
sell_type = self.context.get("sell_type")
if sell_type:
return object.balance(sell_type)
return object.balance
The error I'm getting
'int' object is not callable
#property couldn't be called. So I made member variable and setter (with calculation) methods in Item model, then make sure setter method will be called in get_balance serializer method, just before returning balance.
Django ORM model itself is just a class; you can do anything class could, not just only linking with ORM.
My Code:
models.py
from django.db import models
from django.contrib.auth.models import User
class Item(models.Model):
entered_by = models.ForeignKey(User, on_delete=models.PROTECT)
name = models.CharField(max_length=50, blank=True)
_balance = 0
def calculate_balance(self, stock_type='Retail'):
stock = Stock.objects.filter(item=self, type=stock_type, status='InStock').aggregate(
models.Sum('quantity')).get('quantity__sum')
self._balance = stock or 0
#property
def balance(self):
return self._balance
serializer.py
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
balance = serializers.SerializerMethodField()
class Meta:
model = Item
fields = ('entered_by', 'name', 'balance')
def get_balance(self, object):
sell_type = self.context.get("sell_type")
if sell_type:
object.calculate_balance(sell_type)
return object.balance
views.py
from .models import Item
from .serializer import ItemSerializer
from django.http.response import JsonResponse
def getItemInfo(request):
if request.is_ajax and request.method == "GET":
id = request.GET.get("item_id", None)
if id is None:
return JsonResponse({"data": False}, status=400)
sell_type = request.GET.get("sell_type", None)
try:
item = Item.objects.get(id=id)
serializer = ItemSerializer(item, context={'sell_type': sell_type})
return JsonResponse(serializer.data, status=200, safe=False)
except Item.DoesNotExist:
return JsonResponse({"data": False}, status=400)

DRF ViewSet Returns QuerySet With Empty Values

I have a DRF ViewSet called "QueryCriteriaViewSet" which I'm using in a query builder which allows users to select a field and then select from related criteria. So, for example, a user can select the "reg_status" field and then select from the related criteria of "Active" and "Inactive".
This works totally fine when I select a field from the main "person" model. But I'm running into problems when I select a field from a related model like the "lookup_party" model. The weird thing though, is that when I print the queryset to the console it works perfectly, but when I get call the API it returns a list of empty objects.
As a further example, here's what happens when I make the calls:
api/querycriteria/?fields=reg_status returns:
[
{"reg_status": "Active"},
{"reg_status": "Inactive"}
]
while api/querycriteria/?fields=party__party_name returns:
[
{},
{},
{},
{},
{}
]
even though when I print(queryset) prior to returning the queryset, the following is printed:
<QuerySet [{'party__party_name': None}, {'party__party_name': 'Democratic'},
{'party__party_name': 'Non-Partisan'}, {'party__party_name': 'Registered
Independent'}, {'party__party_name': 'Republican'}]>
Here's the full ViewSet:
class QueryCriteriaViewSet(DefaultsMixin, viewsets.ModelViewSet):
serializer_class = QueryCriteriaSerializer
def get_queryset(self):
fields = self.request.GET.get('fields', None)
queryset = Person.objects.values(fields).distinct()
print(queryset)
return queryset
def get_fields_to_display(self):
fields = self.request.GET.get('fields', None)
return fields.split(',') if fields else None
def get_serializer(self, instance=None, data=empty, many=False,
partial=False):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return serializer_class(instance, data=data,
many=many, partial=partial,
context=context, fields=fields)
Let me know if any additional information would be helpful.
Here's my serializer:
from django.contrib.auth import get_user_model
from rest_framework import serializers
from rest_framework.reverse import reverse
from ..models.people import Person
from ..models.people_emails import Email
from ..models.people_addresses import Address
from ..models.people_phones import Phone
from ..models.people_tags import PersonTag
from ..models.people_elections import PersonElection
from ..models.people_districts import PersonDistrict
from ..models.people_attributes import Attribute
from .serializer_dynamic_fields import DynamicFieldsModelSerializer
from .serializer_tag import TagSerializer
from .serializer_email import EmailSerializer
from .serializer_address import AddressSerializer
from .serializer_phone import PhoneSerializer
from .serializer_election import ElectionSerializer
from .serializer_attribute import AttributeSerializer
from .serializer_district import DistrictSerializer
class QueryCriteriaSerializer(DynamicFieldsModelSerializer):
emails = EmailSerializer(many=True, required=False)
addresses = AddressSerializer(many=True, required=False)
phones = PhoneSerializer(many=True, required=False)
tags = TagSerializer(many=True, required=False)
elections = ElectionSerializer(many=True, required=False)
attributes = AttributeSerializer(many=True, required=False)
districts = DistrictSerializer(many=True, required=False)
class Meta:
model = Person
fields = ('id', 'elected_official', 'title', 'first', 'last', 'middle', 'suffix',
'full_name', 'birthdate', 'sex', 'website', 'deceased', 'registered', 'party',
'reg_date', 'reg_status', 'reg_state', 'county', 'match_id',
'date_added', 'date_updated', 'do_not_call', 'do_not_mail', 'do_not_email', 'do_not_text', 'emails',
'addresses', 'phones', 'tags', 'attributes', 'elections', 'districts')
And here's the DynamicFieldsModelSerializer:
from django.contrib.auth import get_user_model
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
ok now found the problem, DynamicFieldsModelSerializer is only working if the fields you are passing to it is a subset of the serializer original fields.
you should use a serializers in a way that accepts extra fields, something like this:
class ExtraDynamicFieldsModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
extra_fields = kwargs.pop('fields', [])
self.extra_fields = set()
# Instantiate the superclass normally
super(ExtraDynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
allowed = set(extra_fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
for field_name in allowed - existing:
self.extra_fields.add(field_name)
def to_representation(self, obj):
data = super().to_representation(obj)
for field in self.extra_fields:
data[field] = obj[field]
return data

Django Rest Framework - "Got a `TypeError` when calling `Note.objects.create()'"

When i am trying to POST using the browsable API of DRF, i get the following error:
Got a TypeError when calling Note.objects.create(). This may be
because you have a writable field on the serializer class that is not
a valid argument to Note.objects.create(). You may need to make the
field read-only, or override the NoteSerializer.create() method to
handle this correctly.
I don't know what is generating this error or how to overcome it. Literally spent hours google searching or changing the code. Can someone explain how to overcome this error? (Please note i am new to Django and DRF!)
Here is the models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
import uuid
class Stock(models.Model):
'''
Model representing the stock info.
'''
user = models.ForeignKey(User)
book_code = models.CharField(max_length=14, null=True, blank=True)
def __str__(self):
return self.book_code
class Note(models.Model):
'''
Model representing the stock note.
'''
user = models.ForeignKey(User)
note = models.TextField(max_length=560)
stock = models.ForeignKey(Stock, related_name='notes')
date_note_created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.note
This is the views.py:
from rest_framework import generics
from stocknoter.models import Stock, Note
from api.serializers import StockSerializer, NoteSerializer
# Create your views here.
class StockList(generics.ListCreateAPIView):
serializer_class = StockSerializer
def get_queryset(self):
user = self.request.user
return Stock.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
class NoteList(generics.ListCreateAPIView):
serializer_class = NoteSerializer
def get_queryset(self):
user = self.request.user
return Note.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(data=self.request.data)
def perform_update(self, serializer):
serializer.save(data=self.request.data)
This is the serializers.py:
from stocknoter.models import Stock, Note
from rest_framework import serializers
class StockSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
notes = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Stock
fields = ('id', 'user', 'book_code', 'notes')
class NoteSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Note
fields = ('user', 'note', 'stock')
I think this is because you are providing keyword argument data to save method. This is unnecessary. save() dont have such argument. Try just this:
def perform_create(self, serializer):
serializer.save()
take a print() of serializer_data in the view.py
to see what you actually sending to model and create.
then in your serializer.py override the create method..
for example:
def post(self, request):
print(serializer_data.validated_data)
def create(self, validated_data):
del validated_data['c']
return X.objects.create(a=validated_data['a'],
b=validated_data['b'],
)

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()