Django REST Framework serializer PrimaryKeyRelatedField() not adding object in GET response - django

I'm using Django 2.x and `Django REST Framework.
I have models.py with content as
class ModeOfPayment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField()
class AmountGiven(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField()
mode_of_payment = models.ForeignKey(
ModeOfPayment,
on_delete=models.PROTECT,
blank=True,
default=None,
null=True
)
and serializers.py
class AmountGivenSerializer(serializers.ModelSerializer):
mode_of_payment = serializers.PrimaryKeyRelatedField(queryset=ModeOfPayment.objects.all())
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'mode_of_payment',
)
def update(self, instance, validated_data):
mode_of_payment = validated_data.pop('mode_of_payment')
instance.mode_of_payment_id = mode_of_payment.id
return instance
This works fine as I'm able to update mode_of_payment field. But in response when calling amount_given doesn't contain parameters of mode_of_payment object.
the response is like
{
"id": "326218dc-66ab-4c01-95dc-ce85f226012d",
"contact": {
"id": "b1b87766-86c5-4029-aa7f-887f436d6a6e",
"first_name": "Prince",
"last_name": "Raj",
"user": 3
},
"amount": 3000,
"mode_of_payment": "0cd51796-a423-4b75-a0b5-80c03f7b1e65",
}
while removing the line
mode_of_payment = serializers.PrimaryKeyRelatedField(queryset=ModeOfPayment.objects.all())
does add the mode_of_payment parameters with response but this time this does not update the mode_of_payment field on amount_given.
why mode_of_payment data is not contained even when depth is set to 1.

You can create ModeOfPaymentSerializer and use it in AmountGivenSerializer's to_representation() method:
class ModeOfPaymentSerializer(serializers.ModelSerializer):
class Meta:
model = ModeOfPayment
fields = (
'id', 'title',
)
class AmountGivenSerializer(serializers.ModelSerializer):
mode_of_payment = serializers.PrimaryKeyRelatedField(queryset=ModeOfPayment.objects.all())
class Meta:
model = AmountGiven
fields = (
'id', 'contact', 'amount', 'mode_of_payment',
)
def update(self, instance, validated_data):
mode_of_payment = validated_data.pop('mode_of_payment')
instance.mode_of_payment_id = mode_of_payment.id
return instance
def to_representation(self, value):
data = super().to_representation(value)
mode_serializer = ModeOfPaymentSerializer(value.mode_of_payment)
data['mode_of_payment'] = mode_serializer.data
return data

Related

how to create two table objects by one viewset django rest framework

Hi i am trying to save objects in reviews table from posts table filtered by type="R"
i think i should override def create but i don't know how. please help me thanks.
my posts table
my reviews table
this is my post model
class Post(models.Model):
"""Model definition for Post."""
class Meta:
db_table = 'posts'
verbose_name = 'Post'
verbose_name_plural = 'Posts'
POST_TYPE = (
('R', 'review'),
('C', 'clubpost'),
('A', 'advertisement'),
)
title = models.CharField(
max_length=20,
null=False,
verbose_name='제목'
)
content = models.TextField(
max_length=2000,
null=False,
verbose_name='내용'
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
null=False,
verbose_name='글쓴이',
)
type = models.CharField(
max_length=5,
choices=POST_TYPE,
null=True,
verbose_name='글 종류',
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name='생성 일시',
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name='수정 일시',
)
review model
class Review(models.Model):
class Meta:
db_table = 'reviews'
verbose_name = 'Review'
verbose_name_plural = 'Reviews'
post = models.OneToOneField(
Post,
on_delete=models.CASCADE,
related_name="review",
)
post serializer
class PostSerializer(ModelSerializer):
author = UserAbstractSerializer(read_only=True)
class Meta:
model = Post
fields = [
'id',
'title',
'content',
'author',
'type',
'created_at',
'updated_at'
]
extra_kwargs = {
'title': {
'error_messages': {
'required': '제목을 입력해주세요.',
}
},
'content': {
'error_messages': {
'required': '내용을 입력해주세요.',
}
}
}
review serializer
class ReviewSerializer(ModelSerializer):
post_id = PostSerializer(read_only=True)
class Meta:
model = Post
fields = [
'id',
'post_id',
]
review viewset
class ReviewViewSet(PostViewSet):
queryset = Post.objects.filter(type="R"),
serializer_class = ReviewSerializer
def perform_create(self, serializer):
return serializer.save(author=self.request.user, type="R")
i think i should override create method in review viewset but i dont know how to write
wanna figure out how to create two table objects by one viewset django rest framework

Django Serializer not showing related fields

