Convert URL of an object to its database instance - python

I created an extra function in my View that receives a list with hyperlinked references to some ResourceGroup objects, but I don't know how to convert them to database instances
class ResourceViewSet(viewsets.ModelViewSet):
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
#action(methods=['put'], detail=True)
def groups_append(self, request, pk=None):
instance = self.get_object()
groups = request.data.get("groups")
for resource_group in groups:
instance.groups.add(WHAT_HERE(resource_group))
instance.save()
return Response(self.get_serializer(instance, many=False).data)
This is the request:
PUT http://.../api/resources/1/groups_append/
with body:
{"groups": ["http://.../api/resource_groups/1/", ...]}
ResourceSerializer:
class ResourceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Resource
fields = ('resource_id', 'object_id', 'type', 'system', 'path', 'groups', 'job_set')
def update(self, instance, validated_data):
instance.object_id = validated_data.get('object_id', instance.object_id)
instance.type = validated_data.get('type', instance.type)
instance.system = validated_data.get('system', instance.system)
instance.path = validated_data.get('path', instance.path)
instance.save()
return instance
ResourceGroupSerializer:
class ResourceGroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ResourceGroup
fields = ('resource_group_id', 'label', 'resource_set')
def update(self, instance, validated_data):
instance.label = validated_data.get('label', instance.label)
instance.save()
return instance

use HyperlinkedRelatedField for groups in ResourceSerializer or just create a new serializer for this action(the main idea is to get the data using a serializers not just directly from the request body) like this:
class ResourceSerializer(serializers.HyperlinkedModelSerializer):
groups = serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='groups-detail' ## name of the groups detail url
)
class Meta:
model = Resource
....
then edit your action as below:
#action(methods=['put'], detail=True)
def groups_append(self, request, pk=None):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
### then get the groups from the validated data
groups = serializer.validated_data.get('groups', [])
....
....
References:
1- hyperlinkedrelatedfield

Related

How to serialize ManyToManyField

I want to serialize ManyToManyField but at the same time, I am looking for something which updates the same using ModelViewSet. I am able to serialize it but when I am updating it I am not able to. I know I can make a separate API for that but due to some requirements, I need to stick to one endpoint. Here is my code
class ComponentSerializers(serializers.ModelSerializer):
class Meta:
model = coreModel.Component
fields = '__all__'
class MonitorSerializers(serializers.ModelSerializer):
device = ComponentSerializers(read_only=True, many=True)
class Meta:
model = models.Monitor
fields = '__all__'
read_only_fields = ('id', 'created_at', 'updated_at',)
and views.py is
class MonitorViewSet(viewsets.ModelViewSet):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
queryset = models.Monitor.objects.all()
filter_backends = (DjangoFilterBackend,OrderingFilter,SearchFilter)
filter_class = superFilter.MonitorFilters
serializer_class = serializers.MonitorSerializers
If you want update ManytoMany or Related objects
Override
def update(self, validated_data):
in MonitorSerializers
class MonitorSerializers(serializers.ModelSerializer):
device = ComponentSerializers(read_only=True, many=True)
device_ids = serializers.ListField(write_only=True,
child = serializers.IntegerField(min_value = 1))
....
def update(self, instance, validated_data):
# Remove component data and save in variable
iscomponentdataexists = 'device_ids' in validated_data
if iscomponentdataexists :
componentdata= validated_data.pop('device_ids')
instance = super().update(instance, validated_data) # Update Monitor Data
# looping through new device_ids list
if iscomponentdataexists :
for deviceid in componentdata:
try:
obj = coreModel.Component.objects.get(id=deviceid)
instance.devices.add(obj)
except coreModel.Component.DoesNotExist:
pass
instance.save()
return instance
Remove read_only=True from device

DRF serializer doesn't save instance to the db

Views
class AuthDataViewSet(ModelViewSet):
queryset = AuthData.objects.all()
serializer_class = AuthDataSerializer
def create(self, request, *args, **kwargs):
serializer_data, headers = create_auth_data(self, request.data, {'request': request})
# returning response with the data
create_auth_data function
def create_response_data(view, data: dict = None, context: dict = None):
# I calling the viewset methods below
serializer = view.get_serializer(data=data, context=context)
serializer.is_valid(raise_exception=True)
view.perform_create(serializer)
headers = view.get_success_headers(serializer.data)
return serializer.data, headers
Serializer
class AuthDataSerializer(ModelSerializer):
class Meta:
model = AuthData
fields = ('login', 'password', 'project', 'manager')
def create(self, validated_data):
validated_data['manager'] = self.context['request'].user.manager
return validated_data
I got the correct serializer.data, no errors and pure data, but the instance didn't saved to the database.
Give this a try:
class AuthDataSerializer(ModelSerializer):
class Meta:
model = AuthData
fields = ('login', 'password', 'project', 'manager')
def create(self, validated_data):
auth_data = AuthData.objects.create(**validated_data)
return auth_data

