How we can use model objects in ModelSerializer? - django

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.

Related

Update field without rest API

I have a model.py
class UserPaymentInformation(models.Model):
...
awaiting_confirmation = models.BooleanField(default=False)
I want to make awaiting_confirmation = True in code. But forbid awaiting_confirmation update via RestAPI call.
views.py
class UserPaymentInformationUpdateAPIView(generics.UpdateAPIView):
permission_classes = (IsAuthenticatedDriver,)
serializer_class = UserPaymentInformationUpdateSerializer
queryset = serializer_class.Meta.model.objects.all()
def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
instance = self.serializer_class.Meta.model.objects.get(
user=self.request.user
)
self.mark_user_as_new()
# awaiting_confirmation = True # I WANT SOMETHING LIKE THIS
serializer = self.get_serializer(
instance, data=request.data, partial=partial
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({"result": serializer.data})
serializers.py
class UserPaymentInformationUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = UserPaymentInformation
fields = ("id", "full_name", "card_number", "account_number", "bik", "awaiting_confirmation")
How can I fix update method?
In your UserPaymentInformationUpdateSerializer you could set the read_only_fields = ('awaiting_confirmation',), so your serializer would become:
class UserPaymentInformationUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = UserPaymentInformation
fields = ("id", "full_name", "card_number", "account_number", "bik", "awaiting_confirmation")
read_only_fields = ("awaiting_confirmation",)
This would mean it'd still be returned in the serializer data but it would not be possible to update it through an API request.

Changing Return Type on Creation in Serializer

I have the following two serializers:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
from radio.models import Program
model = Program
fields = ('id', 'title')
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program', 'program_data',)
They are based on the following models:
class Program(models.Model):
title = models.CharField(max_length=64)
class UserRecentlyPlayed(models.Model):
user = models.ForeignKey(User)
program = models.ForeignKey(Program)
What I'm trying to do is the following: On create, I want to be able create a new instance of UserRecentlyPlayed in the following manner:
{
"user": "...user id ....",
"program": "....program id...."
}
However, when I return a list, I would like to return the following:
[{
"id": "... id .... ",
"user": ".... user id .....",
"program": {"id": "...program id...", "title": "...title..." }
}]
These are called in the following view:
class RecentlyPlayed(generics.ListCreateAPIView):
serializer_class = UserRecentlyPlayedSerializer
This, unfortunately is not working. What is the correct magic for this?
You can rename your program_data in your serializer to program or you can specify source for your nested serializer.
That should return the output of list as you'd like.
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program',)
or
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True, source='program')
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program_data',)
And to support same json input for create, the easiest way is create another serializer for input:
class UserRecentlyPlayedSerializerInput(serializers.ModelSerializer):
program = serializers.PrimaryKeyRelatedField(queryset=Program.objects.all())
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program',)
And use it in your view when request is POST/PUT/PATCH:
class RecentlyPlayed(generics.ListCreateAPIView):
serializer_class = UserRecentlyPlayedSerializer
def get_serializer_class(self):
if self.request.method.lower() == 'get':
return self.serializer_class
return UserRecentlyPlayedSerializerInput
While this works great for a "get", I would like to see that same
result after a create. I still see {"program": "...id...."
For this, you have to change slightly the implementation of create method in your view
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
headers = self.get_success_headers(serializer.data)
oser = UserRecentlyPlayedSerializer(instance)
return Response(oser.data, status=status.HTTP_201_CREATED,
headers=headers)
Firstly create a property named program_data in your model
class Program(models.Model):
title = models.CharField(max_length=64)
class UserRecentlyPlayed(models.Model):
user = models.ForeignKey(User)
program = models.ForeignKey(Program)
#property
def program_data(self):
return self.program
Then in your serializer you do not need to change anything following, it will remain same as below
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
from radio.models import Program
model = Program
fields = ('id', 'title')
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program', 'program_data',)
Ok, I went in a slightly different direction and it works. Instead of using the ListCreateAPIView, I created my own class using ListModeMixin, CreateModelMixin and GenericAPIView. The magic was in overriding the def list class. I also implemented a "return_serializer_class" attribute. That's what did it.
class RecentlyPlayed(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
serializer_class = UserRecentlyPlayedSerializer
return_serializer_class = ProgramSerializer
parser_classes = (JSONParser, MultiPartParser)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.create(request, *args, **kwargs)
return self.list(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.return_serializer_class(queryset, many=True)
return Response({'recently_played': serializer.data})

Check a field in model in generics.RetrieveDestroyAPIView

I am writing a rest API. this is my view:
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
queryset = Order.objects.all()
serializer_class = OrderDeleteSerializer
# permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
and this is its model:
class Order(models.Model):
product = models.ForeignKey(Product)
customer = models.ForeignKey(Customer, null=True)WAITING = 'WA'
PREPARATION = 'PR'
READY = 'RD'
DELIVERED = 'DV'
STATUS_CHOICES = (
(WAITING, 'waiting'),
(PREPARATION, 'preparation'),
(READY, 'ready'),
(DELIVERED, 'delivered'),
)
status = models.CharField(
max_length=2,
choices=STATUS_CHOICES,
default=WAITING,
)
and:
class Customer(models.Model):
name = models.CharField(max_length=40)
customer_email = models.EmailField()
def __str__(self):
return self.name
and this is its serializer:
class OrderDeleteSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
What should I do if I want the object(order) can be deleted, only when the status field is 'waiting' ?
You can implement some checks, in the destroy and return an error message as response in case the constraints are not met:
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
queryset = Order.objects.all()
serializer_class = OrderDeleteSerializer
# permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.status != Order.WAITING:
return JsonResponse(
status=412,
data={'status':'false',
'message': 'status should be WAITING'}
)
super(OrderDeleteAPIView, self).destroy(request, *args, **kwargs)
Of course you can return any sort of answer (not per se a JSON response, nor does the status has to be 412). Usually HTTP status code 412 means that 412 Precondion failed.
The question is not about permissions, but it is a close situation.
If you want to check permissions, there is a way to do this via overridden check_permissions:
from functools import lru_cache
from rest_framework.exceptions import PermissionDenied
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
serializer_class = OrderDeleteSerializer
permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
#lru_cache
def get_object(self, *args, **kwargs):
return Order.objects.get(self.kwargs['order_id'])
def check_permissions(self, request):
# this method will call OwnerCanManageOrReadOnly first
super().check_permissions(request)
# and then do other checks
instance = self.get_object()
if instance.owner != request.user:
raise PermissionDenied()
As you can notice, I use #lru_cache to cache get_object. The reason to use it is because after check_permissions there will be another method (in this case retrieve or destroy) which will call this method again. To reduce database requests count I use caching.

How to update foreign key of field from another model

i am trying to update field in another model by serializer.py but i get this error:
duplicate key value violates unique constraint "sales_company_company_name_8f098bd8_uniq"
DETAIL: Key (company_name)=(fdsfasf) already exists.
serializer.py code:
Company model is a FK in Customer model
class CompanySerializer(serializers.ModelSerializer):
model = models.Company
fields = ('company_name',)
class CustomersIsCompanySerializer(serializers.ModelSerializer):
company = CompanySerializer
company_name = serializers.CharField(max_length=255)
def update(self, instance, validated_data):
instance.company_name = models.Company.objects.update(company_name=validated_data.get('company_name', instance.company_name))
instance.phone = validated_data.get('phone', instance.phone)
instance.mobile = validated_data.get('mobile', instance.mobile)
instance.email = validated_data.get('email', instance.email)
instance.website = validated_data.get('website', instance.website)
instance.save()
return instance
class Meta:
model = models.Customers
fields = (
'id',
'company_name',
'phone',
'mobile',
'email',
'website',
)
extra_kwargs = {
'phone': {'validators': []},
'mobile': {'validators': []},
'email': {'validators': []},
'website': {'validators': []},
}
and my view code :
class CustomerIsCompanyGetIdPutPatchView(generics.RetrieveAPIView,
mixins.UpdateModelMixin):
permission_classes = (AllowAny,)
queryset = models.Customers.objects.all()
serializer_class = CustomersIsCompanySerializer
def get_object(self):
id = self.kwargs['id']
obj = generics.get_object_or_404(User, id=id)
return obj
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
but if i replace update and but create it work fine but it make a new line in the DB not update the old line.
for example if i replace that code :
instance.company_name = models.Company.objects.update(company_name=validated_data.get('company_name', instance.company_name))
by this code :
instance.company_name = models.Company.objects.create(company_name=validated_data.get('company_name', instance.company_name))
it work fine but not updated the old , it create new
just i want update the old in the company model
i found the solution for this problem this is what i want:
instance.company_name, created = models.Company.objects.get_or_create(company_name=validated_data.get('company_name', instance.company_name))
i used here created and get_or_create to see if it exist before made it directly PUT and edit The FK of the exist Company Name OR If it not exist create new one and PUT the FK of the new company name

Getting and saving value from POST with nested serializer

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)