Update ManytoMany relationship in Django Rest Framework - python

In my django application I have a ManytoMany relationship between Orders and Packages. An order can have multiple packages. I want to know about the update and create methods
Models.py
class Package(models.Model):
prod_name = models.CharField(max_length=255, default=0)
quantity = models.IntegerField(default=0)
unit_price = models.IntegerField(default=0)
class Orders(models.Model):
order_id = models.CharField(max_length=255, default=0)
package = models.ManyToManyField(Package)
is_cod = models.BooleanField(default=False)
Serializers.py
class PackageSerializer(serializers.ModelSerializer):
class Meta:
model = Package
fields = "__all__"
class OrderSerializer(serializers.ModelSerializer):
package = PackageSerializer(many=True)
class Meta:
model = Orders
fields = "__all__"
Views.py
class OrdersCreateAPIView(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = OrderSerializer
def post(self, request):
serializer = OrderSerializer(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)
Is that sufficient to handle the related data? I am trying to understand ManytoMany relationship both in Django as well as DRF so please explain if I need to change the Models or views in anyway
Update:
I have updated my serializer as well as view in order to create manytomany related objectslike this:
class OrderSerializer(serializers.ModelSerializer):
package = PackageSerializer(many=True)
class Meta:
model = Orders
fields = "__all__"
def create(self, validated_data):
package_data = validated_data.pop('package')
pkgs = []
order = Orders.objects.create(**validated_data)
for i in package_data:
try:
p = Package.objects.create(**i)
pkgs.append(p)
except:
pass
order.package.set(pkgs)
return order
Views.py
class OrdersCreateAPIView(CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = OrderSerializer
def perform_create(self,serializer):
serializer.save(owner=self.request.user)
However I am still unclear about overriding the update method of RetrieveUpdateDestroyAPIView. Also, Is the above method is the right method to store M2M related objects ?
Please help with the update part of the serializer, I understand that I have to pass the query in the serializer

Working codebase
#serializers.py
class PackageSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
class Meta:
model = Package
fields = "__all__"
class OrderSerializer(serializers.ModelSerializer):
package = PackageSerializer(many=True)
def get_or_create_packages(self, packages):
package_ids = []
for package in packages:
package_instance, created = Package.objects.get_or_create(pk=package.get('id'), defaults=package)
package_ids.append(package_instance.pk)
return package_ids
def create_or_update_packages(self, packages):
package_ids = []
for package in packages:
package_instance, created = Package.objects.update_or_create(pk=package.get('id'), defaults=package)
package_ids.append(package_instance.pk)
return package_ids
def create(self, validated_data):
package = validated_data.pop('package', [])
order = Orders.objects.create(**validated_data)
order.package.set(self.get_or_create_packages(package))
return order
def update(self, instance, validated_data):
package = validated_data.pop('package', [])
instance.package.set(self.create_or_update_packages(package))
fields = ['order_id', 'is_cod']
for field in fields:
try:
setattr(instance, field, validated_data[field])
except KeyError: # validated_data may not contain all fields during HTTP PATCH
pass
instance.save()
return instance
class Meta:
model = Orders
fields = "__all__"
#views.py
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
queryset = Orders.objects.all()
Register this view with the help of DefaultRouter as,
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'order', OrderViewSet, basename='order')
urlpatterns = [
] + router.urls
Thus you will get basic CRUD end-points as described in that table (see the DefaultRouter ref).
Let your order list end-point be /foo-bar/order/
HTTP POST to /foo-bar/order/ to create a new instance
HTTP PUT or HTTP PATCH to /foo-bar/order/<ORDER_PK>/ to update the content
Note
In this case, you should pass the id value of package if you wish to map an existing package relation with the Order
References
DRF ModelVieSet
Django get_or_create(...)
Django create_or_update(...)
Django M2M set(...)
DRF DefaultRouter
UPDATE-1
You can wire-up the view like this
urlpatterns = [
path('foo/order/', OrderViewSet.as_view({'post': 'create'})), # create new Order instance
path('foo/order/<int:pk>/', OrderViewSet.as_view({'patch': 'partial_update'})), # update Order instance
]
Note: This supports only HTTP POST and HTTP PATCH

Related

Django Rest Framework problem in CreateAPIView

I have a project with Courses and Sections in it. I am building this project with django rest framework and i have the function that should create a section with 3 fields: course, title and creator. I want to understand how i can take a course slug from url and put it in course field, i mean don't pick course manually. How to implement that?
models.py
class CourseSections(models.Model):
creator = models.ForeignKey(User,related_name='creator_sections',on_delete=models.CASCADE,null=True)
title = models.CharField(max_length=50)
course = models.OneToOneField(Course, related_name='course_section', on_delete=models.CASCADE,null=True)
serializers.py
class CourseSectionSerializer(serializers.ModelSerializer):
class Meta:
model = CourseSections
fields = ['creator', 'title', 'course']
def create(self, validated_data):
instance = self.Meta.model(**validated_data)
request = self.context.get('request')
if request and hasattr(request, 'user'):
user = request.user
instance.save()
return instance
views.py
class SectionsCreateAPIView(CreateAPIView):
queryset = CourseSections.objects.all()
serializer_class = CourseSectionSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsAdminOrReadOnly]
lookup_field = 'slug'
lookup_url_kwarg = 'course__slug'
def perform_create(self, serializer):
serializer.save(creator=self.request.user)
urls.py
url(r'^sections/(?P<course__slug>[-\w]+)/create/$', SectionsCreateAPIView.as_view(), name='create_sections'),