Original exception text was: 'QuerySet' object has no attribute 'client'

I got AttributeError when attempting to get a value for field client on serializer ClientSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the QuerySet instance.
models.py
class Box(models.Model):
box = models.IntegerField()
controller = models.ForeignKey(Controller, related_name='boxes', on_delete=models.CASCADE)
def __str__(self):
return str(self.box)
class Client(models.Model):
client = models.CharField(max_length=30)
cpf = models.IntegerField()
box = models.OneToOneField(
Box,
on_delete=models.CASCADE,
primary_key=True
)
def __str__(self):
return self.client
serializers.py
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = [
"id",
"client",
"cpf",
"box",
]
class BoxSerializer(serializers.ModelSerializer):
class Meta:
model = Box
fields = [
"id",
"box",
"controller"
]
views.py
class ClientViewSet(viewsets.ModelViewSet):
serializer_class = ClientSerializer
queryset = Client.objects.all()
def list(self, request, store_pk=None, locker_pk=None, controller_pk=None, box_pk=None):
queryset = Client.objects.filter(box=box_pk, box__controller=controller_pk, box__controller__locker=locker_pk, box__controller__locker__store=store_pk)
serializer = ClientSerializer(queryset, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, store_pk=None, locker_pk=None, controller_pk=None, box_pk=None):
queryset = Client.objects.filter(box=box_pk, box__controller=controller_pk, box__controller__locker=locker_pk, box__controller__locker__store=store_pk)
client = get_object_or_404(queryset)
serializer = ClientSerializer(client, context={'request': request})
return Response(serializer.data)
I'm trying to get the object client on lockers/1/controllers/1/boxes/1/client/
which is OneToOneField relations with boxes and It's in a nested router
I already tried use decorator #action but yet didn't work.
Anyone know why it's not finding the correct object attribute ?
For a list method you should use many=True parameter when you're creating a new serializer instance:
serializer = ClientSerializer(queryset, context={'request': request}, many=True)
In case of retrieve only one object should be received. Instead of
client = get_object_or_404(queryset)
you should call first(), last() (or most basically and clearly - .get(pk=pk)) on queryset to retrieve only one item from QuerySet. Then you should just execute:
# client is one of elements of your queryset
serializer = ClientSerializer(client, context={'request': request})

How to set user_id field using JWT in DRF

I want to set user_id field using JWT token and store in database table when new reservation is created. there can be single or multiple reservation request.
whenever user create reservation i want to store there user_id in our table. currently there is no foreign key associated with it. it is simply an integer field.
I am able to fetch user_id from JWT.but its not updating in database
I know this question had been asked previously i tried all the answer of previous post but its not working for me. i dont know why
model.py
class reservations(models.Model):
pet_id=models.IntegerField()
user_id=models.IntegerField(default=0)
location=models.PositiveSmallIntegerField()
arrival=models.DateTimeField()
depature=models.DateTimeField()
comments=models.TextField(max_length=200)
view.py
class requestReservation(CreateAPIView):
serializer_class = requestReservationSerailizer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
serializer = requestReservationSerailizer(data=request.data,context={'user_id': request.user.id}, many=True)
if not serializer.is_valid(raise_exception=False):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({"message":"Success","status_message":"Reservation Created Successfully"},status=status.HTTP_200_OK)
serializer.py
class requestReservationSerailizer(ModelSerializer):
user_id = SerializerMethodField('set_user_id')
class Meta:
model=reservations
fields = [
'pet_id',
'user_id',
'location',
'arrival',
'depature',
'comments',
]
def set_user_id(self, obj):
obj.user_id = self.context.get("user_id")
return obj.user_id
currently it is simply storing user_id as 0 which is default set in model.
SerializerMethodField is read-only by default, here's a quick look at the source code:
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
kwargs['source'] = '*'
kwargs['read_only'] = True
super(SerializerMethodField, self).__init__(**kwargs)
Assuming you want to read and write into this field; remove the SerializerMethodField overriding from the serializer declaration; and set the user_id in your view
class requestReservationSerailizer(ModelSerializer):
class Meta:
model=reservations
fields = [
'pet_id',
'user_id',
'location',
'arrival',
'depature',
'comments',
]
def create(self, request, *args, **kwargs):
data = request.data.copy()
for datum in data:
datum['user_id'] = request.user.id
serializer = requestReservationSerailizer(data=data, many=True)
if not serializer.is_valid(raise_exception=False):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({"message":"Success","status_message":"Reservation Created Successfully"},status=status.HTTP_200_OK)
Ofcourse; if you don't want your view to be doing this (and I'd agree with you here), then pass it in context (explicit) or use self.request.user.id and override validate_user_id
class requestReservationSerailizer(ModelSerializer):
class Meta:
model=reservations
fields = [
'pet_id',
'user_id',
'location',
'arrival',
'depature',
'comments',
]
def validate_user_id(self, value):
user_id = self.context.get('user_id', None) # Assuming you continue to pass it in context
if user_id is None:
# Handle error
return user_id
# You can also do this; might raise an AttributeError if the user is not authenticated:
# return self.request.user.id

