Getting and saving value from POST with nested serializer - django

class InfoSerializer(serializers.ModelSerializer):
class Meta:
model = EventInfo
fields = ('email', 'pin')
class EventSerializer(DataSerializer, GeoModelAPIView):
# other fields
event_info = InfoSerializer(read_only=True)
def create(self, validated_data):
event_info = validated_data.pop('event_info', {})
event = super().create(validated_data)
EventInfo.objects.create(event=event, **event_info)
return event
Model
class EventInfo(models.Model):
pin = models.CharField(max_length=60, null=False, blank=False)
email = models.EmailField()
event = models.ForeignKey(Event)
POST
{
# other data
"event_info": {
"email": "example#example.com",
"pin": "1234567890"
}
}
So I have a model that is not visible on the browsable API, but I want to be able to save data from POST request to that model. Using this code I can create the objects and it correctly links the info to a correct Event model. However the email and pin fields won't get saved. What I have figured out is that the 'event_info' data from the POST is not visible on the validated_data.
The validation goes to the DataSerializer's validation method but I guess that I should somehow bypass the validation for just the 'event_info' data?
Edit:
class EventViewSet(BulkModelViewSet, JSONAPIViewSet):
queryset = Event.objects.filter(deleted=False)
queryset = queryset.select_related('location')
queryset = queryset.prefetch_related(list of related fields)
serializer_class = EventSerializer
filter_backends = (EventOrderingFilter, filters.DjangoFilterBackend)
filter_class = EventFilter
ordering_fields = (fields to order by)
ordering = ('-last_modified_time',)
def __init__(self, **kwargs):
super().__init__(**kwargs)
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
def get_serializer_context(self):
context = super(EventViewSet, self).get_serializer_context()
context.setdefault('skip_fields', set()).update(set([
'headline',
'secondary_headline']))
return context
#atomic
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
if isinstance(serializer.validated_data, list):
event_data_list = serializer.validated_data
else:
event_data_list = [serializer.validated_data]
super().perform_create(serializer)

Related

How we can use model objects in ModelSerializer?

I have to create an endpoint to make a customer subscribes to a chef.
The subscription model has two FK fields chef and customer.
model/subscriptionSerializer:
class Subscription(models.Model):
chef = models.ForeignKey(Chef, on_delete=models.CASCADE, db_column="chef")
customer = models.ForeignKey(User, on_delete=models.CASCADE, db_column="customer")
I created this serializer:
class SubscriptionSerializer(serializers.ModelSerializer):
chef = serializers.SerializerMethodField("get_chef")
customer = serializers.SerializerMethodField("get_customer")
class Meta:
model = Subscription
fields = [
"id",
"chef",
"customer",
]
def get_chef(self, *args):
return Chef.objects.get(pk=self.context.get("chef"))
def get_customer(self, *args):
return User.objects.get(pk=self.context.get("customer"))
I want to have one endpoint like this: customer/chef/<int:chef_id>/subscribe
So in view.py I used ModelViewSet:
class CustomerSubscribeChefView(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = SubscriptionSerializer
def get_serializer_context(self):
context = super(CustomerSubscribeChefView, self).get_serializer_context()
context.update({"chef": self.kwargs['chef_id']})
context.update({"customer": self.request.user.id})
return context
def get_queryset(self):
return self.request.user.subscription_set
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(
{"msg": "Chef is subscribed!"}, status=status.HTTP_201_CREATED
)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response({"msg": "Chef is unsubscribed!"}, status=status.HTTP_200_OK)
when I try to send a post request I got this error:
django.db.utils.IntegrityError: NOT NULL constraint failed: core subscription. chef _id
I've tried to use my custom serializer so I've updated SubscriptionSerializer to be like this:
class SubscriptionSerializer(serializers.ModelSerializer):
chef = ChefListSerializer
customer = CustomerSerializer
class Meta:
model = Subscription
fields = [
"id",
"chef",
"customer",
]
But I don't know how to serialize chef and customer objects.
What is the problem with my approach, and how I can fix this?
Update
I changed the subscription serializer to be like this
class SubscriptionSerializer(serializers.ModelSerializer):
chef = Chef()
customer = User()
def __init__(self, *args, **kwargs):
context = kwargs.pop('context')
self.chef = Chef.objects.get(pk=context.get("chef"))
self.customer = User.objects.get(pk=context.get("customer"))
super().__init__(*args, **kwargs)
class Meta:
model = Subscription
fields = ["id", "chef", "customer"]
But I still get the same error.

How to patch several objects at the same time in Django Rest Framework?

I'm creating an app in which notifications model exists. That is why I need a behavior like this: when a person requests the notification page, the field is_read which is boolean turns from default FALSE to TRUE. The trouble is that there could be many of objects, so how to set to TRUE all of them?
Model:
class Notification(models.Model):
is_read = models.BooleanField(default=False)
notification_from = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="notiffrom")
notification_to = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="notifto")
View:
class UserNotificationView(ListModelMixin, GenericAPIView, CreateModelMixin):
serializer_class = NotificationSerializer
def get_queryset(self):
notification_to = self.kwargs["notification_to"]
return Notification.objects.filter(notification_to=notification_to)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
I know that there are bulk requests in Django but struggle with making them with DRF. Maybe there are some other ways?
You could add the update function in the get_queryset function.
class UserNotificationView(ListModelMixin, GenericAPIView, CreateModelMixin):
serializer_class = NotificationSerializer
def get_queryset(self):
notification_to = self.kwargs["notification_to"]
queryset = Notification.objects.filter(notification_to=notification_to)
queryset.update(is_read = True)
return queryset
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

