Save Instance Nested Serializers in Django Rest Framework - python

I have problem with saving instance Live_In Nested Serializers in Django Rest Framework. Hope your guys help me! I think just a basic issue.
My Serializers:
I think it comes error when I write saving instance
class CityLiveInSerializer(ModelSerializer):
country = CharField(required=False, source='country.name')
class Meta:
model = City
fields = [
'name',
'slug',
'country'
]
class UserEditSerializer(ModelSerializer):
live_in = CityLiveInSerializer(source='profile.live_in')
about = serializers.CharField(source='profile.about')
class Meta:
model = User
fields = [
'username',
'live_in',
'about',
]
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.save()
# Update Serializers Profile
if (validated_data.get('profile') is not None):
profile_data = validated_data.pop('profile')
profile = instance.profile
profile.about = profile_data.get('about', profile.about)
profile.save()
if (validated_data.get('live_in') is not None):
live_in_data = validated_data.pop('live_in')
try:
city = City.objects.get(name=live_in_data['name'])
except City.DoesNotExist:
city = City.objects.create(**live_in_data)
instance.profile.live_in = city
instance.profile.save()
return instance
My City Model (Live_in)
class City(BaseCity):
class Meta(BaseCity.Meta):
swappable = swapper.swappable_setting('cities', 'City')
class BaseCity(Place, SlugModel):
name = models.CharField(max_length=200, db_index=True, verbose_name="standard name")
country = models.ForeignKey(swapper.get_model_name('cities', 'Country'), related_name='cities', null=True, blank=True)
My Profile Model
class Profile(models.Model):
# Extend User
user = models.OneToOneField(User, unique=True)
about = models.TextField(max_length=1000, default='', blank=True, null=True)
live_in = models.ForeignKey(City, null=True, blank=True, related_name="live_in")
Data sent by Postman (Json)
{ "live_in": { "name": "Encamp" } }
TraceError:
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/rest_framework/serializers.py" in data
263. self._data = self.to_representation(self.instance)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/rest_framework/serializers.py" in to_representation
488. attribute = field.get_attribute(instance)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/rest_framework/fields.py" in get_attribute
463. raise type(exc)(msg)
Exception Type: AttributeError at /api/v1/users/account/edit/
Exception Value: Got AttributeError when attempting to get a value for field live_in on serializer UserEditSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the User instance.
Original exception text was: 'User' object has no attribute 'live_in'.

First of all you dont need many=True in this case. It required for related objects list, but you will pass only one city.
Secondly live_in is attribute of profile model so you need to update profile and add source argument:
live_in = CityLiveInSerializer(source="profile.live_in")
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.save()
# Update Serializers Profile
if (validated_data.get('profile') is not None):
profile_data = validated_data.pop('profile')
profile = instance.profile
profile.about = profile_data.get('about', profile.about)
if (profile_data.get('live_in') is not None):
live_in_data = profile_data.pop('live_in')
try:
city = City.objects.get(name=live_in_data["name"])
except City.DoesNotExist:
city = City.objects.create(**live_in_data)
profile.live_in = city
profile.save()
return instance
In this case you need to allow create city without country, so you need to add null=True, blank=True to country attribute:
class BaseCity(Place, SlugModel):
name = models.CharField(max_length=200, db_index=True, verbose_name="standard name")
country = models.ForeignKey(swapper.get_model_name('cities', 'Country'),
related_name='cities', null=True, blank=True)

Related

serialize self-referential foreign key