I can't get the State field to appear in the result. Don't know why.
My model:
class City(models.Model):
city_id = models.AutoField(primary_key=True)
city = models.CharField(max_length=100, blank=True, null=True)
state = models.ForeignKey('State', models.DO_NOTHING, blank=True, null=True)
class Meta:
managed = False
db_table = 'city'
def __str__(self):
return self.city
class State(models.Model):
state_id = models.AutoField(primary_key=True)
state = models.CharField(max_length = 10, blank=True, null=True)
class Meta:
managed = False
db_table = 'state'
My serializer:
class StateSerializer(serializers.ModelSerializer):
class Meta:
model = State
fields = ('state_id', 'state')
class CitySerializer(serializers.ModelSerializer):
state = StateSerializer(source='state_set', many=False, read_only = True)
class Meta:
model = City
fields = ('city_id', 'city', 'state')
My Views:
class CityList(APIView):
# Return all the cities
def get(self, request):
cities = City.objects.all()
serializer = CitySerializer(cities, many=True)
return Response(serializer.data)
def post(self):
pass
My result JSON:
[
{
"city_id": 242,
"city": null
},
{
"city_id": 754,
"city": "CARY"
},
{
"city_id": 2085,
"city": "FROM YOUR"
},...
How can I get the state field to appear in the JSON result? Can someone help? I got several tables like this.
You have bad relation in CitySerializer.
City dont have state_set relation... it have only one state.
CitySerializer should look like this:
class CitySerializer(serializers.ModelSerializer):
# Removed source.. by default it's like the field name
# I removed also many=False because it's default
state = StateSerializer(read_only=True)
class Meta:
model = City
fields = ('city_id', 'city', 'state')

create multiple related object with Django REST Framework

I'm using Djago 2.0 and Django REST Framework
I have following model classes for contacts/models.py
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
class ContactPhoneNumber(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
phone = models.CharField(max_length=100)
primary = models.BooleanField(default=False)
contacts/views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
and contacts/serializers.py is defined as
class ContactPhoneNumberSerializer(serializers.ModelSerializer):
class Meta:
model = ContactPhoneNumber
fields = ('id', 'phone', 'primary', 'created', 'modified')
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
user = serializers.CurrentUserDefault()
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'date_of_birth', 'phone_numbers')
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
user = validated_data.pop('user_id')
instance = Contact.objects.create(
user=user,
**validated_data
)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
I want to be able to create multiple phone_number instances with new contact object.
How can I pass multiple phone_numbers with fields phone, primary along with contact data?
Your JSON Post body needs to look like this:
{
"user": 1,
"first_name": "Anuj",
"last_name": "TBE",
"date_of_birth": "1990-01-01",
"contactphonenumber_set": [
{
"phone": "+91999999999",
"primary": false
},
{
"phone": "+91999999993",
"primary": true
}
]
}
Do think about how you want to deal with duplicates in this context.

create associted data with ModelSerializer in Django REST Framework

I'm using Django 2.0 and Django REST Framework to write REST API.
My contacts/models.py contains
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
avatar = models.ImageField(upload_to='contact/%Y/%m/%d', blank=True)
class Meta:
db_table = 'contacts'
class ContactPhoneNumber(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
phone = models.CharField(max_length=100)
class Meta:
db_table = 'contact_phone_numbers'
and contacts/serializers.py
class ContactPhoneNumberSerializer(serializers.ModelSerializer):
class Meta:
model = ContactPhoneNumber
fields = ('id', 'phone', 'primary', 'created', 'modified')
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail',
read_only=True
)
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'date_of_birth', 'avatar', 'phone_numbers')
def create(self, validated_data):
print(validated_data)
instance = Contact.objects.create(**validated_data)
instance.save()
return instance
I want to be able to create contact along with phone_number and one contact can have many phone_numbers.
But when I send POST request with only contact data, it gives error as
'contactphonenumber_set' is an invalid keyword argument for this function
on calling contacts only is showing all associted mobile numbers in json response but unable to create record.
print(validated_data) gives following data
{'first_name': 'Anshuman', 'last_name': 'Upadhyay', 'date_of_birth': datetime.date(2018, 5, 15), 'contactphonenumber_set': [], 'user_id': <SimpleLazyObject: <User: anuj>>}
How can I create related multiple fields with REST Framework?
You cannot pass contactphonenumber_set to objects.create() method directly. You should create each related phonenumber separately, like this:
def create(self, validated_data):
print(validated_data)
phone_numbers = validated_data.pop('contactphonenumber_set')
instance = Contact.objects.create(**validated_data)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
See details about writable nested serializers here.
Give you a demo with drf-writable-nested
models.py:
class UnitGroup(models.Model):
name = models.CharField(max_length=255,
verbose_name='名称')
class Unit(models.Model):
unit_group = models.ForeignKey('medicine.UnitGroup',
related_name='unit_unit_group',
null=True,
on_delete=models.SET_NULL,
verbose_name='unit_group')
name = models.CharField(max_length=255,
verbose_name='name')
display_name = models.CharField(max_length=255,
verbose_name='display_name')
serializers.py:
class UnitCreateSerializer(ModelSerializer):
class Meta:
model = Unit
fields = ('name', 'display_name', 'ratio', 'is_active')
class UnitGroupCreateSerializer(WritableNestedModelSerializer):
unit_unit_group = UnitCreateSerializer(many=True)
class Meta:
model = UnitGroup
fields = ('unit_unit_group', 'name')
tests.py:
class UnitGroupTests(APITestCase):
def setUp(self):
try:
self.user = User.objects.get(tel='18094213198')
except User.DoesNotExist:
self.user = User.objects.create_user(tel='18094213198', password='123456')
self.user.user_permissions.add(*get_model_permission(UnitGroup))
token, _ = Token.objects.get_or_create(user=self.user)
self.access_token = token.access_token
def test_create(self):
"""create"""
url = reverse('unitgroup-list')
self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.access_token)
data = {'name': 'Widget', 'unit_unit_group': [{'name': '1', 'display_name': 'yi'}]}
response = self.client.post(url, data, format='json')
print([(x, x.unit_group) for x in Unit.objects.all()])
print('create', json.dumps(response.data, ensure_ascii=False, indent=2))
self.assertEqual(response.status_code, status.HTTP_200_OK)
test output:
[(<Unit: 1>, <UnitGroup: Widget>)]
create{
"id": 1,
"name": "Widget",
"create_time": "2018-05-08 16:59:51",
"update_time": "2018-05-08 16:59:51"
}