ListSerializer and Foreign Key, is_valid doing N+1 query

I'm trying to improve my serializer to be able to create multiple objects with minimum queries. So I did implement a ListSerializer that will bulk create objects instead of calling save on each objects.
Here my current code:
class GatewayTechnicalLogListSerializer(serializers.ListSerializer):
gateway = serializers.IntegerField(required=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gateways_ids: dict = {}
for gat_tech_log in self.initial_data:
self.gateways_ids[gat_tech_log['gateway']] = True
self.gateways_ids = Gateway.objects.filter(
id__in=self.gateways_ids.keys()
).only('id').values_list('id', flat=True)
def validate(self, attrs):
if attrs.gateway not in self.gateways_ids.keys():
raise serializers.ValidationError('Gateway does not exists.')
return attrs
def create(self, validated_data):
gw_tech_logs_o = [GatewayTechnicalLog(**item) for item in validated_data]
res = GatewayTechnicalLog.objects.bulk_create(gw_tech_logs_o)
return res
class GatewayTechnicalLogSerializer(serializers.ModelSerializer):
class Meta:
model = GatewayTechnicalLog
fields = '__all__'
list_serializer_class = GatewayTechnicalLogListSerializer
My problem is now that when the method is_valid is called, it is trying to validate the foreign key gateway for each objects and so fetching the foreign key related.
I'm trying then to remove the validation on that field and validating it myself but it doesn't change anything...
I have not found any example of this, any ideas ?
Thanks !
Ok, so I ended up doing, I'm not sure if this is the best approach but it seems to work:
class GatewayTechnicalLogListSerializer(serializers.ListSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gateways_ids: dict = {}
for gw_tech_log in self.initial_data:
self.gateways_ids[gw_tech_log['gateway']] = True
self.gateways_o = Gateway.objects.filter(
id__in=self.gateways_ids.keys()
)
self.gateways_ids = list(self.gateways_o.values_list('id', flat=True))
def validate(self, attrs):
# Validating because validation was removed
for gw_tech_log in attrs:
if gw_tech_log['gateway'] not in self.gateways_ids:
raise serializers.ValidationError('Gateway does not exists.')
return attrs
def create(self, validated_data):
# Bulk creating and logging after into Azure to improve performance
gw_tech_logs_o: list = []
for item in validated_data:
gateway_id: int = item.pop('gateway')
item['gateway'] = next(
gateway_o for gateway_o in self.gateways_o if gateway_o.id == gateway_id
)
gw_tech_logs_o.append(GatewayTechnicalLog(**item))
res = GatewayTechnicalLog.objects.bulk_create(gw_tech_logs_o)
return res
class GatewayTechnicalLogSerializer(serializers.ModelSerializer):
class Meta:
model = GatewayTechnicalLog
fields = '__all__'
list_serializer_class = GatewayTechnicalLogListSerializer
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Removing validation on serializer for gateway field
self.fields['gateway'] = serializers.IntegerField(required=True)
def to_representation(self, obj):
# Rolling back changes on field for representation
self.fields['gateway'] = serializers.PrimaryKeyRelatedField(required=True, queryset=obj.gateway)
return super(GatewayTechnicalLogSerializer, self).to_representation(obj)

Return different serializer after create() in CreateAPIView in Django REST Framework

I'm using Django 2.2 and Django REST Framework.
I have to serializers for the same model.
class OrderListSerializer(serializers.ModelSerializer):
plan = PlanBaseSerializer(read_only=True, many=False)
class Meta:
model = Order
fields = [
'id', 'name', 'plan', 'pricing',
'created', 'completed',
]
class OrderCreateSerializer(serializers.ModelSerializer):
plan_pricing = serializers.IntegerField(required=True, write_only=True)
class Meta:
model = Order
fields = [
'plan_pricing'
]
def create(self, validated_data):
plan_pricing_ = validated_data.pop('plan_pricing', None)
try:
plan_pricing = PlanPricing.objects.get(pk=plan_pricing_)
except PlanPricing.DoesNotExists:
raise ValidationError('Plan pricing not available')
validated_data['plan'] = plan_pricing.plan
validated_data['amount'] = plan_pricing.price
return super().create(validated_data)
OrderListSerializer serializer is used for listing orders or order detail view and OrderCreateSerializer is used for creating a new order instance.
The view is
class CreateOrderView(generics.CreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
This is working fine as the order object is creating as expected. But the returned value contains no data.
I want to use OrderListSerializer to render saved order details after creating the order.
How to change the serializer class after creating the object?
Also, I have to trigger a signal after the object has been successfully created. What is the best place to trigger a signal?
Change CreateOrderView as below,
class CreateOrderView(generics.CreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer)
instance_serializer = OrderListSerializer(instance)
return Response(instance_serializer.data)
serializer.save() returns the instance that just created or updated. So we use that istance to pass to the OrderListSerializer and returning the corresponding response.
You could overwrite create(), and return whatever you want:
from rest_framework import response, status
(...)
def create(self, request, *args, **kwargs):
super().create(request, *args, **kwargs)
return response.Response(status=status.HTTP_201_CREATED)
(...)
There are several ways you can use here. First,
class CreateOrderView(generics.ListCreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def list(self, *args, **kwargs):
serializer_class = OrderListSerializer
serializer = serializer_class(self.get_queryset())
return Response(serializer.data)
The alternative would be a conditional if statement, where
if self.request.method=='POST':
self.serializer_class = OrderCreateSerializer
elif self.request.method=='GET':
self.serializer_class = OrderListSerializer

AttributeError - object has no attribute 'create'

I'm trying to save a through model which has the following attributes via Django-rest-framework
when sending a POST (I'm trying to create a new instance), I get the following error:
AttributeError at /api/organisation/provider/
'EnabledExternalProvider' object has no attribute 'create'
any ideas as to what i'm doing incorrectly?
the through model in question is:
class EnabledExternalProvider(models.Model):
provider = models.ForeignKey(ExternalProvider, related_name='associatedProvider')
organisation = models.ForeignKey(Organisation, related_name='associatedOrg')
enabled = models.BooleanField(default=True)
tenantIdentifier = models.CharField('Tenant identifer for organisation', max_length = 128, null=True, blank=True)
def __str__(self):
return self.provider + '-' + self.organisation
my view is:
class EnabledExternalProvider(mixins.RetrieveModelMixin, mixins.UpdateModelMixin,generics.GenericAPIView):
serializer_class = ConnectedServiceSerializer
def get_queryset(self):
return EnabledExternalProvider.objects.filter(organisation=self.request.user.organisation_id)
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
# make sure to catch 404's below
obj = queryset.get(organisation=self.request.user.organisation_id)
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
and my serializer is:
class ConnectedServiceSerializer(serializers.ModelSerializer):
provider=ExternalProviderSerializer(read_only=True)
organisation=OrganisationDetailSerializer(read_only=True)
class Meta:
model = EnabledExternalProvider
fields = ( 'organisation', 'enabled', 'tenantIdentifier')
read_only_fields = ('organisation', 'provider')
I'm POSTing the following:
{"provider":"1","tenantIdentifier":"9f0e40fe-3d6d-4172-9015-4298684a9ad2","enabled":true}
Your view doesn't have that method because you haven't defined it, or inherited from a class that has it; your mixins provide retrieve and update, but not create.
You could add mixins.CreateModelMixin to the inheritance, but at this point you should really be using a ViewSet instead.