How to update multiple objects in django rest framework? - django

I am trying to update multiple objects using IDs which i am passing in every objects that need to be updated but can't find any way to do it successfully. Here is my code
models.py
class EventTicket(models.Model):
id = models.UUIDField(primary_key=True, default=uuid_generate_v1mc, editable=False)
name = models.CharField(max_length=250)
description = models.TextField(max_length=1000)
views.py
class EventTicketView(APIView, PaginationHandlerMixin):
permission_classes = (AllowAny,)
def get_object(self, ticket_id):
try:
return EventTicket.objects.get(id=ticket_id)
except EventTicket.DoesNotExist():
raise status.HTTP_400_BAD_REQUEST
def patch(self, request, *args, **kwargs):
for each_ticket in request.data:
ticket_id = self.get_object(each_ticket['ticket_id'])
serializer = EventTicketSerializer(instance=ticket_id,data=request.data,partial=True)
if serializer.is_valid():
serializer.save()
result = {
'message': "updated sucessfully"
}
return Response(result, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class EventTicketSerializer(serializers.ModelSerializer):
class Meta:
model = EventTicket
fields = ['name', 'description']
```
I have to send data like list of multiple objects :::
[
{
"ticket_id": "054665ea-4fde-11ea-94b2-9f415c43ba4c",
"name": "chris",
"description":"The golden ticket for day only",
},
{
"ticket_id": "054656ea-4fde-11ea-94b2-9f415c43ba4c",
"name": "daut",
"description":"The premium ticket for day only",
}
]

The following code will give you a proper understanding of updating multiple objects in single request.
For updating multiple objects in a single request it is best practice to use the PUT method instead of PATCH.
Here body data given is.
BODY DATA
{
"ids":[
"5e41770d2e8fa013d1f034ec",
"5e41772c2e8fa013d1f034ee",
"5e4177702e8fa013d1f034f2",
"5e453f302e8fa075aa18b277",
"5e4a314f2e8fa070c5251a0a"
]
}
I'am updating the enabled attribute from False to True for given ids of DemoConfig model.
In the same way, you can update your data. As per your requirement, you can write validate methods to validate the body data.
Serializer has written to serialized the instance data for the response.
class DemoAPI(APIView):
def get_object(self, obj_id):
try:
return DemoConfig.objects.get(id=obj_id)
except (DemoConfig.DoesNotExist, ValidationError):
raise status.HTTP_400_BAD_REQUEST
def validate_ids(self, id_list):
for id in id_list:
try:
DemoConfig.objects.get(id=id)
except (DemoConfig.DoesNotExist, ValidationError):
raise status.HTTP_400_BAD_REQUEST
return True
def put(self, request, *args, **kwargs):
id_list = request.data['ids']
self.validate_ids(id_list=id_list)
instances = []
for id in id_list:
obj = self.get_object(obj_id=id)
obj.enabled = True
obj.save()
instances.append(obj)
serializer = DemoSerializer(instances, many=True)
return Response(serializer.data)
Serialiser for this view is:
class DemoSerializer(DocumentSerializer):
class Meta:
model = DemoConfig
fields = '__all__'
Output:
{
"data": [
{
"id": "5e41770d2e8fa013d1f034ec",
"name": "CONFIG_1",
"enabled": true,
},
{
"id": "5e41772c2e8fa013d1f034ee",
"name": "CONFIG_2",
"enabled": true,
},
{
"id": "5e4177702e8fa013d1f034f2",
"name": "CONFIG_3",
"enabled": true,
},
{
"id": "5e453f302e8fa075aa18b277",
"name": "CONFIG_4",
"enabled": true,
},
{
"id": "5e4a314f2e8fa070c5251a0a",
"name": "CONFIG_5",
"enabled": true,
}
]
}
As per your code requirement you need to use put method in follwoing way.
def put(self, request, *args, **kwargs):
data = request.data
ticket_ids = [i['ticket_id'] for i in data]
self.validate_ids(ticket_ids)
instances = []
for temp_dict in data:
ticket_id = temp_dict['ticket_id']
name = temp_dict['name']
description = temp_dict['description']
obj = self.get_object(ticket_id)
obj.name = name
obj.description = description
obj.save()
instances.append(obj)
serializer = DemoSerializer(instances, many=True)
return Response(serializer.data)

Related

Django Rest Framework unique field constraint on array

So, I'm trying to make an endpoint where I insert a list of objects.
My issue is the behavior and response when inserting duplicates.
What I want to accomplish is to:
Send the duplicate lead external_id(s) in the error response
Insert any other non duplicated object
I'm pretty sure this logic (for the response and behavior) could be accomplished in the modifying the serializer.is_valid() method... but before doing that, I wanted to know if anyone had experience with this kind of request.. Maybe there is a "clean" way to do this while keeping the unique validation in the model.
Data on OK response:
[
{
"external_id": "1",
"phone_number": "1234567890"
}
]
Data for a FAIL request (1 is dup, but 2 should be inserted. Expecting a response like "external_id" : "id 1 is duplicated"):
[
{
"external_id": "1",
"phone_number": "1234567890"
},
{
"external_id": "2",
"phone_number": "2234567890"
}
]
models.py
class Lead(models.Model):
external_id = models.CharField(max_length=20, unique=True)
phone_number = models.CharField(max_length=50)
serializers.py
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = '__all__'
def create(self, validated_data):
lead = Lead.objects.create(**validated_data)
log.info(f"Lead created: {lead.import_queue_id}")
return lead
views.py
class LeadView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
#extend_schema(description="Insert campaign data", request=LeadSerializer(many=True), responses=None, tags=["Leads"])
def post(self, request):
serializer = LeadSerializer(data=request.data, many=True)
valid = serializer.is_valid()
if serializer.is_valid():
serializer.save()
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
You can customize the behaviour of list of object through list_serializer_class option in meta class. Like this:
class LeadListSerializer(serializers.ListSerializer):
def validate(self, data):
items = list(map(lambda x: x['phone_number'], data))
if len(set(items)) == len(items)):
return super().validate(data)
raise ValidationError()
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = '__all__'
list_serializer_class = LeadListSerializer

Django Rest Framework - Wrapping the JSON response with the name of the model

My goal is to customize the JSON response from Django DRF when listing items.
The model:
class Object(models.Model):
code = models.CharField(max_length=16, primary_key=True, unique=True)
item = models.CharField(max_digits=128)
last_updated = models.DateTimeField(auto_now=True, editable=False)
the serializer:
class ObjectSerializer(serializers.ModelSerializer):
class Meta:
model = Object
fields = ['code', 'item']
the view:
class ObjectList(generics.ListAPIView):
queryset = Object.objects.all()
serializer_class = ObjectSerializer
def list(self, request):
queryset = self.get_queryset()
serializer = ObjectSerializer(queryset, many=True)
return Response(serializer.data)
with this setup the JSON response is:
[
{
"code": "111",
"item": "aaa"
},
{
"code": "222",
"item": "bbb"
}
]
Is there a way to wrap the response with the name of the model?
Expected result would be:
"objects": [
{
"code": "111",
"item": "aaa"
},
{
"code": "222",
"item": "bbb"
}
]
You can customize response on view level, just wrap serializer's data into outer dictionary:
class ObjectList(generics.ListAPIView):
queryset = Object.objects.all()
serializer_class = ObjectSerializer
def list(self, request):
queryset = self.get_queryset()
serializer = ObjectSerializer(queryset, many=True)
return Response({"objects": serializer.data})

DRF: to_representation() called twice on serializer update()

My site has a Vue frontend with a DRF backend. I have a model with a multi-value field that looks like this:
stages = StageSerializer(
many=True,
read_only=True
)
In the to_representation() method, I process the stages data into three different elements to facilitate features for the frontend.
def to_representation(self, instance):
results = super().to_representation(instance)
return render_stages(results)
The problem is, when I post (PATCH actually) to an update view, which ends up calling the update() method of the serializer, the to_representation() method seems to be called twice. I could post the code from render_stages(), but I don't see how that could be causing the problem. Suffice to say that it takes this, from the call to super().to_representation():
{
.
. other fields
.
"stages": [
{
"id": 1,
"name": "To Do"
},
{
"id": 2,
"name": "In Progress"
},
{
"id": 3,
"name": "Done"
}
]
}
...and turns it into this:
{
"stages": [
"To Do",
"In Progress",
"Done"
],
"stages_order": "1,2,3",
"stages_data": [
{
"id": 1,
"name": "To Do",
"order": 1
},
{
"id": 2,
"name": "In Progress",
"order": 2
},
{
"id": 3,
"name": "Done",
"order": 3
}
]
}
As you can see, the format of the "stages" element changes after the first pass through to_representation() from a list of dicts to a list of strings. So when to_representation() gets called a second time, it raises an exception.
On a list view, it only gets called once. It's just on an update view that it gets called twice. Can someone explain why that's happening?
Update: More details:
The view. The settings model only has one row - pk=1 - so this is a departure from the norm.
class SettingsUpdate(ViewMetaMixin, UpdateAPIView):
serializer_class = SettingsSaveSerializer
permission_classes = (IsAuthenticated, CanAccessEndpoint)
queryset = Settings.objects.none()
def get_object(self):
obj = Settings.objects.get(pk=1)
self.check_object_permissions(self.request, obj)
return obj
def put(self, request, *args, **kwargs):
return self.patch(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
response = self.partial_update(request, *args, **kwargs)
return Response({
'results': response.data,
'messages': [
{
'message': _('Settings updated.'),
'message_type': 'status',
}
]
})
The serializer:
class SettingsSaveSerializer(SettingsSerializer):
stages = StageSerializer(
many=True,
read_only=True
)
class Meta:
model = Settings
fields = '__all__'
def to_representation(self, instance):
results = super().to_representation(instance)
return render_stages(results)
...validations omitted...
...create method omitted...
def update(self, instance, validated_data):
stages = get_initial_data_field(self.initial_data, 'stages', many=True)
if stages is not None: # Only process "stages" if it's present in form data
validated_data['stages_order'] = Stage.objects.save_stages(instance, stages)
return super().update(instance, validated_data)

Save multiple models in a single post - Django rest frameweok

I have 4 models
class User(AbstractEmailUser):
first_name = models.CharField(max_length=100, blank=True)
last_name = models.CharField(max_length=100, blank=True)
class Event(models.Model):
name = models.CharField(max_length=200)
address = models.CharField(max_length=200)
date = models.DateField()
class EventLocation(models.Model):
event = models.ForeignKey(Event)
ubigeo = ArrayField(models.CharField(max_length=200), blank=True)
class EventStaff(models.Model):
recycler = models.ForeignKey(User)
event = models.ForeignKey(Event)
When I want to register an event and be able to assign users to this same publication at the time of creation, assign users or do not assign them. I have already created a nested serialier that in the documentation is well explained so that the event is saved and at the same time it is saved in the ubigeo field of the EventLocation table (code of the district of the place):
Class EventLocationSerializer(serializers.ModelSerializer):
class Meta:
model = EventLocation
fields = ('id', 'ubigeo')
class EventSerializer(serializers.ModelSerializer):
event_location = EventLocationSerializer(required=True, write_only=True)
def to_representation(self, instance):
representation = super(EventSerializer, self).to_representation(instance)
event_location = EventLocation.objects.filter(event=instance.id)
if event_location:
representation['event_location'] = event_location.values('ubigeo')[0]
return representation
class Meta:
model = Event
fields = ('id', 'date', 'name', 'address', 'schedule', 'event_location')
def create(self, validated_data):
location_data = validated_data.pop('event_location')
event = Event.objects.create(**validated_data)
EventLocation.objects.create(event=event, **location_data)
return event
and it works correctly, but how would you add the users you want to assign to the event at the same time? I know I have to save them in the EventStaff table but how do I insert them in that same post?
This is my viewset:
#transaction.atomic
def create(self, request, *args, **kwargs):
with transaction.atomic():
try:
data = request.data
serializer = EventSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({"status": True, "results": "Evento registrado correctamente"},
status=status.HTTP_201_CREATED)
except ValidationError as err:
return Response({"status": False, "error_description": err.detail}, status=status.HTTP_400_BAD_REQUEST)
This is the json format:
{
"date": "2018-03-01",
"name": "La prueba reciclaje",
"address": "Av espaƱa trujillo",
"users": [
{"id": 40, "first_name": "Raul"},
{"id": 23, "first_name": "ALejandro"}
],
"eventlocation": {
"ubigeo": ["130101"]
}
}
In my opinion, we can custom your def create a bit more.
So we create one Serializer for User, get params user and save it after Event saved.
Maybe like this:
#transaction.atomic
def create(self, request, *args, **kwargs):
with transaction.atomic():
try:
data = request.data
serializer = EventSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
// recheck , this loop have input is all users in json
for user in data.get('users'):
user_serializer = UserSerializer(data=user)
if user_serializer.is_valid(raise_exception=True):
user_serializer.save()
return Response({"status": True, "results": "Evento registrado correctamente"},
status=status.HTTP_201_CREATED)
except ValidationError as err:
return Response({"status": False, "error_description": err.detail}, status=status.HTTP_400_BAD_REQUEST)
Hoop this help
As I said in the commentary, it works wonderfully :D
#transaction.atomic
def create(self, request, *args, **kwargs):
with transaction.atomic():
try:
data = request.data
users = request.data.get('users', None)
serializer = EventSerializer(data=data)
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
if users:
for user in users:
EventStaff.objects.create(recycler_id=user['id'], event_id=instance.id)
return Response({"status": True, "results": "Evento registrado correctamente"},
status=status.HTTP_201_CREATED)
except ValidationError as err:
return Response({"status": False, "error_description": err.detail}, status=status.HTTP_400_BAD_REQUEST)

Return user_id as response of post api

I want to return user_id for each object created using post api.
I want output in form of
[
{"info": {
"status": "SUCCESS",
"message": "User Details Successfully Uploaded"
},
"user_id": 10001
}
]
Here is my Code
Serializer.py
class UserDetailsSerializer(serializers.ModelSerializer):
"""docstring for UserDetailsSerializer"""
class Meta:
model = UserDetails
fields = ['user_id', 'user_email', 'user_full_name', 'user_token', 'patient_id', 'user_preferences']
read_only_fields = ['user_id']
Views.py
class UserDetailsViewSet(APIView):
def get(self, request):
queryset= UserDetails.objects.all()
serializer_class=UserDetailsSerializer(queryset,many=True)
return Response(serializer_class.data)
def post(self, request):
serializer_class_post=UserDetailsSerializer(data=request.data)
if serializer_class_post.is_valid():
try:
serializer_class_post.save()
return Response([{"info": {
"status": "SUCCESS",
"message": "User Details Successfully Uploaded"
},
"user_id": serializer_class_post.data
}], status=status.HTTP_201_CREATED)
except IntegrityError as e:
return Response([{"info": {
"status": "Error",
"message": "Error Uploading User Details"
}
}], status=status.HTTP_400_BAD_REQUEST)
return Response(serializer_class_post.errors, status=status.HTTP_400_BAD_REQUEST)`
models.py
class UserDetails(models.Model):
user_id = models.IntegerField(primary_key= True)
user_email = models.CharField(max_length=254)
user_full_name = models.CharField(max_length=200)
user_token = models.CharField(max_length=200)
patient_id = models.CharField(max_length=12, unique=True)
user_preferences = models.CharField(max_length=200)
class Meta:
managed = False
db_table = 'walnut_users'
def __str__(self):
return self.user_id
Change your post to the following:
def post(self, request):
serializer_class_post=UserDetailsSerializer(data=request.data)
if serializer_class_post.is_valid():
try:
obj = serializer_class_post.save()
return Response([{"info": {
"status": "SUCCESS",
"message": "User Details Successfully Uploaded"
},
"user_id": obj.pk
}], status=status.HTTP_201_CREATED)
except IntegrityError as e:
return Response([{"info": {
"status": "Error",
"message": "Error Uploading User Details"
}
}], status=status.HTTP_400_BAD_REQUEST)
return Response(serializer_class_post.errors, status=status.HTTP_400_BAD_REQUEST)`
serializer.save() returns the newly created object; just use that and get the pk in your response.
Also, API best practice is to return the full object representation; not the ID alone. Ideally I would do what you have done: {"data": serializer_post_data.data} -> this will return the full nested representation. You should still be able to access the user_id as data.user_id. However, if you are sure about your usecase, the code above will work.
Since user_id is an IntegerField and the primary key, then Django expects you to supply the value for the field. Where are you assigning value to user_id - does your API also post the value for user_id. If you want to auto-assign values, change it to user_id = models.AutoField(primary_key=True)