Lets say I want to create an article that links via slug to one or multiple other articles (for now one).
A post request comes in giving me some article-info and the related slug.
I want to
validate the data and the related slug
create an article in the db
return the article (with the slug)
models.py
class Article(models.Model):
heading = models.CharField(max_length=2550)
slug = models.SlugField(null=True, default=None, unique=True, max_length=255)
article_previous = models.ForeignKey('self', to_field='slug', blank=True, null=True,
related_name='subcategories', on_delete=models.DO_NOTHING)
serializers.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = (
"heading",
"slug",
"article_previous",
def create(self, validated_data):
try:
article_previous = Article.objects.get(slug=d['value'])
article_previous_slug = article_previous.slug
except Article.DoesNotExist:
raise serializers.ValidationError(f'No Article with the slug {d["value"]}')
article=Article.objects.create(**validated_data,article_previous_id=article_previous_slug)
return validated_data
This solution gives:
article_previous = serializers.CharField()
ValueError: Cannot assign "'test_1'": "Article.article_previous" must be a "Article" instance.
This solution gives:
article_previous = serializers.CharField('article.article_previous')
TypeError: Article() got an unexpected keyword argument 'article'
If I use serializer.validated_data in views.py:
return Response(serializer.validated_data, status=status.HTTP_201_CREATED, headers=headers)
response = serializer.save()
return Response(response, status=status.HTTP_201_CREATED, headers=headers)
TypeError: Object of type Article is not JSON serializable.
You can serialize the slug by working with the article_previous_id field:
class ArticleSerializer(serializers.ModelSerializer):
article_previous = serializers.CharField(source='article_previous_id')
class Meta:
model = Article
fields = ('heading', 'slug', 'article_previous')
# …
class ArticleSerializer(serializers.ModelSerializer):
article_previous=serializers.SlugRelatedField(queryset=Article.objects.all(), slug_field="slug")
class Meta:
model = Article
fields = ('heading', 'slug', 'article_previous')
def create(self, validated_data):
article_previous=validated_data.pop("article_previous")
article=Article.objects.create(**validated_data, article_previous=article_previous)
validated_data.update( {"article_previous" : article.article_previous.slug} )
return validated_data
I think this solution has the advantage that the naming stays article_previous and is not changed to article_previous_id.

Posting to multiply related tables Django

I would like to create my own endpoint for POST request to two related tables. I have two tables User and Userattribute.
models.py
class User(models.Model):
email = models.CharField(unique=True, max_length=180)
roles = models.JSONField(default=dict)
password = models.CharField(max_length=255, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
firebase_id = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField(default=now)
progress_sub_step = models.IntegerField(blank=True, null=True)
step_available_date = models.DateTimeField(blank=True, null=True)
progress_step = models.IntegerField(blank=True, null=True)
active = models.IntegerField(default=1)
last_login_at = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'user'
class Userattribute(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name = 'attribute')
attribute = models.ForeignKey(Attribute, on_delete=models.CASCADE)
The table Userattribute contains the field user which is OnetoOne to Id primary key from User table.
I tried to implement POST to two tables in serializers.py In the commented section there is a create definition which works perfectly for me. However, I wouldlike to move it to views.py as register_in_course endpoint
serializers.py
class FilmSerializer(serializers.ModelSerializer):
class Meta:
model = Film
fields = ['tytul', 'opis', 'po_premierze']
class UserattributeSerializer(serializers.ModelSerializer):
class Meta:
model = Userattribute
fields = ['user', 'attribute']
class UASerializer(serializers.ModelSerializer):
class Meta:
model = Userattribute
fields = ['attribute']
class UserSerializer(serializers.ModelSerializer):
attribute = UASerializer(many = False)
class Meta:
model = User
fields = ['email', 'name', 'firebase_id', 'attribute']
# This is what workks perfectly for me, and I want to move it to views.py
# VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
# def create(self, validated_data):
# attribute_data = validated_data.pop('attribute')
# user = User.objects.create(**validated_data)
# Userattribute.objects.create(user=user, **attribute_data)
# return user
Current views.py:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object()
user = User.objects.create(email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id'])
user_id = User.objects.filter(firebase_id = request.data['firebase_id'])['id']
attribute = Userattribute.objects.create(user = user_id, attribute = request.data['attribute']['attribute'])
user = user.attribute.add(attribute)
serializer = UserSerializer(user, many = false)
return Response(serializer.data)
Using endpoint register_in_course to POST I get following error:
Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly.
urls.py
from django.urls import include, path
from django.conf.urls import url
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'userattribute', views.UserattributeViewSet)
urlpatterns = [
url('', include(router.urls))
]
i removed one line user_id variable and changed attribute variable. please check, maybe it should solve your problem, because you have already have Assigned variable as a User object..
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object()
user = User.objects.create(email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id'])
attribute = Userattribute.objects.create(user = user, attribute = request.data['attribute']['attribute']) # changed this line
user = user.attribute.add(attribute)
serializer = UserSerializer(user, many = false)
return Response(serializer.data)
This issue is caused by calling get_object in a view that is defined with detail=False:
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object() # The problem is caused by this line
It seems you don't need this data, as you are using request.data.
So you can define your view like this:
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
user = User.objects.create(
email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id']
)
Userattribute.objects.create(
user=user,
attribute = request.data.get('attribute', {}).get('attribute', {})
)
return Response(UserSerializer(user).data)

Django - expected type pk, received str [Many to many]

I have a webapp where we can create communities with django as a backend, but when i try to send a POST to create a community, I get:
community_configuration: ["Incorrect type. Expected pk value, received str."]
My POST:
{
title: knkn kn .k jbjnmn,
logo: [object File],
is_active: true,
description: test,
welcome_message: Welcome message,
org_id: 114,
community_configuration: About us,Community news,FAQs,Supporters,Resources,
}
Here are my serializers:
class MicroConfigurationSerializer(serializers.ModelSerializer):
class Meta:
model = MicroConfiguration
fields = [
'name',
]
def to_representation(self, instance):
return instance.name
class CommunityConfigurationSerializer(serializers.ModelSerializer):
micro_configurations = MicroConfigurationSerializer(many=True, read_only=True)
class Meta:
model = CommunityConfiguration
fields = '__all__'
class CommunitySerializer(serializers.ModelSerializer):
logo = serializers.SerializerMethodField()
organisation = serializers.SerializerMethodField()
community_configuration = CommunityConfigurationSerializer()
class Meta:
model = Community
fields = (
...
etcetc
...
)
Heres my model:
class MicroConfiguration(core_models.BaseModel):
name = models.CharField(max_length=32, unique=True)
permissions = models.ManyToManyField(Permission)
def __str__(self):
return self.name
class CommunityConfiguration(core_models.BaseModel):
name = models.CharField(max_length=32)
micro_configurations = models.ManyToManyField(MicroConfiguration)
permission_group = models.ForeignKey(
Group,
on_delete=models.SET_NULL,
null=True
)
def __str__(self):
return self.name
class Community(core_models.BaseModel):
accounts = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through='associates.AccountCommunity',
through_fields=('community', 'account')
)
goals = models.ManyToManyField(Goal, through='associates.CommunityGoal')
type = models.ManyToManyField(CommunityType, through='CommunityAttribute')
communities = models.ManyToManyField(
'self',
through='associates.CommunityCommunity',
symmetrical=False,
)
crossposts = models.ManyToManyField(
Action,
through='activities.CommunityCrosspost',
through_fields=('community', 'action'),
related_name='communities',
)
title = models.CharField(max_length=64)
logo = models.ImageField(null=True, blank=True)
intro_media = models.ForeignKey(
'associates.MediaStore',
null=True,
on_delete=models.SET_NULL
)
website_url = models.CharField(max_length=256, blank=True)
is_invite_only = models.BooleanField(default=False)
description = models.TextField(blank=True)
intro_message = models.TextField(blank=True)
welcome_message = models.TextField(blank=True)
signup_autojoin = models.BooleanField(default=False)
community_configuration = models.ForeignKey(
CommunityConfiguration,
default=1,
null=True,
on_delete=models.SET_NULL,
help_text='Do not edit directly, create a new custom config instead, \
as it is reference by difference community.',
)
objects = models.Manager()
associates = AssociationManager()
#property
def url(self):
return reverse(
'communities:detail',
kwargs={
'id': self.id,
}
)
class Meta:
verbose_name = 'Community'
verbose_name_plural = 'Communities'
def __str__(self):
return self.title
Here's my views:
class CommunityCreateAPIView(CreateAPIView):
serializer_class = CommunityCreateSerializer
def create(self, request, **kwargs):
organisation_id = request.data.get('org_id')
micro_configurations_qd = request.data.copy()
micro_configurations = micro_configurations_qd.pop('community_configuration', False)
if micro_configurations:
micro_configurations = micro_configurations[0].split(',')
user = request.user
data = {}
permitted = AccountOrganisation.objects.filter(
account=user,
organisation_id=organisation_id,
is_active=True,
association__permissions__codename='add_community',
).exists()
if not permitted and not request.user.is_superuser:
data['message'] = 'Action not allowed'
return Response(
data=data,
status=status.HTTP_400_BAD_REQUEST,
)
response = super().create(request, **kwargs)
if response.status_code == 400:
data['message'] = 'Failed to update community'
return Response(
data=data,
status=status.HTTP_400_BAD_REQUEST,
)
community_id = response.data.get('id')
OrganisationCommunity.objects.create(
community_id=community_id,
organisation_id=organisation_id,
)
association = Group.objects.get(name='Community Admin')
AccountCommunity.objects.create(
community_id=community_id,
account=user,
association=association,
)
if micro_configurations:
com_config_qs = CommunityConfiguration.objects.filter(
micro_configurations__name__in=micro_configurations
).annotate(
num_micro_config=Count('micro_configurations__name')
).filter(
num_micro_config=len(micro_configurations)
)
community_configuration = None
if micro_configurations:
for com_config in com_config_qs:
micro_config_count = com_config.micro_configurations.count()
if micro_config_count == len(micro_configurations):
community_configuration = com_config
break
if community_configuration:
Community.objects.filter(
pk=community_id
).update(
community_configuration=community_configuration
)
elif micro_configurations:
micro_qs = MicroConfiguration.objects.filter(
name__in=micro_configurations
)
community_config = CommunityConfiguration.objects.create(
name='Custom'
)
community_config.micro_configurations.set(micro_qs)
community_config.save()
Community.objects.filter(
pk=community_id
).update(
community_configuration=community_config
)
return response
I've been stuck with this for hours, any help?
Your serializer expects a pk (or id) of the community_configuration instance.
basically you have it set up so that you need to create a community_configuration entry first, then fetch the id of the new created entry and use that id when creating your community.
If you want to have the community_configuration instance to be created while creating the community then an option to do that would be to override the create method in the serializer and extract the community_configuration data then create a new entry for that model and use it, something like this
class CommunitySerializer(serializers.ModelSerializer):
# .. put your serializer code here
def create(self, validated_data):
community_configuration= validated_data.pop('community_configuration')
config_instance, created = CommunityConfiguration.objects.get_or_create(<community_configuration>) # modify this however you need to create the CommunityConfiguration instance
community_instance = community.objects.create(**validated_data, community_configuration=config_instance)
return community_instance
you will need to probably modify the code for it to follow your needs.
I didn't read all of your models, not sure how you are gonna create the nested models from the input value but you get the point.

Serializer for creation of user profile in Django Rest Framework

I am trying to update the user profile in django rest framework
So i am able to create the user profile simultaneously with user creation using signals
Now i am trying to update the profile created:-
//models.py
class User(AbstractBaseUser,PermissionsMixin):
phone_regex=RegexValidator(regex = r'^[6-9]\d{9}$',message='please enter the correct phonenumber')
#name_regex=RegexValidator(regex=r'/^[A-Za-z]+$/',message='Please enter the correct name')
phone=models.CharField(validators=[phone_regex],max_length=15,unique=True)
date_joined=models.DateTimeField(verbose_name='date joined',auto_now_add=True)
last_login=models.DateTimeField(verbose_name='last login',auto_now=True)
active = models.BooleanField(default=True)
staff = models.BooleanField(default=False) # a admin user; non super-user
admin = models.BooleanField(default=False)
first_login=models.BooleanField(default=True)
USERNAME_FIELD='phone'
REQUIRED_FIELDS = []
class UserProfile(models.Model):
# There is an inherent relationship between the Profile and
# User models. By creating a one-to-one relationship between the two, we
# are formalizing this relationship. Every user will have one -- and only
# one -- related Profile model.
GENDER = (
('M', 'Homme'),
('F', 'Femme'),
)
user = models.OneToOneField(
User,related_name="profile", on_delete=models.CASCADE
)
# Each user profile will have a field where they can tell other users
# something about themselves. This field will be empty when the user
# creates their account, so we specify blank=True.
email=models.EmailField(unique=True,validators=[validate_email],max_length=254,blank=True,null=True)
name=models.CharField(max_length=15,blank=True)
dateofbirth=models.DateField(auto_now=False, null=True, blank=True)
Gender=models.CharField(max_length=1, choices=GENDER,blank=True,null=True)
address1 = models.CharField(
"Address",
max_length=1024,
blank=True,
null=True
)
address2 = models.CharField(
"Society",
max_length=1024,
blank=True,
null=True
)
address3 = models.CharField(
"Landmark",
max_length=1024,
blank=True,
null=True
)
zip_code = models.CharField(
"ZIP / Postal code",
max_length=12,
blank=True,
null=True
)
city = models.CharField(
"City",
max_length=1024,
default ="Gurugram",
blank=True,
null=True
)
country = models.CharField(
"Country",
max_length=10,
default="India",
blank=True,
null=True
)
# In addition to the `bio` field, each user may have a profile image or
# avatar. This field is not required and it may be blank.
# A timestamp representing when this object was created.
created_at = models.DateTimeField(auto_now_add=True,blank=True)
# A timestamp representing when this object was last updated.
updated_at = models.DateTimeField(auto_now=True,blank=True)
def __str__(self):
return self.user.phone
#receiver(post_save, sender=User)
def create_profile_for_user(sender, instance=None, created=False, **kargs):
if created:
UserProfile.objects.get_or_create(user=instance)
// Serializer.py
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields=("name",)
class UserSerializer(serializers.ModelSerializer):
user = UserProfileSerializer(many=True)
class Meta:
model = User
fields = ('user', 'phone',)
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profile = user_data.objects.create(user=user, **validated_data)
return profile
def update(self, instance, validated_data):
print("update function")
# profile_data = validated_data.pop('profile')
# print(profile_data)
profile_data = validated_data.pop('user')
profile = instance.profile
# print("profile is ",profile)
print(profile_data.name)
print("name in validation ",validated_data['name'], " type is ",print(type(validated_data)))
instance.username = validated_data.get('name', instance.username)
instance.email = validated_data.get('email', instance.email)
instance.save()
print("name is ",instance.profile.name )
#profile.Gender = validated_data['Gender'],
#profile.email = validated_data['email'],
#profile.dateofbirth=validated_data['dateofbirth'],
#profile.address1=validated_data['address1'],
##profile.address2=validated_data['address2'],
#profile.address3=validated_data['address3']
# print("1",profile)
#print(profile.Gender)
#print(profile.email)
#print(profile.dateofbirth)
profile.save()
print("2",profile)
print(profile.name)
print("save done")
return instance
//views.py
class ProfileCreateAPIView(CreateAPIView):
def put(self, request, *args, **kwargs):
print("my print 0",request)
header_token = request.META.get('HTTP_AUTHORIZATION', None)
print(header_token)
access_token=header_token.split(' ')[1]
status,user =validate_token(access_token)
print(request.data)
user_profile_serializer = UserProfileSerializer(
instance=user,
data=request.data
)
user_serializer = UserSerializer(
instance=user,
data=request.data)
print("my print 1",user_profile_serializer.is_valid())
print("my print 2",user_serializer.is_valid())
if user_profile_serializer.is_valid() and user_serializer.is_valid():
user_profile_serializer.save()
print("user_profile save done")
user_serializer.save()
print("user save done")
return Response(
status=rt_status.HTTP_200_OK)
# Combine errors from both serializers.
# errors = dict()
# errors.update(user_profile_serializer.errors)
#errors.update(user_serializer.errors)
else:
return Response(status=rt_status.HTTP_400_BAD_REQUEST)
'''
return Response(status=rt_status.HTTP_400_BAD_REQUEST)
'''
Now the issue is when i am hitting the api I am getting user_serializer.is_valid() as TRUE but False from UserProfile.
I feel there is some issue with my getting the profile data.
But I am not able to understand how to resolve the issue .
Any help is much appreciated.

Django Rest Framework: Saving ForeignKey inside OneToOne model

I have 2 models that are OneToOne related and model that is FK to 2nd model
models.py
class Legal(TimeStampedModel):
name = models.CharField('Name', max_length=255, blank=True)
class LegalCard(TimeStampedModel):
legal = models.OneToOneField('Legal', related_name='legal_card', on_delete=models.CASCADE)
branch = models.ForeignKey('Branch', related_name='branch', null=True)
post_address = models.CharField('Post address', max_length=255, blank=True)
class Branch(TimeStampedModel):
name = models.CharField('Name',max_length=511)
code = models.CharField('Code', max_length=6)
Using DRF I made them to behave as single model so I can create or update both:
serializer.py
class LegalSerializer(serializers.ModelSerializer):
branch = serializers.IntegerField(source='legal_card.branch', allow_null=True, required=False)
post_address = serializers.CharField(source='legal_card.post_address', allow_blank=True, required=False)
class Meta:
model = Legal
fields = ('id',
'name',
'branch',
'post_address',
)
depth = 2
def create(self, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
legal = super(LegalSerializer, self).create(validated_data)
self.update_or_create_legal_card(legal, legal_card_data)
return legal
def update(self, instance, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
self.update_or_create_legal_card(instance, legal_card_data)
return super(LegalSerializer, self).update(instance, validated_data)
def update_or_create_legal_card(self, legal, legal_card_data):
LegalCard.objects.update_or_create(legal=legal, defaults=legal_card_data)
views.py
class LegalDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Legal.objects.all()
serializer_class = LegalSerializer
I'm trying to save this by sending FK as integer (I just want to post id of the branch), but I receive error
ValueError: Cannot assign "2": "LegalCard.branch" must be a "Branch" instance.
Is there any way to pass over only ID of the branch?
Thank you
In Django, if you only need the FK value, you can use the FK value that is already on the object you've got rather than getting the related object.
Assume you have a Legal and Branch object with id's as 1. Then you can save a LegalCard object by:
LegalCard(legal_id=1,branch_id=1,post_address="Istanbul Street No:1")
Just use legal_card.branch_id instead of legal_card.branch to get just an id, not a related object.
And depth = 1

Categories

Resources