How to GET from a model, showing its related models' details in the JSON response?

I am writing a small system of which a Transaction has an Account and Category related to it. Currently I'm using PrimaryKeyRelatedField in the TransactionSerializer. I need to, when GETing all the transactions or just one, to return the related Account and Category details in the JSON response. By using PrimaryKeyRelatedField, the response is alike this:
class TransactionSerializer(serializers.ModelSerializer):
account = serializers.PrimaryKeyRelatedField(read_only=True)
category = serializers.PrimaryKeyRelatedField(read_only=True)
# output:
[
transaction: {
id: 1,
account: 1
category: 3,
...
},
...
]
To bring the details the related account and category, I've done the following in the TransactionSerializer:
class TransactionSerializer(serializers.ModelSerializer):
account = AccountSerializer()
category = CategorySerializer()
# output:
[
transaction: {
id: 1,
account: { id: 1, name: "Foo", ... }
category: { id: 3, name: "Bar", ... },
...
},
...
]
But then I cannot create a new Transaction. It shows an error saying account and category are required. I've tried moving the fields to read_only within the serializer, but then another error says these fields should be within fields list.
Full views.py of transactions:
class TransactionList(APIView):
def get(self, request):
user_id = request.user.pk
transactions = Transaction.objects.filter(user_id=user_id).order_by('-created_at')
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
def post(self, request):
account = Account.objects.get(pk=request.data['account'])
category = Category.objects.get(pk=request.data['category'])
serializer = TransactionSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, account=account, category=category)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
class TransactionDetail(APIView):
def get(self, request, pk):
try:
transaction = Transaction.objects.get(pk=pk)
serializer = TransactionSerializer(transaction)
return Response(serializer.data)
except:
raise Http404
urls.py:
urlpatterns = [
url(r'^$', views.TransactionList.as_view(), name="transaction_list"),
url(r'^(?P<pk>[0-9]+)/$', views.TransactionDetail.as_view(), name="transaction"),
]
models.py:
class Transaction(models.Model):
user = models.ForeignKey(User, default=None)
account = models.ForeignKey(Account, default=None)
category = models.ForeignKey(Category, default=None)
name = models.CharField(max_length=32)
amount = models.DecimalField(max_digits=10, decimal_places=2, default="0.0")
description = models.CharField(max_length=256, default=None, null=True)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
So this is my problem. How do I create a new Transaction, assigning an account and category by ID, and yet when retrieving all, bring all the details?
Creating two serializers, one for listing and one for creating did the trick.
class ListTransactionSerializer(serializers.ModelSerializer):
account = AccountSerializer()
category = CategorySerializer()
class Meta:
model = Transaction
fields = '__all__'
class CreateTransactionSerializer(serializers.ModelSerializer):
account = serializers.PrimaryKeyRelatedField(read_only=True)
category = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Transaction
fields = '__all__'
def create(self, validated_data):
"""
Create and return a new 'Transaction' instance
:param validated_data:
:return: Transaction
"""
return Transaction.objects.create(**validated_data)
def update(self, instance, validated_data):
pass
First rewrite models to related name fileds for rest implementation.
class Transaction(models.Model):
user = models.ForeignKey(User, default=None, related_name="user_serialzer")
account = models.ForeignKey(Account, default=None, related_name="account_serialzer")
category = models.ForeignKey(Category, default=None, related_name="category_serialzer")
name = models.CharField(max_length=32)
amount = models.DecimalField(max_digits=10, decimal_places=2, default="0.0")
description = models.CharField(max_length=256, default=None, null=True)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
Then serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fileds = '__all__'
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fileds = '__all__'
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fileds = '__all__'
class SerializerTransactions(serializers.ModelSerializer):
user_serialzer = UserSerializer(many=True)
account_serialzer = AccountSerializer(many=True)
category_serialzer = CategorySerializer(many=True)
class Meta:
model = Transaction
fileds = ('id', 'name', 'user_serialzer', 'account_serialzer', 'category_serialzer',)