many to many field in django view - django-rest-framework

I try to write a view user create paper trading,I have some assets in my site,now when user create paper trading have to choose from that assets,but cant create and have an error
# -------------------- Create Paper_Trading View --------------------
class CreatePaperTradingApiView(viewsets.GenericViewSet):
permission_classes = (IsAuthenticated,)
"""
create a paper_trading for request.user
"""
serializer_class = PaperTradingSerializer
#action(methods=['POST'], detail=True, url_name='paper', url_path='paper')
def create_paper_trading(self, request, pk=None):
data = request.data
symbol = Assets.objects.get(id=pk).core_key
user_id = request.user.pk
if not PaperTrading.objects.filter(user=user_id, status='OPENED').filter(assets__core_key=symbol).exists():
queryset = PaperTrading.objects.create(
user_id=user_id,
average_price=data['average_price'],
quantity=data['quantity'],
status='OPENED',
side=data['side']).assets.set(Assets.objects.filter(core_key=symbol))
queryset = PaperTrading.objects.filter(user=user_id, status='OPENED').filter(assets__core_key=symbol)
serializer = PaperTradingSerializer(queryset, many=True)
return Response(serializer.data)
return Response(data={'result': 'This symbol opened.'})
I took assets from list of asset that is many to many field
The error is
"<PaperTrading: PaperTrading object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.

