Django Serializer not showing related fields - django

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

Related

how to Combine UserProfile and the User model that are connected through OneToOne relation into a single endpoint?

I have a custom user class and a profile class. Profile class has a OneToOne relation with the custom User. the Serializer is having User as Meta model with adding Profile model in a new field profile extended to the fields tuple. but When I try to get the detail view it returns an error saying Profile field is not an attribute of CustomUser.
I would appreciate if you go over the code that I added below and help me through this.
The User model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
class Types(models.TextChoices):
DOCTOR = "DOCTOR", "Doctor"
PATIENT = "PATIENT", "Patient"
# what type of user
type = models.CharField(_("Type"), max_length=50, choices=Types.choices, null=True, blank=False)
avatar = models.ImageField(upload_to="avatars/", null=True, blank=True)
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomBaseUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'type'] #email is required by default
def get_full_name(self):
return self.name
def __str__(self):
return self.email
The Profile Model:
class DoctorProfile(models.Model):
"""Model for Doctors profile"""
class DoctorType(models.TextChoices):
"""Doctor will choose profession category from enum"""
PSYCHIATRIST = "PSYCHIATRIST", "Psychiatrist"
PSYCHOLOGIST = "PSYCHOLOGIST", "Psychologist"
DERMATOLOGIST = "DERMATOLOGIST", "Dermatologist"
SEXUAL_HEALTH = "SEXUAL HEALTH", "Sexual health"
GYNECOLOGIST = "GYNECOLOGIST", "Gynecologist"
INTERNAL_MEDICINE = "INTERNAL MEDICINE", "Internal medicine"
DEVELOPMENTAL_THERAPIST = "DEVELOPMENTAL THERAPIST", "Developmental therapist"
owner = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='doctor_profile')
doctor_type = models.CharField(
_("Profession Type"),
max_length=70,
choices=DoctorType.choices,
null=True,
blank=False)
title = models.IntegerField(_('Title'), default=1, choices=TITLES)
date_of_birth = models.DateField(null=True, blank=False)
gender = models.IntegerField(_('Gender'), default=1, choices=GENDERS)
registration_number = models.IntegerField(_('Registration Number'), null=True, blank=False)
city = models.CharField(_('City'), max_length=255, null=True, blank=True)
country = models.CharField(_('Country'), max_length=255, null=True, blank=True)
def __str__(self):
return f'profile-{self.id}-{self.title} {self.owner.get_full_name()}'
Serializer:
class DoctorProfileFields(serializers.ModelSerializer):
"""To get the fields from the DoctorProfile. it will be used in the DoctorProfileSerializer"""
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number', 'gender', 'city', 'country', )
class DoctorProfileSerializer(serializers.ModelSerializer):
"""retrieve, update and delete profile"""
profile = DoctorProfileFields()
class Meta:
model = User
fields = ('name', 'avatar', 'profile', )
#transaction.atomic
def update(self, instance, validated_data):
ModelClass = self.Meta.model
profile = validated_data.pop('profile', {})
ModelClass.objects.filter(id=instance.id).update(**validated_data)
if profile:
DoctorProfile.objects.filter(owner=instance).update(**profile)
new_instance = ModelClass.objects.get(id = instance.id)
return new_instance
View:
class DoctorProfileAPIView(generics.RetrieveUpdateDestroyAPIView):
"""To get the doctor profile fields and update and delete"""
serializer_class = DoctorProfileSerializer
queryset = User.objects.all()
def get_object(self):
return get_object_or_404(User, id=self.request.user.id, is_active=True)
What I want is a json response in the detail view like below:
{
"name": the name,
"avatar": avatar,
"profile": {
"doctor_type": "PSYCHIATRIST",
"title": 1,
"date_of_birth": 11-11-1990,
"registration_number": 21547,
}
}
Can Anybody guide me through this..? Or is there any other design approach that meets my objective. My objective is to have the user info + profile info combined in a single endpoint as a whole Profile in the frontend from which the user will see/edit profile.
First of all move the foreign key OneToOne in the CustomUser model, add:
owner = models.OneToOneField('DoctorProfile', on_delete=models.CASCADE, related_name='doctor_profile')
and delete from DoctorProfile:
owner = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='doctor_profile')
Make all migrations, and now you have to set new data in the db.
In the serializers you are using Nested relationships correctly, so add the attribute many set to False:
profile = DoctorProfileFields(many=False)
Edit
If you cant edit the structure of your models, you can work with SerializerMethodField (not tested):
class DoctorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number')
class CustomDoctorProfileSerializer(serializers.Serializer):
name = serializers.SerializerMethodField()
avatar = serializers.SerializerMethodField()
profile = DoctorProfileSerializer(many=False)
def get_name(self, obj)
return obj.doctor_profile.name
def get_avatar(self, obj)
return obj.doctor_profile.avatar

TypeError: create() got multiple values for keyword argument 'name'

I am currently having trouble executing nesting with Django rest framework, I think the problem is in my loop but cant't get that I have gone through most of the answers but no help. Any help would be appreciated. Thanks in advance!!
MODEL
class Category(models.Model):
name = models.CharField(max_length=36, unique=True)
class sub_category(models.Model):
parentCategory = models.ForeignKey(Category, blank=True, null=True,
related_name='subcategories',
on_delete=models.CASCADE)
name = models.CharField(max_length=36, unique=True)
class childern(models.Model):
parentCategory = models.ForeignKey(sub_category, blank=True, null=True,
related_name='subcategories',
on_delete=models.CASCADE)
name = models.CharField(max_length=36, unique=True)
Searlizers
class ChildernSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = childern
fields = ['name',
'id',]
class SubCategorySerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(read_only=True)
subcategories = ChildernSerializer(many=True)
class Meta:
model = sub_category
fields = ['name',
'subcategories',
'id',]
def create(self, validated_data):
sub_cat = validated_data.pop('subcategories')
name = Category.objects.create(**validated_data)
for cat in sub_cat:
sub_category.objects.create(**cat, name=name)
return name
class CategorySerializer(serializers.ModelSerializer):
subcategories = SubCategorySerializer(many=True)
class Meta:
model = Category
fields = [
'name',
'subcategories',
]
def create(self, validated_data):
sub_cat = validated_data.pop('subcategories')
name = Category.objects.create(**validated_data)
for cat in sub_cat:
sub_category.objects.create(**cat, name=name)
return name
You need to pop the name from the cat data, otherwise the name will both occur in the cat and through the name=name parameter:
def create(self, validated_data):
sub_cat = validated_data.pop('subcategories')
name = Category.objects.create(**validated_data)
for cat in sub_cat:
cat.pop('name', None)
sub_category.objects.create(**cat, name=name)
return name
Your error is because the property Name was passed twice in your call to create:
sub_category.objects.create(**cat, name=name)
name already exists within **cat and you also passed it with name=name
simply remove name it from SubCategorySerializer so it become:
class SubCategorySerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(read_only=True)
subcategories = ChildernSerializer(many=True)
class Meta:
model = sub_category
fields = ['subcategories',
'id',]

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

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

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',)