Create nested object (1-1 relationship) from flattened POST request - python

I'm trying to implement two serializer classes which will allow me to create both user and profile objects from a flattened POST request.
I tried the implementation described here and it works perfectly fine for updating (and only updating) existing objects.
Here is my current implementation:
# serializers.py
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
write_only=True, required=True, style={"input_type": "password"}
)
class Meta:
model = User
fields = (
"username",
"password",
# ...
"date_joined",
)
read_only_fields = ("date_joined")
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
def to_representation(self, instance):
representation = super().to_representation(instance)
representation.update(representation.pop("user"))
return representation
def to_internal_value(self, data):
user_internal = {}
for key in UserSerializer.Meta.fields:
if key in data:
user_internal[key] = data.pop(key)
internal = super().to_internal_value(data)
internal["user"] = user_internal
return internal
def create(self, validated_data):
user_data = validated_data.pop("user")
user = User.objects.create(**user_data)
user.set_password(user_data["password"])
user.save()
profile = UserProfile.objects.create(user=user, **validated_data)
return profile
class Meta:
model = UserProfile
fields = (
"user",
"date_updated",
# ...
"phone_number",
)
# views.py
class Register(generics.CreateAPIView):
serializer_class = UserProfileSerializer
name = "userprofile-create"
I expect the app to take the flattened JSON and create both user and profile objects.
Example POST body:
{
"username": "test_user",
"password": "P#$$w0rd",
"first_name": "Foo",
"last_name": "Boo",
"email": "foo#example.com",
"street": "Random Street",
"street_number": "11",
"flat_number": "11",
"zip_code": "11-111",
"city": "Some City",
"province": 1,
"phone_number": "111222333"
}
When I'm browsing through the API, the view still expects JSON with nested User object:
{
"user": {
"username": "",
"password": "",
"first_name": "",
"last_name": "",
"email": ""
},
"street": "",
"street_number": "",
"flat_number": "",
"zip_code": "",
"city": "",
"province": null,
"phone_number": ""
}

Overriding to_internal_value() and to_representation() wasn't correct approach.
I've created only one serializer with all fields and overriden create() method:
class UserProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source="user.username")
password = serializers.CharField(
source="user.password",
write_only=True,
required=True,
style={"input_type": "password"},
)
first_name = serializers.CharField(source="user.first_name")
last_name = serializers.CharField(source="user.last_name")
email = serializers.EmailField(source="user.email")
def create(self, validated_data):
user_data = validated_data.pop("user")
user = User.objects.create(**user_data)
user.set_password(user_data["password"])
user.save()
profile = UserProfile.objects.create(user=user, **validated_data)
return profile
class Meta:
model = UserProfile
fields = (
"username",
"password",
"first_name",
"last_name",
"email",
"date_updated",
"street",
"street_number",
"flat_number",
"zip_code",
"city",
"province",
"phone_number",
)

Related

django rest framework update nested serializer by id

How to update a list in a nested serializer by id
I have a user who has multiple contacts
Example:
contact serializer
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = [
'id',
'name',
'last_name'
]
user serializer
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
contacts = ContactSerializer(many=True)
class Meta:
model = User
fields = [
"email",
"contacts"
]
def update(self, instance, validated_data):
contacts_data = validated_data.pop('contacts')
contacts = (instance.contacts).all()
contacts = list(contacts)
instance.name = validated_data.get('name', instance.name)
instance.save()
# many contacts
for contact_data in contacts_data:
contact = contacts.pop(0)
contact.name = contact_data.get('name', contact.name)
contact.last_name = contact_data.get('last_name', contact.last_name)
contact.save()
return instance
I want to update contacts by ID, for now it works, but it only updates the first contact in the list
Example I want to update the contact with ID 4 and 6
Payload request
{
"name": "string",
"contacts": [
{
"id": 4,
"name": "string",
"last_name": "string"
},
{
"id": 6,
"name": "string",
"last_name": "string"
}
]
}
Any ideas or recommendations?
Try it like this
def update(self, instance, validated_data):
contacts_data = validated_data.pop('contacts')
instance.name = validated_data.get('name', instance.name)
instance.save()
# many contacts
for contact_data in contacts_data:
contact = Contact.objects.get(pk=contact_data['id']) # this will crash if the id is invalid though
contact.name = contact_data.get('name', contact.name)
contact.last_name = contact_data.get('last_name', contact.last_name)
contact.save()
return instance

