I am trying to create an api for device group and device. A device group can have multiple devices and i want to have the post api for device group with multiple devices because group creation is shown only when device is selected and user might select multiple devices and then create a new group. That way when group is created, those selected devices should also be shown as device_list
Here is my code, i am not sure on how to do post request
class BaseDevice(PolymorphicModel):
name = models.CharField(max_length=250, blank=False, null=False)
group = models.ForeignKey('DeviceGroup', related_name="groups", null=True, blank=True)
class Device(BaseDevice):
description = models.TextField(blank=True, null=True)
class DeviceGroup(models.Model):
name = models.CharField(max_length=250, blank=False, null=False)
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
# id is the token of the device and name is the name of the device
fields = ('id', 'name')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
device_list = DeviceIdSerializer(read_only=False, many=True, required=False, source="groups")
class Meta:
model = DeviceGroup
# name is the name of group created and device_list is the list of devices with id(token) and device name
fields = ('id', 'name', 'device_list')
def create(self, validated_data):
print ('validated_data', validated_data)
device_list_data = validated_data.pop('device_list')
group = DeviceGroup.objects.create(**validated_data)
for device_list in device_list_data:
BaseDevice.objects.create(group=group, **device_list)
return group
class DeviceGroupAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get_object(self, user, token):
try:
return BaseDevice.objects.filter(owner=user).get(token=token)
except ObjectDoesNotExist:
return error.RequestedResourceNotFound().as_response()
def post(self, request, token=None, format=None):
device_group_instance = DeviceGroup.objects.get(token=token)
for device_token in request.data['devices']:
device = Device.objects.get(token=device_token, owner=request.user)
device.group = device_group_instance
Here is my api design
{
"data":[
{
"id":1,
"name":"Home",
"device_list":[
{
"id":"481cfef5a4884e52a63d135967fbc367",
"name":"Oxygen Provider"
},
{
"id":"7eb006d6db50479aa47f887da0d4f10e",
"name":"Fan Speed"
}
]
},
{
"id":2,
"name":"Business",
"device_list":[
]
}
]
}
UPDATE
url(r'^device_group/(?P<token>[0-9a-f]+)/add$', DeviceGroupAPIView.as_view(), name='device_group'),
I've changed your code a bit
class BaseDevice(PolymorphicModel):
name = models.CharField(max_length=250, blank=False, null=False)
group = models.ForeignKey('DeviceGroup', related_name="groups", null=True, blank=True)
class Device(BaseDevice):
description = models.TextField(blank=True, null=True)
class DeviceGroup(models.Model):
name = models.CharField(max_length=250, blank=False, null=False)
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
# token does not exist in your model so this will not be included
class Meta:
model = Device
# id is the token of the device and name is the name of the device
fields = ('id', 'name')
class DeviceGroupSerializer(serializers.ModelSerializer):
device_list = DeviceIdSerializer(read_only=False, many=True, required=False, source="groups")
class Meta:
model = DeviceGroup
# name is the name of group created and device_list is the list of devices with id(token) and device name
fields = ('id', 'name', 'device_list')
def create(self, validated_data):
print ('validated_data', validated_data)
device_list_data = validated_data.pop('groups', [])
# notice that I pop 'groups' because validation changes the input data
# to match the field names
# Also since it is not required I've added a default value
group = DeviceGroup.objects.create(**validated_data)
devices = [BaseDevice(group=group, **device_list) for device_list in device_list_data]
BaseDevice.objects.bulk_create(devices)
# Use bulk create when you have to create multiple objects
# It hits the db only once instead of multiple times
return group
class DeviceGroupAPIView(ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = DeviceGroupSerializer
queryset = DeviceGroup.objects.all()
# Although I have used ModelViewSet, you could use any other one
# I used this so that I don't need to write the code for create,
# update, delete or list
# urls.py
router = routers.DefaultRouter()
router.register(r'device_group', DeviceGroupAPIView, base_name='device_group')
# this gives the following urls
# /device_group/ POST to create, GET to list
# /device_group/(?<pk>\d+)/ GET to retrieve single DeviceGroup, PATCH/PUT to update it, and DELETE to delete it
This is the structure of the JSON to POST to create a new DeviceGroup with a bunch of Devices
{
"name":"Group Name",
"device_list":[
{
"name":"Device 1"
},
{
"name":"Device 2"
},
{
"name":"Device 3"
}
]
}
Hope this helps
Also you should read up more about Django-Rest-Framework
Related
I'm writing app in witch I store data in separate models. Now I need to combine this data to use it.
The problem.
I have three models:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=50, blank=True)
...
class Contacts(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user")
contact_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="contact_user")
class UserPhoto(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
url = models.CharField(max_length=220)
How can I get the current user contacts with their names and pictures like this (serialized)
{
{
"contact_user":"1",
"first_name":"Mark ",
"url":first picture that corresponds to contact_user id
},
{
"contact_user":"2",
"first_name":"The Rock",
"url":first picture that corresponds to contact_user id
}
}
Now I'm quering the Contacts model to get all contacts_user id's that he has connection to.
class MatchesSerializer(serializers.ModelSerializer):
class Meta:
model = Contacts
fields = '__all__'
depth = 1
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactsSerializer
def get_queryset(self):
return Contacts.objects.filter(user__id=self.request.user.id)
The thing you need to is to serialize the Contacts queryset to include the related User and UserPhoto objects for each contact.
Try to create a custom serializer for the Contacts model so:
class ContactSerializer(serializers.ModelSerializer):
contact_user = serializers.SerializerMethodField()
def get_contact_user(self, obj):
user = obj.contact_user
photo = user.userphoto_set.first()
return {
"id": user.id,
"first_name": user.first_name,
"url": photo.url if photo else None
}
class Meta:
model = Contacts
fields = ("contact_user",)
Then, modify the ContactViewSet to use this newly created serializer so:
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
def get_queryset(self):
return Contacts.objects.filter(user__id=self.request.user.id)
Note: Generally, Django models don't require s to be added as suffix since it is by default added, so it is better to modify it as Contact from Contacts.
I have a CustomUser model and two separate Models for profile two types of User. I'm trying to combine the attribute of CustomUser and one of the Profile into single endpoint from which the user can see/update/delete the user/profile. For instance there are 2 types of users, doctor & patient. so if the user is doc then the endpoint will return the attributes of CustomUser+DoctorProfile and same for the Patient CustomUser+PatientProfile. Below is the code. I will explain the issue in the code base with comments. I will enormously appreciate any suggestion. One thing to mention is that I split the models.py file into 3 different folder and imported all of them into __init__.py of models folder.
CustomUser 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
DoctorProfile Model:
class DoctorProfile(models.Model):
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 required attributes from DoctorProfile model"""
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number', 'gender', 'city', 'country', )
class DoctorProfileSerializer(serializers.ModelSerializer):
"""Above Serializer is used in a new attribute profile. So that I can combine the CustomUser and DoctorProfile."""
profile = DoctorProfileFields(source='*')
"""
if I use source in the above line the serializer returns the json in the
expected format while I use get method, otherwise it return error saying profile
is not an attribute of CustomUser. but for put method the json payload is getting
received in a wrong formation. attributes of nested Profile object is getting
combined in the same level of Custom user,
{"name": "jon", "avatar": null, "doctor_type": "anything"}
but it has to receive like this
{"name": "jon", "avatar": null, "profile": {"doctor_type": "anything}}
"""
class Meta:
model = User
fields = ('name', 'avatar', 'profile', )
#transaction.atomic
def update(self, instance, validated_data):
ModelClass = self.Meta.model
"""print("=======validated_data=========: ", validated_data). I found in this
line that the payload is wrong"""
profile = validated_data.pop('profile', {})
"""print("=======profile=========: ", profile) profile is not in validated data
that's why 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
On the other hand if I don't use source the error is same for both get and put method.
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)
After using source the get method returns in the expected format:
{
"name": "Maruf updated again",
"avatar": null,
"profile": {
"doctor_type": null,
"date_of_birth": null,
"registration_number": null,
"city": null,
"country": null
}
}
But the problem is with the Put method.
Another note: with using source and not overriding the update method in the serializer, only the CustomUser attributes is getting updated.
my objective is to get both CustomUser+Profile in the same endpoint.
And when updating, the CustomUser and the Profile will be updated in their own table but through the same endpoint.
model
class Enrollee(TimeStampedModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('id'))
first_name = models.CharField(max_length=60, verbose_name=_('first name'))
dependents = models.ManyToManyField(to=Dependant, blank=True, verbose_name=_('dependents'))
class Dependant(TimeStampedModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('id'))
first_name = models.CharField(max_length=60, verbose_name=_('first name'))
view
class EnrolleeViewSet(viewsets.ModelViewSet):
queryset = models.Enrollee.objects.all()
serializer_class = serializers.EnrolleeSerializer
filter_class = filters.EnrolleeFilter
serializers
class EnrolleeSerializer(DynamicFieldsMixin, DynamicFieldsModelSerializer):
dependents = DependantSerializer(many=True, required=False)
def create(self, validated_data):
dependant_data = validated_data.pop('dependents')
enrollee = models.Enrollee.objects.create(**validated_data)
for dependant in dependant_data:
models.Dependant.objects.create(**dependant)
return enrollee
class Meta:
model = models.Enrollee
fields = '__all__'
error:
{
"first_name": "hmo1",
"dependents": [],
}
It means dependents is not inserting
Here I am trying to send nested data for my dependents(ManyToManyField)
But, Getting above error while inserting data.
Is there any way we can achive this ?
{
"first_name":"soubhagya",
"dependents" : [
{
"first_name":"soubhagya"
}
]
}
Above is the data i am sending using post method.
please have a look
def create(self, validated_data):
dependant_data = validated_data.pop('dependents')
enrollee = models.Enrollee.objects.create(**validated_data)
for dependant in dependant_data:
obj = models.Dependant.objects.create(**dependant)
enrollee.dependents.add(obj)
enrollee.save()
return enrollee
I am creating an api where the list of groups are shown along with the devices id that falls under that groups. For example if there is a device named Speedometer, Humidifier and they fall under Home group then my api should include
{
"id": 1,
"name": "Home"
"device_list": [
{
"id": "b45c56ioxa1"
},
{
"id": "h4oc2d5ofa9"
}
]
},
but my code does not produce device_list in the api. It only shows name and id
device_list is the list of all the devices id that are in a certain group.
Here is my code
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
device_list = DeviceIdSerializer(read_only=False, many=True, required=False, source="groups")
class Meta:
model = DeviceGroup
fields = ('id', 'name', 'device_list')
class DevicesGroupsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, format=None):
"""
Returns a list of groups
"""
reply = {}
try:
groups = DeviceGroup.objects.all()
print ('reply', groups)
reply['data'] = DeviceGroupSerializer(groups, many=True).data
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
class BaseDevice(PolymorphicModel):
# User's own identifier of the product
name = models.CharField(max_length=250, blank=False, null=False)
# Any device should have a owner, right from the creation
owner = models.ForeignKey(User, blank=False, null=False)
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
group = models.ForeignKey('DeviceGroup', related_name="groups", null=True, blank=True)
class Device(BaseDevice):
description = models.TextField(blank=True, null=True)
class DeviceGroup(models.Model):
name = models.CharField(max_length=250, blank=False, null=False)
I tried out the very same code you have except I used models.Model as the base model.
The first time I got an error
The fields option must be a list or tuple or "all". Got str.
which clearly states where your problem is.
So I changed class the fields option in DeviceIdSerializer
DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id',)
Note that I've added a comma (",") which makes fields a tuple instead of a string as it was before.
Now the data I get is
{
"id":1,
"name":"test",
"device_list":[
{
"id":"38ec7e152f9d49a38008c859a1022525"
},
{
"id":"b0d799509260474cb092899ef84ce49c"
},
{
"id":"e5c7cf8f9f5043c68c34c7b962569b08"
}
]
}
which is the same as what you are looking for...
I think your serializers need to look like this:
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
groups = DeviceIdSerializer(read_only=False, many=True, required=False)
class Meta:
model = DeviceGroup
fields = ('id', 'name', 'groups')
Or change this:
class BaseDevice(PolymorphicModel):
# User's own identifier of the product
name = models.CharField(max_length=250, blank=False, null=False)
# Any device should have a owner, right from the creation
owner = models.ForeignKey(User, blank=False, null=False)
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
group = models.ForeignKey('DeviceGroup', related_name="device_list", null=True, blank=True)
I am trying to add gmail feature of moving the mail to custom created label or already made label. It is a move to label feature. When you select the mail and then click on move to label actions, you will see lots of pre-defined label name such as important, sms, personal, work etc and also a create a new label in the dropdown. I want to have get, post and delete. Am i on the right track?
Here is my model
class Device(BaseDevice):
"""
This stores Device
"""
# Description is optional (can be blank, can be null)
description = models.TextField(blank=True, null=True)
created_on = models.DateTimeField(auto_now_add=True)
updated_on= models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Label(models.Model):
"""
This creates a label
"""
# Name is required
name = models.CharField(max_length=250, blank=False, null=False)
device = models.ForeignKey(Device, related_name='label')
Serializers
class IOSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex')
class Meta:
model = IO
fields = ('id', 'name', 'description', 'type')
class LabelSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex')
class Meta:
model = Label
fields = ('id', 'name')
class DeviceSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
label = LabelSerializer(read_only=False, many=True, required=False)
io = IOSerializer(read_only=False, many=True, required=False)
class Meta:
model = Device
fields = ('id', 'name', 'description', 'io', 'label')
def save_io(self, device, io):
for io in io:
token = io.pop('token', None)
if token is None:
IO.objects.create(device=device, **io)
else:
IO.objects.filter(token=token, device=device).update(**io)
def update(self, instance, validated_data):
io = validated_data.pop('io',[])
instance = super(DeviceSerializer, self).update(instance, validated_data)
if len(io):
self.save_io(instance, io)
return instance
def create(self, validated_data):
io = validated_data.pop('io',[])
instance = super(DeviceSerializer, self).create(validated_data)
if len(io):
self.save_io(instance, io)
return instance