How to associate user to a post request in Django drf

I have the following :
I am working with DRF, based JWT token.
I want to associate an experiment with a USER, i.e when a post request is arriving I want to be able to save that post request with the Foreginkey it needed for the author by the user whom sent the request.
The POST request is always authenticated and never anonymous, i.e request.user is always exist ( I can see it when debugging)
I tried to add the following
def create(self, request, **kwargs):
request.data["author"] = request.user
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return....
But is_valid return always False ( the only time ts was true, was when I took out the author from the ExperimentsSerializers fields....
will be happy for any leads....
my code attached below
Model.py:
class User(AbstractUser):
pass
def __str__(self):
return self.username
class Experiments(models.Model):
name = models.CharField(max_length=40)
time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
View.py:
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
serializer_class = ExperimentsSerializers
queryset = Experiments.objects.all()
filterset_fields = '__all__'
permission_classes = (permissions.IsAuthenticated,)
serializers.py
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
You can just pass additional data with save arguments:
def create(self, request, **kwargs):
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
Note that you may need to specify author field as read_only so it would not be required in request body:
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
read_only_fields = ['author']
One more approach can be to use
HiddenField with default value set to CurrentUserDefault
This way that field will not be exposed at the same time current user will be accessible and other operations can be done on that user context.
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
Something like this:
class ExperimentsSerializers(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = models.Experiments
fields = '__all__'
Reference :
HiddenField - https://www.django-rest-framework.org/api-guide/fields/#hiddenfield
CurrentUserDefault - https://www.django-rest-framework.org/api-guide/validators/#currentuserdefault

how to set foreignkey value in child serializer in django rest framework when the parent record exists

I have two models first as parent model "Country", that filled before the second one as child model "City". as the following
class Country(models.Model):
name = models.CharField(max_length=35)
icon = models.ImageField()
def __str__(self):
return self.name
class City(models.Model):
name = models.CharField(max_length=35)
country = models.ForeignKey(to=Country, on_delete=models.CASCADE)
def __str__(self):
return self.name
My serializers.py for my need as following :
class CountrySerializer(ModelSerializer):
class Meta:
model = Country
fields = '__all__'
class CitySerializer(ModelSerializer):
country = serializers.PrimaryKeyRelatedField(queryset=Country.objects.all())
class Meta:
model = City
fields = ('name', 'country')
view.py
class CountryAPIView(ListAPIView):
queryset = Country.objects.all()
serializer_class = CountrySerializer
permission_classes = [AllowAny, AllowAnonymous]
class CityAPIView(ListAPIView):
queryset = City.objects.all()
serializer_class = CitySerializer
permission_classes = [AllowAny, AllowAnonymous]
def post(self, request):
serializer = CitySerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
serializer.create(validated_data=request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.error_messages,
status=status.HTTP_400_BAD_REQUEST)
now when i run get api it run and gives me a result fine . But when im trying to create a new city and set "country":"id" in json i got this error
Cannot assign "2": "City.country" must be a "Country" instance.
So if i was not clear ,, what i need is exactly set foreign key to city when i create city ,, not create city and country,,
please any one had a solution help, because i tried many ways and read the django rest framework docs about this point but i didn't got it.
First of all, the raise_exception should be a boolean value (either True or False)
You could avoid this error by using inheriting the view class from ListCreateAPIView
from rest_framework.generics import ListCreateAPIView
class CityAPIView(ListCreateAPIView):
queryset = City.objects.all()
serializer_class = CitySerializer
permission_classes = [AllowAny, AllowAnonymous]
You don't want to use the post() method if you're using ListCreateAPIView, because DRF will take care of that part well.
Suggestion
Since you're dealing with CRUD functionalities of the model, you can use the DRF's ModelViewset class
you are not using the validated data to create a new city, just change this line:
serializer.create(validated_data=request.data)
to this:
serializer.save()
when you perform serializer.save(), the serializer will use its validated data.
also, DRF has a generic view(ListCreateAPIView) that covers your use-case.

prevent creating related objects while adding objects to many to many fields in django rest framework

I have already referred to many posts like these and a bug on github repo of DRF. I am still unclear if this is bug still exists as the info i fetched so far is not from recent posts. I am trying to create an api using django rest framework. below given is my code.
Serializers.py
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email')
class TeamSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True)
class Meta:
model = Team
fields = ('name', 'members')
class TeamUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ('name', 'description', 'members', 'team_type', 'created_by')
Views.py
class TeamViewSet(viewsets.ModelViewSet):
queryset = Team.objects.all()
serializer_class = TeamSerializer
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get_queryset(self):
queryset = Team.objects.filter(created_by=self.request.user)
return queryset
members is a M2M relationship to django user model. I can create a new object, get the details but upon updating an existing Team model bby adding a new user to member field, it says a user with that username exists. I can override save() method or switch to another serializer in the update() method of my modelserializer to solve the issue.
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
members = request.data['members']
member_list = list()
for member in members:
member_list.append(User.objects.get(username=member['username']).id)
request.data['members'] = member_list
serializer = TeamSerializerUpdate(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
But I am not sure if that is the right thing to do. It would be very helpful if a workaround for this is devised.
TIA

Sending patch request to Django Rest Framework API with Foreign Key

I need to send put request to API for which I know only the foreign key.
How am supposed to do this.
models.py
class Company(models.Model):
name = models.CharField(max_length = 100)
user = models.OneToOneField(settings.AUTH_USER_MODEL, unique = True)
serializer.py
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('id', 'name','user')
views.py
class Company(object):
permission_classes = (IsAuthenticated,IsUserThenReadPatch)
serializer_class = CompanySerializer
def get_queryset(self):
user = self.request.user
return Company.objects.filter(user = user)
class CompanyDetails(Company, RetrieveUpdateAPIView, APIView):
pass
urls.py
url(r'^company/$',views.CompanyDetails.as_view()),
In order to enable all CRUD operations in DRF you probably want to use ViewSet instead of View:
# views.py
class CompanyViewSet(ViewSet):
permission_classes = (IsAuthenticated,IsUserThenReadPatch)
serializer_class = CompanySerializer
def get_queryset(self):
user = self.request.user
return Company.objects.filter(user = user)
# urls.py
router = routers.SimpleRouter()
router.register(r'company', CompanyViewSet)
urlpatterns = router.urls
The above will allow you do send all CRUD REST requests:
GET /company - list all companies
POST /company - create a company
GET /company/:id - get a single company
PUT/POST /company/:id - update a company
PATCH /company/:id - partially update a company
DELETE /company/:id - delete a company
You can read more in the DRF docs - viewsets and routers

Categories

Resources