Can someone help me here. I'm trying to assign more than one rule to a single user based on role's names in django DRF

Here in my model I have multiple roles for users like HR, IT, ADMIN, FINANCE, EMPLOYEE and PROJECT_MANAGER. Purpose is to assign roles to user more than one based on role names. But I'm unable to get there.
Here are the models:
class UserRole(HRMBaseModel):
HR = 'HR'
IT = 'IT'
ADMIN = 'ADMIN'
FINANCE = 'FINANCE'
EMPLOYEE = 'EMPLOYEE'
PROJECT_MANAGER = 'PROJECT MANAGER'
ROLE_CHOICES = (
(HR, 'hr'),
(IT, 'it'),
(ADMIN, 'admin'),
(FINANCE, 'finance'),
(EMPLOYEE, 'employee'),
(SUPER_ADMIN, 'super admin'),
(PROJECT_MANAGER, 'project manager'),
)
role_name = models.CharField(max_length=255, choices=ROLE_CHOICES)
def __str__(self):
return self.role_name
class User(AbstractUser, HRMBaseModel):
username = models.CharField(max_length=50, unique=True)
email = models.EmailField(_('email address'))
role = models.ManyToManyField(UserRole)
Here is serializers.py to create user
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'role', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
role_data = validated_data.pop('role')
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password)
user.save()
profile_data = {
"display_name": validated_data['first_name'],
"fullname": f"{validated_data['first_name']} {validated_data['last_name']}",
"dob": None,
"address": None,
"country": None,
"city": None,
"state": None,
"zip_code": None,
"profile_avatar": None
}
UserProfile.objects.create(user=user, **profile_data)
user.role.add(UserRole.objects.get(role_name=role_data[0]))
return user
Here is how I'm creating user
{
"email": "test_99#admin.com",
"first_name": "test_99",
"last_name": "test_99",
"username": "test_99",
"password": "test_99",
"role": [3]
}
But this is how I want to create user
{
"email": "test_99#admin.com",
"first_name": "test_99",
"last_name": "test_99",
"username": "test_99",
"password": "test_99",
"role": ["IT", "ADMIN", "HR"]
}
Almost there - let's modify your create method a bit:
def create(self, validated_data):
# pop role, should be a list ["IT", "ADMIN", "HR"]:
role_names = validated_data.pop('role')
# filter for role objects based on data in list:
roles = UserRole.objects.filter(role_name__in=role_names)
# other logic in create:
user = ...
...
# finally, add roles to user:
user.role.add(*roles)
user.save()
return user

How to create new instances of both models contributing in OneToOne relationship in django rest framework (DRF)?

I have created an Author model in which it has a OneToOne relationship with default Django User, as below:
from django.contrib.auth.models import User
from django.db import models
class Author(models.Model):
user = models.OneToOneField(
User,
related_name='author',
on_delete=models.CASCADE,
default="",
)
is_author = models.BooleanField(
default=True
)
And here I have created the following viewset:
class AuthorViewSet(viewsets.ModelViewSet):
serializer_class = AuthorSerializer
queryset = Author.objects.all()
def get_permissions(self):
if self.action == "list" or self.action == "retrieve" or self.action == "update":
self.permission_classes = [IsCurrentOwner, permissions.IsAdminUser]
elif self.action == "create":
self.permission_classes = [permissions.AllowAny]
return super(AuthorViewSet, self).get_permissions()
Question Is there any way that I can create both user and author in one step (request)? How?
serializer code:
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = "__all__"
extra_kwargs = {
'password': {'write_only': True},
'id': {'read_only': True}
}
I have tried the following request but it doesn't work.
#url: localhost:8000/users/
#method: POST
{
"user": {
"username": "mostafa",
"password": "1"
}
}
Error:
{
"user": [
"Incorrect type. Expected pk value, received dict."
]
}
For this, you will need to create another serializer for user:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
extra_kwargs = {
'password': {'write_only': True},
}
After that, make the user field in your AuthorSerializer an instance of UserSerializer and override the create method in this manner:
class AuthorSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Author
fields = "__all__"
extra_kwargs = {
'id': {'read_only': True}
}
def create(self, validated_data):
user = None
if "user" in validated_data:
user_data = validated_data.pop("user") or {}
user = User.objects.create_user(**user_data) # Assuming that it is default django user model
author = Author.objects.create(user=user, **validated_data)
author.save()
return author
curl script for your request payload:
curl --location --request POST 'http://127.0.0.1:8000/users/' \
--header 'Content-Type: application/json' \
--data-raw '{
"user": {
"username": "mostafa",
"password": "abcd"
},
"is_author": true
}'
And it responses as below:
{
"id": 1,
"user": {
"id": 1,
"last_login": null,
"is_superuser": false,
"username": "mostafa",
"first_name": "",
"last_name": "",
"email": "",
"is_staff": false,
"is_active": true,
"date_joined": "2020-12-05T09:54:37.674749Z",
"groups": [],
"user_permissions": []
},
"is_author": true
}