I will assume the Many to many relationship is Assets to PaperTrading (it would be good to see your models otherwise. Try:
new_instance = PaperTrading.objects.create(
user_id=user_id,
average_price=data['average_price'],
quantity=data['quantity'],
status='OPENED',
side=data['side'],
)
new_instance.assets.set(Assets.objects.filter(core_key=symbol))
...
My thought is that you firstly need to create the object before you assign its' M2M.

Related

Pytest fails when trying to pass list of dictionaries in POST request

We have a model with only one fields (Name). We want to send a list of dictionaries and create multiple objects for this model in one POST request. The model, serializer, view and mixin is mentioned below:
models.py
class Manufacturer(models.Model):
"""
The Measure Unit Category Table contains the categories
of measurement units (example: weight, length, temperature,etc)
"""
name = models.CharField(verbose_name=_("Manufacturer Name"),
help_text=_("Required. Example: Ford, Tesla, Hyundai"),
max_length=63,
unique=True)
class Meta:
verbose_name = _("Manufacturer")
verbose_name_plural = _("Manufacturers")
ordering = ('name',)
def __str__(self):
return self.name
serializers.py
class ManufacturerSerializer(serializers.ModelSerializer):
class Meta:
model = Manufacturer
fields = '__all__'
mixins.py
class CreateModelMixin(object):
def get_serializer(self, *args, **kwargs):
# add many=True if the data is of type list
if isinstance(kwargs.get("data", {}), list):
kwargs["many"] = True
return super().get_serializer(*args, **kwargs)
views.py
class ManufacturerCreateView(CreateModelMixin, generics.CreateAPIView):
"""
Create View for Manufacturer
"""
queryset = Manufacturer.objects.all()
serializer_class = ManufacturerSerializer
permission_classes = (IsAuthenticated,)
With the above code, I'm able to create multiple objects with one POST request.
I've written the below test case using pytest:
def test_create_manufacturer_list_of_objects(self, authenticated_client):
data = [
{
"name": "Test11"
},
{
"name": "Test12"
}
]
response = authenticated_client.post(reverse('manufacturer-create'), data)
assert response.status_code == 201
urls.py
path('create-manufacturer/',
ManufacturerCreateView.as_view(),
name='manufacturer-create'),
When I run the above, I get the error - AttributeError: 'list' object has no attribute 'items'.
This is a little confusing as the API works without any errors but the test case fails. Can you please guide on correcting this issue?
I tried overriding the POST method, and including the above code in serializers instead of mixins, but it gives the same error.
Passing the "format=json" when calling the post method resolved the issue.
Modified the test url to below:
response = authenticated_client.post(reverse('manufacturer-create'), data, format=json)

request a list to ModelViewSet and check duplicates before insert

A bit confused of usage of ModelViewSet. I am building a meeting room reservation system. And for reservation I am inserting reservation times to DB. But before insert I need to check room already reserved at selected time.
So user selects start and finish times and sends a request. let's say user selects between 09:00 and 14:00
let requestForm = []
this.times.some(time => {
if (time >= this.form.meeting_time_start && time <= this.form.meeting_time_finish) {
this.form.meeting_time = time
requestForm.push(this.form)
}
})
this.$store.dispatch("MEETING_ROOM_CONTENT_INSERT_OR_UPDATE", requestForm)
this means requestForm now is an Array of Objects. And each object holding a value of time 09:00, 10:00, 11:00 ~ till 14:00
But on backend side ModelViewSet always wait an validated_data as an object(dict).
class MeetingRoomContentViewSet(viewsets.ModelViewSet):
queryset = MeetingRoomContent.objects.all()
serializer_class = MeetingRoomContentSerializer
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save()
return serializer
Of course I can send objects one by one but that's not a proper way for my situation. I want to send times as a list and check if any of time has already reserved or not. Is it possible for ModelViewSet to take array(LIST) and loop it before insert.
Additionally, if you need to see Serializer.py Here:
class MeetingRoomContentSerializer(serializers.ModelSerializer):
class Meta:
model = MeetingRoomContent
fields = "__all__"
You should take a look at DRF ListSerializer.
ModelViewSet only allow you to create one object per request. In order to perform batch create, you will need a custom view that accept, validate & perform create list of objects.
For example by using DRF action decorator:
class MeetingRoomContentViewSet(viewsets.ModelViewSet):
queryset = MeetingRoomContent.objects.all()
serializer_class = MeetingRoomContentSerializer
permission_classes = [IsAuthenticated]
#action(detail=False, methods=['post'])
def batch_create(self, request):
serializer = self.serializer_class(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=201)
Your serializer classes:
class MeetingRoomContentListSerializer(serializers.ListSerializer):
def validate(self, data):
validated_data = super().validate(data)
# validated_data will be list of objects
# perform checking duplications
return validated_data
class MeetingRoomContentSerializer(serializers.ModelSerializer):
class Meta:
model = MeetingRoomContent
fields = "__all__"
list_serializer_class = MeetingRoomContentListSerializer

Django: customizing the field types needed for create and retrieve serializers

I currently have the following serializer:
serializers.py
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = SurfInstructorSerializer(many=False)
surfers = SurferSerializer(many=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
def create(self, validated_data):
return SurfGroup(**validated_data)
And the following viewset create method (viewset inherited from viewsets.ViewSet as we need some bespoke customization, extra signals and actions etc):
viewsets.py
# Surf Group Create View:
def create(self, request, format=None):
serializer = SurfGroupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = responses.standardized_json_response(
message='Surf Group Objects Have Been Successfully Created',
data=serializer.data
)
return Response(data=response, status=status.HTTP_201_CREATED, headers=headers)
For the retrieve action, the serializer works well, and we have a nested instructor object in the response. However, I want to perform a create by passing in the instructor uuid attrbiute like (see content in the POST textarea):
Rather than a whole object...I was wondering how we achieve this? Is it best to just have two Serializers, one for performing the create, and one the retrieval?
def create(self, validated_data):
surf_group = SurfGroup(
instructor__uuid=validated_data['instructor'],
)
surf_group.save()
return surf_group
It is good question.
I work with this situations many times and it looks like one option is to have two serializers as you mean: 1 for list/retrieve and 1 for save.
Another option (for me) is to set serializer field input as UUID and output as another serializer data like this:
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = serializers.UUIDField()
surfers = SurferSerializer(many=True, read_only=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
# I use this validate method to transform uuid to object which will
# be bypassed to create method for easly save
def validate_instructor(self, instructor_uuid):
try:
return Instructor.objects.get(uuid=instructor_uuid)
except Instructor.DoesNotExist:
# Remember that you dont need to pass field_key: [errors] to ValidationError
# because validate_FIELD will automatically pass field_key for you !
raise ValidationError(['Instructor with the given uuid does not exist.'])
# Overwrite output data
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['instructor'] = SurfInstructorSerializer(instance=instance.instructor).data
return ret

Cannot generate post request for multiple data

I am trying to take input multiple data object in post request, but getting such error.
non_field_errors: [ Invalid data. Expected a dictionary, but got a list. ]
models.py
class OrderProduct(BaseModel):
product = models.ForeignKey(Product,on_delete=models.CASCADE)
order = models.ForeignKey(Order,on_delete=models.CASCADE)
order_product_price = models.FloatField(blank=False,null=False,default=0) # product may belong to offer do the price
order_product_qty = models.FloatField(default=1)
serializers.py
class OrderProductSerializer(serializers.ModelSerializer):
def update(self,instance,validated_data):
product = self.validated_data.pop('product')
order = self.validated_data.pop('order')
instance.orderproduct_qty =
self.validated_data.get('orderproduct_qty',instance.orderproduct_qty)
instance.product = product
instance.order = order
instance.save()
return instance
class Meta:
model = OrderProduct
fields = '__all__'
views.py
def post(self,request,*args,**kwargs):
if request.data['contact_number'] == '':
request.POST._mutable =True
request.data['contact_number'] = request.user.mobile_number
request.POST._mutable = False
serializer = OrderSerializer(data=request.data,many=isinstance(request.data,list),context={'request': request})
print(serializer)
if serializer.is_valid():
serializer.save(user = request.user,created_by = request.user)
return Response(serializer.data,status=status.HTTP_200_OK)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
urls.py
path('orderproduct/',views.OrderProductList.as_view()),
When you call serializer.save(). It's only perform create() action which is only create one and accept dictionary data type only. If you want to save multiple data like that, you will have to override the create function of the serializer class. You can do something similar like this or run a for loop.
serializers.py
def create(self, validate_data):
# Get the data objects you need to perform bulk create
order_products = OrderProduct.objects.bulk_create(validate_data)
return order_products
views.py
if serializer.is_valid(raise_exception=True):
# Replace the serializer.save() by this line to trigger the create method in serializer
self.perform_create(serializer)
return Response(...)

How to get the id of a current object in Django serializers?

I have a model, 'Project'. The idea is that, a user will log in and create a project. After creating, the user will work on this project at any time. Certain details will be saved to other models where I have written custom functions for it in Serializers.py.
In order solve the idea I have, I need to retrieve the id of the current project that the user is currently working on in Serializers.py. Below is my code:
View.py
class MaterialTagExcelViewSet(FilteredModelViewSet):
queryset = MaterialTagExcel.objects.all()
serializer_class = MaterialTagExcelSerializer
permission_classes = (IsAuthenticated,)
http_method_names = ('get', 'head', 'post', 'options', 'patch')
Serializers.py
class MaterialTagExcelSerializer(BaseSerializer):
class Meta:
fields = "__all__"
model = MaterialTagExcel
def create(self, validated_data):
name = validated_data.get('name') # get current material name
if name is not None:
name_tag = MaterialTagExcel.objects.filter(name=name).first() # filter name to check if it already exists
client = self.context['request'].user.profile.client # get current client details
if name_tag is not None: # if name exists
objects = MaterialExcelClient.objects.filter(client_id=client.id, name_id=name_tag.id)
if objects.count() == 0:
material_excel_client = MaterialExcelClient(client_id=client.id, name_id=name_tag.id)
material_excel_client.save() # get current id and mat id and save to material_client_excel
return MaterialExcelClient.objects.filter(name_id=name_tag.id).order_by('-id')[0]
else:
return MaterialExcelClient.objects.filter(client_id=client.id, name_id=name_tag.id).first()
else:
MaterialTagExcel.objects.create(**validated_data)
MaterialTagExcel.objects.all() # save if material is new and does not exist
# return the id of this newly created material
obj = MaterialTagExcel.objects.filter(name=name).order_by('-id')[0]
# save the id of the newly created material and current client id into material_excel_client
material_excel_client = MaterialExcelClient(client_id=client.id, name_id=obj.id)
material_excel_client.save()
return MaterialExcelClient.objects.filter(name_id=obj.id).order_by('-id')[0]
From above serializer, I am able to get the client.id with the help of CurrentUserDefault. In my table user is related to profile and profile is related to client but not project. I tried to with a custom CurrentProjectDefault, but I didnt succeeded. I tried with many online sources to solve my problem.
Is there any way to get the id of the current object from client ?
I am apologizing in advance if the solution to my problem is very simple.
If you would need some more details, kindly write it in comment.
Thanks in advance.
Models.py
class MaterialTagExcel():
name = models.CharField(max_length=255, verbose_name='name', null=False, blank=False)
def __str__(self):
return "Material %s: %s" % (self.id, self.name)
#classmethod
def get_queryset_for_user(cls, user):
return cls.objects.all()
class Project():
client = models.ForeignKey(Client, related_name='projects', on_delete=models.PROTECT)
name = models.CharField(max_length=255)
class ToDo(BaseModel):
project = models.ForeignKey(Project, related_name='todos', on_delete=models.CASCADE)
owner_client = models.ForeignKey(Client, related_name='todos', on_delete=models.CASCADE)
You wish to retrieve the current project the user is working on. The goal of a REST API is to be stateless, which roughly means that the request contains all the necessary information to perform its action without relying on an external context.
This means that you have to provide the current project id in each of your request.
So, in your example, when you want to POST a new MaterialTagExcel, you'll have to provide the Project. You can modify your serializer like this to do so:
class MaterialTagExcelSerializer(BaseSerializer):
project = serializers.PrimaryKeyRelatedField(write_only=True, queryset=Project.objects.all())
class Meta:
fields = "__all__"
model = MaterialTagExcel
def create(self, validated_data):
name = validated_data.get('name')
project = validated_data.pop('project') # A Project object
Now, when you're doing a request, you'll have to specify the property project with the id. of the project the user has selected in the menu.

Resources