I am trying to implement an API to add Agent. The Agent has Many To Many field, Role.
I am using Django and Django Rest Framework.
Here is the models :
class Role(models.Model):
code = models.CharField(max_length=50, primary_key=True)
labe = models.CharField(max_length=50)
def __str__(self):
return '%s %s' % (self.labe, self.code)
class Agent(models.Model):
firstName = models.CharField(max_length=60)
lastName = models.CharField(max_length=60)
role = models.ManyToManyField(Role)
So I created Serializers :
class RegistrationSerializer(serializers.ModelSerializer):
role = serializers.PrimaryKeyRelatedField(
many=True, read_only=True)
class Meta:
model = Agent
fields = ['email', 'firstName', 'lastName', 'role',
'phoneNumber', 'experienceWorkYeares'],
def save(self):
agent = Agent.objects.create(
email=self.validated_data['email'],
firstName=self.validated_data['firstName'],
lastName=self.validated_data['lastName'],
phoneNumber=self.validated_data['phoneNumber'],
experienceWorkYeares=self.validated_data['experienceWorkYeares']
role=self.validated_data['role'] // One of my multiple try but doesn`t work.
)
agent.save()
return agent
How can I retrieve the role I sent via Postman and put it in the agent ? for the role I am POSTing role = "CODE1".
Thank you so much in advance.
Basically what I am trying to do is : For each Agent there one or more role. I trying a lot of thing and I follow documentation but I am not able to do it.
First of all, your role field should not be declared in the Meta class but in the ModelSerializer directly:
class RegistrationSerializer(serializers.ModelSerializer):
role = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Agent
fields = ['email', 'firstName', 'lastName', 'role',
'phoneNumber', 'experienceWorkYeares'],
extra_kwargs = {
'password': {'write_only': True},
}
Then, if you want to write a ManyToManyField you should use something like:
my_role = Role.objects.get(pk=self.validated_data['role'])
agent.role.add(my_role)
Another thing you could do is add a validate_role(self, value) method in your serializer that checks if the primary key provided is correct, and returns Role.objects.get(pk=value) -- then you would get the Role instance in your validated_data. See this part of DRF doc
Related
I'm trying to return the name of the pricing field but all I get is its foreign key id instead. What am I doing wrong here? I looked at some similiar issues on here but I didn't find anything that resembled my situation.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"assignedteams",
"agent",
"facility",
"organisor",
"avatar",
)
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
class UserSerializer(UserDetailsSerializer):
profile = UserProfileSerializer(source="userprofile")
subscription = UserSubscriptionSerializer(source="usersubscription")
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('profile', 'subscription',)
def update(self, instance, validated_data):
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
userprofile_data = validated_data.pop('userprofile', {})
usersubscription_serializer = self.fields['subscription']
usersubscription_instance = instance.usersubscription
usersubscription_data = validated_data.pop('usersubscription', {})
# update the userprofile fields
userprofile_serializer.update(userprofile_instance, userprofile_data)
usersubscription_serializer.update(usersubscription_instance, usersubscription_data)
instance = super().update(instance, validated_data)
return instance
You have 2 options to solve this problem.
option1:
If you want to return only the name of your pricing model you can use SlugRelatedField to do it.
Example:
class UserSubscriptionSerializer(serializers.ModelSerializer):
pricing = serializers.SlugRelatedField('name', readonly=True)
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
Option2:
If you want to return the Pricing object you can create a new ModelSerializer for your Pricing model and use it.
Example:
class PricingSerializer(serializers.ModelSerializer):
class Meta:
model = Pricing
fields = ["id","name"]
class UserSubscriptionSerializer(serializers.ModelSerializer):
pricing = PricingSerializer(readonly=True)
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
There are some other options that can you use but you must explain more about your problem can I will help you with.
you can easily add a new field representation or override the pricing field when want to represent data
so in your serializer add the following code
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
def to_representation(self, instance):
data = super().to_representation(instance)
data['pricing_name'] = instance.pricing.name # or replace the name with your pricing name field
return data
As you are saying pricing returned FK id, so i assume pricing column inside Subscription model is a FK to another model, let's assume it Pricing model.
You can create a serializer for Pricing and use it on UserSubscriptionSerializer,
like the way you created UserProfileSerializer and UserSubscriptionSerializer for UserSerializer
But, using directly a nested serializer will give you problem while doing write operation since as far i can understand you are accepting pricing as FK value when creating or updating
To solve this issue you can do some if/else on get_fields() method
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
def get_fields(self):
fields = super().get_fields()
# make sure request is passed through context
if self.context['request'] and self.context['request'].method == 'GET':
fields['pricing']=PricingSerializer()
return fields
Now coming back to the question, since you only need the pricing name which i assume name is a column on Pricing model
simply rewrite the previous code as
def get_fields(self):
fields = super().get_fields()
# make sure request is passed through context
if self.context['request'] and self.context['request'].method == 'GET':
fields['pricing'] = serializers.CharField(source='pricing.name', read_only=True)
return fields
P.S: I haven't tested this code on my computer
I have the following model Project:
class Project(models.Model):
name = models.CharField(max_length=128)
slug = models.SlugField(blank=True)
assigned_to = models.ManyToManyField(
User, blank=True, related_name="assignees")
created_date = models.DateField(auto_now_add=True)
updated_date = models.DateField(auto_now_add=False, auto_now=True)
If I need to create a new project, all I need to do is supply the name alone. This works for both the Admin dashboard and DRF APIVIEW. But when I try to test the functionality with DRF with an API call, I get the error: [b'{"assigned_to":["This field is required."]}'] Although the field is not required.
My test code below
import datetime
from marshmallow import pprint
from rest_framework.test import APITestCase, APIClient
from freezegun import freeze_time
from accounts.models import User
from .models import Project
#freeze_time("2021-11-14")
class ProjectTests(APITestCase):
client = APIClient()
project = None
name = 'IOT on Blockchain'
dead_line = datetime.date(2021, 11, 21)
data = {
'name': name,
'dead_line': dead_line,
}
def create_user(self):
username = 'test_user1'
email = 'test.user1#gmail.com'
password = '#1234xyz#'
user_type = 'regular'
data = {'username': username,
'email': email,
'password': password,
'user_type': user_type,
}
return User.objects.create_user(**data)
def create_project(self):
project = Project.objects.create(**self.data)
user = self.create_user()
project.assigned_to.add(user)
return project
def test_create_project_without_api(self):
"""
Ensure we can create a new project object.
"""
self.project = self.create_project()
self.assertEqual(Project.objects.count(), 1)
self.assertEqual(self.project.name, 'IOT on Blockchain')
self.assertEqual(self.project.dead_line,
datetime.date(2021, 11, 21))
self.assertFalse(self.project.reached_deadline)
self.assertEqual(self.project.days_to_deadline, 7)
# runs successfully
def test_create_project_with_api(self):
"""
Ensure we can create a new project object with an
API call.
"""
url = 'http://127.0.0.1:8000/api/projects'
project = self.client.post(url, self.data, format='json')
# project.data.assigned_to.set(self.create_user())
pprint(project.__dict__)
self.assertEqual(Project.objects.count(), 1)
self.assertEqual(self.project.name, 'IOT on Blockchain')
self.assertEqual(self.project.slug, 'iot-on-blockchain')
# does not run successfully (error mentioned in text body)
def test_delete_project(self):
"""
We can delete a user
"""
self.project = self.create_project()
self.project.delete()
self.assertEqual(Project.objects.count(), 0)
Edit: Added serializer code
class ProjectWriteSerializer(serializers.ModelSerializer):
"""
This serializer is used for CREATE, UPDATE operations on the Project model.
"""
# We receive list of user ids (ids[int] <= 0) by which we assign
# users to a project
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), many=True)
class Meta:
model = Project
fields = ('id', 'name', 'slug', 'assigned_to')
Any insights and help is very appreciated.
You have specified your own serializer field. As a result, it will no longer look at the blank=True part, and by default serializer fields are required. You can make these optional with:
class ProjectWriteSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(),
many=True,
required=True
)
class Meta:
model = Project
fields = ('id', 'name', 'slug', 'assigned_to')
I have two serializers... MyRegisterSerializer inherits and extends a popular app/package, django-rest-auth, which connects to a fairly standard user table. I also have a Model and serializer for a custom app, TeamSerializer (a one-to-many relationship with users). When a user signs up, I would like them to be able to join a team at the same time, so I somehow need to create a team, return the team ID and then pass that ID to the RegisterSerializer, so that the ID of the team can be stored in the User table. I know I could make two calls, first to create the team and return the value, and then pass it to the register serializer, but is there a way to do this all in one serializer? I am a n00b at python, and cant find a great example of this, considering I have to return the get_cleaned_data() function as it is. Thank you!
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ('id', 'name', 'logo', 'user')
class MyRegisterSerializer(RegisterSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
def get_cleaned_data(self):
super(MyRegisterSerializer, self).get_cleaned_data()
return {
'team_id': <How do I get this value>
'username': self.validated_data.get('username', ''),
'position': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', ''),
'first_name': self.validated_data.get('first_name', ''),
'last_name': self.validated_data.get('last_name', '')
}
It depends on how you want to create the team:
1. The team is created by some other information:
You should be able to use this custom field:
from rest_framework.relations import PrimaryKeyRelatedField
class TeamPrimaryKeyRelatedField(PrimaryKeyRelatedField):
def to_internal_value(self, data):
if self.pk_field is not None:
data = self.pk_field.to_internal_value(data)
try:
obj, created = self.get_queryset().get_or_create(
pk=data,
defaults=get_team_data(),
)
return obj
except (TypeError, ValueError):
self.fail('incorrect_type', data_type=type(data).__name__)
And use it in your Serializer:
class MyRegisterSerializer(RegisterSerializer):
team = TeamPrimaryKeyRelatedField()
# ...
2. Use extra user input to create the team:
This looks like a perfect use case for writable nested serializers:
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ('id', 'name', 'logo', 'user')
class MyRegisterSerializer(RegisterSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
team = TeamSerializer()
def create(self, validated_data):
team_data = validated_data.pop('team')
# You could do this if the user is not necessary in the team object:
team = Team.objects.create(**team_data)
user = super().create(team=team, **validated_data)
# Otherwise:
user = super().create(**validated_data)
# Should this be a many-to-many relationship?
team = Team.objects.create(user=user, **team_data)
# I don't know if this works/you need it:
self.team = team
# Or it should be like this?
self.validated_data['team'] = team
return user
I'm not sure what exactly you need. Let me know if you need further help.
I'm working on a Project using Python(3), Django(1.11) and DRF(3.6) in which I have to perform a PUT request by passing a nested nested instead of an ID.
Here's What I have tried:
models.py:
class Actor(models.Model):
id = models.CharField(primary_key=True, max_length=255)
login = models.CharField(max_length=255)
avatar_url = models.URLField(max_length=500)
class Repo(models.Model):
id = models.CharField(primary_key=True, max_length=255)
name = models.CharField(max_length=255)
url = models.URLField(max_length=500)
class Event(models.Model):
id = models.CharField(primary_key=True, max_length=255)
type = models.CharField(max_length=255)
actor = models.ForeignKey(Actor, related_name='actor')
repo = models.ForeignKey(Repo, related_name='repo')
created_at = models.DateTimeField()
serializers.py:
class ActorSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = ('id', 'login', 'avatar_url')
class RepoSerializer(serializers.ModelSerializer):
class Meta:
model = Repo
fields = ('id', 'name', 'url')
class EventModelSerializer(serializers.ModelSerializer):
actor = ActorSerializer(many=False)
repo = RepoSerializer(many=False)
class Meta:
model = Event
fields = ('id', 'type', 'actor', 'repo', 'created_at')
depth = 1
def create(self, validated_data):
return Event.objects.create(**validated_data)
Update: Here when I submit a post request with the following object:
{
"id":ID,
"type":"PushEvent",
"actor":{
"id":ID,
"login":"daniel33",
"avatar_url":"https://avatars.com/2790311"
},
"repo":{
"id":ID,
"name":"johnbolton/exercitationem",
"url":"https://github.com/johnbolton/exercitationem"
},
"created_at":"2015-10-03 06:13:31"
}
it return this error as: TypeError: 'ValueError: Cannot assign "OrderedDict([('id', '2790311'), ('login', 'daniel33'), ('avatar_url', 'https://avatars.com/2790311')])": "Event.actor" must be a "Actor" instance.
views.py:
class Actor(generics.GenericAPIView):
serializer_class = EventModelSerializer
queryset = EventModel.objects.all()
def update(self):
actor = EventModel.objects.filter(actor_id=self.request.data('id'))
print(actor)
return HttpResponse(actor)
Sample Input Object:
{
"id":3648056,
"login":"ysims",
"avatar_url":"https://avatars.com/modified2"
}
The requirements is:
Updating the avatar URL of the actor: The service should be able to update the avatar URL of the actor by the PUT request at /actors. The actor JSON is sent in the request body. If the actor with the id does not exist then the response code should be 404, or if there are other fields being updated for the actor then the HTTP response code should be 400, otherwise, the response code should be 200.**
I'm little bit confused about how to perform the PUT request without
passing an ID?
I have seen your two-three questions asked today. I think You are asking the wrong question. I think what you need is three models Event, actor and repo. the event model has two foreign key fields as actor and repo. Now what you want it to update the actor models avtar_url field. OK?
class Actor(models.Model):
avtar_url = models.CharField(max_length=255)
# Other Fields
class Repo(models.Model):
# Other Fields
class EventModel(models.Model):
id = models.CharField(primary_key=True, max_length=255)
type = models.CharField(max_length=255)
actor = models.ForaignKey(Actor)
repo = models.ForaignKey(Actor)
created_at = models.DateTimeField()
Now for create and update the NESTED EventModel entry use writable-nested-serializers. By this, you can directly update the avatar_url for Actor by its id.
UPDATE as per Request
you need to change your create method as following so that it creates Actor, Repo and link their ids to Event
def create(self, validated_data):
actor = validated_data.pop('actor')
repo = validated_data.pop('repo')
actor = Actor.objects.create(**actor)
repo = Repo.objects.create(**repo)
return Event.objects.create(actor=actor,repo=repo,**validated_data)
I'm trying to write a "def create" method to perform nested serialization for multiple objects.
def create(self, validated_data):
suggested_songs_data = validated_data.pop('suggested_songs')
suggest_song_list = list()
for song_data in suggested_songs_data:
song = Song.objects.create(**song_data)
suggest_song_list.append(song)
message = Messages.objects.create(suggested_songs=suggest_song_list, **validated_data)
return message
Here is my schema:
class MessagesSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
suggested_songs = SongSerializer(many=True)
class Meta:
model = Messages
fields = ('id','owner','url','suggested_songs',)
#fields = ('id','url','suggested_songs',)
class SongSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Song
fields =('id','title','artist','album','albumId','num_votes','cleared')
read_only_fields = ('song_id')
But I am getting this error
Cannot assign "[<Song: Song object>, <Song: Song object>]": "Messages.suggested_songs" must be a "Song" instance.
Any advice?
EDIT:
Here is the model.
class Messages(models.Model):
owner = models.OneToOneField(User, primary_key=True, related_name='user_messages', editable=False) #TODO, change owner to 'To'
#suggested_songs = models.ForeignKey(Song, null=True, blank=True)
suggested_songs = models.ManyToManyField(Song, related_name='suggested_songs')
You can't create manyToMany relations without the objects already created. You must first create the objects and then make the relation.
Something like:
def create(self, validated_data):
suggested_songs_data = validated_data.pop('suggested_songs')
message = Messages.objects.create(**validated_data)
for song_data in suggested_songs_data:
song = Song.objects.create(**song_data)
message.suggested_songs.add(song)
return message