How to update multiple records at once (bulk update) in django API

I need to update categories in many Article in one request.
In ArticleViewSet I have:
def get_serializer_class(self):
if self.action in ['partial_update', 'update']:
return ArticlePostSerializer
return ArticleSerializer
So ArticlePostSerializer need to be changed.
This is my serializers code:
class ArticleShortCategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = 'id', 'name'
class ArticleSerializer(serializers.ModelSerializer):
categories = serializers.SerializerMethodField()
def get_categories(self, obj):
return ArticleShortCategorySerializer(obj.categories, many=True).data
class Meta:
model = Article
read_only_fields = 'id'
fields = ('categories', 'text') + read_only_fields
class ArticlePostSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = 'id', 'categories', 'text'
I tried to add:
class ArticlePostListSerializer(serializers.ListSerializer):
and
class Meta:
list_serializer_class = ArticlePostListSerializer
But it doen't work.
How to change this code to do multiple update.
My json request
{
[id: 90, categories: [10,12,14]],
[id: 93, categories: [10,12,14]],
[id: 95, categories: [10,12,14]]
}
Here is sample of CreateMixins OR UpdateMixins you requested.
======================= VIEW ================================
class OrderCreate(mixins.CreateModelMixin,viewsets.GenericViewSet):
pagination_class = None
def get_queryset(self):
return []
def get_serializer_class(self):
return serializers.OrderSerializer
======================= Serializer =============================
class OrderDetailSerializer(serializers.ModelSerializer):
class Meta:
model = crm_models.OrderDetail
fields = (
'product',
'quantity',
'rate_per_unit',
'order_quantity'
)
class OrderSerializer(serializers.ModelSerializer):
order_details = OrderDetailSerializer(many = True)
class Meta:
model = crm_models.OrderMaster
fields = (
'order',
'invoice_number',
'client',
'beat_check',
'target_customer',
'order_editor_client_employee',
'order_marked',
'order_saved',
'edit_marked',
'edit_saved',
'adhoc',
'order_details'
)
def create(self, validated_data,*args,**kwargs):
ordersdetails_data = validated_data.pop('order_details')
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
validated_data['client'] = user.client
validated_data['order_editor_client_employee'] = user
validated_data['adhoc'] = validated_data['adhoc'] if 'adhoc' in validated_data else False
orderObj = super(OrderSerializer, self).create(validated_data,*args,**kwargs)
orderdetails = []
for details in ordersdetails_data:
orderdetails.append(crm_models.OrderDetail(
product= details['product'],
quantity = details['quantity'],
rate_per_unit = details['rate_per_unit'],
order_quantity = details['order_quantity'],
order = orderObj
))
crm_models.OrderDetail.objects.bulk_create(orderdetails)
return orderObj
In Update view function name would be changed to update, you can find more documentation http://www.django-rest-framework.org/api-guide/generic-views/#createmodelmixin
I found K. Moe's answer to this question: Django Rest Framework POST Update if existing or create much easier to understand and implement. You only need to add a create method in the serializer and use mixins.CreateModelMixin, generics.GenericAPIView in the view. Then you can use a POST request, instead of PUT/PATCH. It allows to create AND update data stored in your database. My code for the view:
class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):
def post(self, request, format=None):
is_many = isinstance(request.data, list)
if is_many:
serializer = ZipCodeSerializer(data=request.data, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
serializer = ZipCodeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#Greg Holst, why so much duplication? Why not:
class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):
def post(self, request, format=None):
serializer = ZipCodeSerializer(data=request.data, many=isinstance(request.data, list))
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Anyway, that only worked for me for creating new objects, didn't work to create-or-update in one sweep (it kept telling me these objects already exist), so this is what I did:
class ContributorSyncListAPIView(ListAPIView):
permission_classes = (isSuperUserPermission,)
allowed_methods = ("GET", "PUT")
lookup_field = "airtable_id"
serializer_class = ContributorSyncSerializer # Doesn't get used in Put, just in Get.
model = Contributor
def get_queryset(self):
return self.model.objects.all()
def put(self, request, format=None):
objects = list(request.data)
# objects is a list of OrderedDicts
try:
for o in objects:
try:
instance = self.model.objects.get(
**{self.lookup_field: o.get(self.lookup_field)}
)
for key, value in o.items():
setattr(instance, key, value)
except self.model.DoesNotExist:
instance = self.model(**o)
instance.save()
return Response(objects, status=status.HTTP_200_OK)
except Exception as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
Note my code above is very light on validation just because it was for a process of syncing models from one environment to another by a superuser, different environments with an identical codebase; so the assumption was the data had already been validated when the data was entered into the first environment. For any other purpose you'd want to validate more. But this is what I had to do to handle a list of objects that may need to be created or updating, on an object-by-object basis.

Categories