How to print model fields as json output?

views.py
def districts_list(request):
obj_districts_list = Districts.objects.all()
data = serializers.serialize(
'json', obj_districts_list, fields=('district_name'))
return HttpResponse(data, content_type="application/json")
models.py
class Districts(models.Model):
id = models.AutoField(primary_key=True)
district_name = models.CharField(max_length=40)
country = models.ForeignKey('Country', on_delete=models.CASCADE)
state = models.ForeignKey('States', on_delete=models.CASCADE)
def __str__(self):
return '{}'.format(self.district_name)
OUTPUT:
But i just want the fields inside the Accounts.districts as output in JSON format.
Use QuerySet.values() to retrieve the fields you're interested in as dicts, then pass this to json.dumps(), ie:
>>> qs = User.objects.values("username", "first_name", "last_name", "email", "userprofile__nickname")
>>> print(json.dumps(list(qs), indent=2))
[
{
"username": "toto",
"first_name": "",
"last_name": "",
"email": "toto#example.com",
"userprofile__nickname": "Toto le toto"
},
{
"username": "root",
"first_name": "Root",
"last_name": "Root",
"email": "root#example.com",
"userprofile__nickname": "Root"
},
]
Depending on your models fields, you may have to provide a custom JSONDecoder subclass for datetimes and other types not directly handled by the default JSONEncoder.

Django REST Framework Model Serializer is not showing all fields for POST method?

I'm trying to make custom create()method to save my user profile when a User is created, the serializer works perfect for the GET method, but for POST it only shows one field instead of all fields.
I specified the fields = '__all__' property, but still don't work
GET:
JSON Response:
[
{
"job_title": {
"id": 1,
"name": "Desarrollador"
},
"birthdate": "2019-11-06",
"roles": [
{
"id": 1,
"name": "Administrador",
"description": "Administrador del sistema",
"key": "ADMIN"
}
]
}
]
POST:
Expected JSON:
[
{
"birthdate": "2019-11-06",
}
]
Serializers:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = '__all__'
depth = 1
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(read_only=False, many=False)
class Meta:
model = User
fields = '__all__'
depth = 1
def create(self, validated_data):
profile_data = validated_data.pop('user_profile')
user = User.objects.create(**validated_data)
UserProfile.objects.create(user=user, **profile_data)
return user
views.py
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
profile = serializers.PrimaryKeyRelatedField(many=False, queryset=UserProfile.objects.all())
queryset = User.objects.all()
serializer_class = UserSerializer
class UserProfileList(generics.ListCreateAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='user_profile', on_delete=models.CASCADE)
birthdate = models.DateField(default=datetime.date.today)
job_title = models.ForeignKey(JobTitle, related_name='job_title', on_delete=models.CASCADE)
roles = models.ManyToManyField(Role)
def createProfile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.created(user=kwargs['instance'])
post_save.connect(createProfile, sender=User)
def __str__(self):
return self.user.username
I need that POST method also requires the full JSON, the same as the GET JSON response, in order to complete the create method. Need this for POST method:
{
"id": 1,
"user_profile": {
"job_title": {
"id": 1,
"name": "Desarrollador"
},
"birthdate": "2019-11-06",
"roles": [
{
"id": 1,
"name": "Administrador",
"description": "Administrador del sistema",
"key": "ADMIN"
}
]
},
"password": "pbkdf2_sha256$150000$0XdmQpeiPtVl$rQh2MEYV+IO5Y4gm2o1md2cVzgn/mL95r6m1TvRmG3g=",
"last_login": "2019-11-06T00:34:25-06:00",
"is_superuser": true,
"username": "aruiz",
"first_name": "",
"last_name": "",
"email": "correo#correo.com",
"is_staff": true,
"is_active": true,
"date_joined": "2019-11-05T11:37:49-06:00",
"groups": [],
"user_permissions": []
}

Categories