Creating several DB instances from a single POST request - python-3.x

I have a table like this:
class Mapping(db.Model):
map_id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
bike_id = db.Column(db.String(255))
is_active_data = db.Column(db.Boolean, default=True)
created_by = db.Column(db.String(150))
updated_by = db.Column(db.String(150))
My POST method:
def save_MapperM(adddata):
create_data = Mapping(**adddata)
db.session.add(create_data)
db.session.commit()
return dict(Successful="Successfully Created")
Route:
#test.route("/addmapper"))
class MapperV(Resource):
#staticmethod
def post():
if request.method == 'POST':
save_data = request.get_json()
try:
return jsonify(save_MapperM(save_data))
except Exception:
return jsonify({'Unsuccessful': 'Looks like you missed something !!'})
Current Code :
The current code will take only one bike_id for every request.
Requirements:
I want to take multiple bike_id's as for one user id and store it as multiple records in the table level.
Example data format coming from UI:
{ user_id: 1, bike_id: 1,2,3,4 }

The easiest solution is to modify your save_MapperM function with a cycle:
def save_MapperM(adddata):
for bike_id in adddata["bike_id"]:
item_data = adddata.copy()
item_data["bike_id"] = bike_id
create_data = Mapping(**item_data)
db.session.add(create_data)
db.session.commit()
return dict(Successful="Successfully Created")
But be careful with this function as it allows to create Mapping instances with any parameters received from the POST request. It looks like it is intended but it can lead to security issues if your Mapping class has some private attributes which should be filled only on the server side. For example the user "Mike" can send a request:
{ "user_id": 1, "bike_id": [1, 2], "created_by": "Alex", "updated_by": "Alex" }
It will cause the save_MapperM function to create instances with created_by and updated_by values set to "Alex" which may not be true. It is better to get such attributes from the session data.
So your post method may look like this (post and save_MapperM functionality combined):
def post():
request_data = request.get_json()
for bike_id in request_data.get("bike_id", []):
item = Mapping(
user_id=request_data.get("user_id"),
bike_id=bike_id,
created_by=session.get("username"),
updated_by=session.get("username"),
)
db.session.add(item)
try:
db.session.commit()
except Exception:
return jsonify({"success": False})
return jsonify({"success": True})
The next step may be implementing request JSON data validation. It is OK when you have a couple of JSON keys with a simple structure but when you need to pass lots of data you need to be sure it is correct. You can use some of the serialization/ODM libraries for this Marshmallow for example.

Related

DRF Intermediary Table - POSTing data to the Intermediary Table

I have a many-to-many relationship in my DB design and I am having trouble with the POST aspect. I currently have a table called Loads, Containers, and Container_Loads (this is the intermediary table).
My question is this:
I want to be able to send a POST request into the ContainerLoad intermediary table and just update that table with the values it requires which are: Load ID (PK of the Load table), Container ID (PK of the Container Table) and # of pallets (unique field to the intermediary table). I am able to GET/retrieve the records just fine, but when I try to send a POST request with a payload such as
{
"id":3,
"pallets":"4",
"containerNumberId":5,
"loadNumberId":53
}
(where containerNumberID and loadNumberID are the existing keys in their respective tables), it seems that my code wants to create a whole new Load entry as well (as it asks me for the remaining fields of the Load model), where as I just want to create an entry in the intermediary table without creating a new entry in the Load table.
So for the purpose of my project, a load can be on many containers [imagine that it's split because all of it couldn't fit on one] and a container can belong to many loads.
My models.py looks like this:
class ContainerLoad(models.Model):
id = models.AutoField(primary_key=True)
load_number = models.ForeignKey(Load,on_delete=models.CASCADE)
container_number = models.ForeignKey(Container,on_delete=models.CASCADE)
pallets = models.CharField(blank=True,null=True,default=0,max_length=20)
class Meta:
db_table = 'ContainerLoad'
#load model shortened for brevity
class Load(models.Model):
id = models.AutoField(primary_key=True)
bnsf_container_number = models.ManyToManyField(Container, through='ContainerLoad',through_fields=('load_number','container_number'))
class Meta:
db_table = "Load"
class Container(models.Model):
id = models.AutoField(primary_key=True)
container_number = models.CharField(max_length=15)
in_use = models.BooleanField()
class Meta:
db_table = "Container"
my serializers.py currently looks like this, the commented out section is from me attempting to get the POST to work)
class ContainerLoadSerializer(WritableNestedModelSerializer):
# load_number_id = LoadSerializer(read_only=False)
# container_number_id = ContainerSerializer(read_only=False)
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2
class LoadSerializer(WritableNestedModelSerializer):
primary_driver = DriverSerializer(read_only=False)
second_driver = DriverSerializer(allow_null=True,read_only=False)
third_driver = DriverSerializer(allow_null=True,read_only=False)
bnsf_container_number = ContainerSerializer(read_only=False)
pickup_location = LocationSerializer(read_only=False)
delivery_location = LocationSerializer(read_only=False)
broker = BrokerSerializer(read_only=False)
booked_by = EmployeeSerializer(read_only=False)
class Meta:
model = Load
fields = '__all__'
depth = 1
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class Meta:
model = Container
fields = '__all__'
depth = 1
And finally the views.py
class ContainerLoadViews(APIView):
def get(self, request, id=None):
if id:
container = ContainerLoad.objects.get(id=id)
serializer = ContainerLoadSerializer(container)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
containers = ContainerLoad.objects.all()
serializer = ContainerLoadSerializer(containers, many=True)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
def post(self, request):
serializer = ContainerLoadSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "Error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
If you look at the code of the save method of the BaseSerializer class you will see this:
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
How you are not passing the instance in the post function:
serializer = ContainerLoadSerializer(data=request.data)
The save is always calling to create. You should do something like.
try:
instance = ContainerLoad.object.get(id=request.data['id'])
except:
instance = None
serializer = ContainerLoadSerializer(instance=instance, data = request.data)
You have depth is set to 2 in your ContainerLoadSerializer Meta class, which is telling the serializer to generate a nested representation of your models.
https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization
The default ModelSerializer uses primary keys for relationships, but you can also easily generate nested representations using the depth option:
The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
If you remove the depth attribute, the serializer should default back to expecting a primary key value, which is your desired behaviour.
Your serializer should look something like this:
class ContainerLoadSerializer(serializers.ModelSerializer):
class Meta:
model = ContainerLoad
fields = "__all__"
The solution to this was that I needed a nested response when reading the data from the ContainerLoad table but a simple write (not nested) function when POSTing the data.
The solution was to use the to_representation and to_internal_value methods (https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior) available within DRF to override the behavior of the serializers. Here is the code that now works for both GET and POST requests and it is no longer asking me for fields related to the Load or Container models when inserting data.
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class ContainerFieldSerializer(serializers.Field):
def to_internal_value(self,value):
return Container.objects.get(id=value)
def to_representation(self,instance):
return ContainerSerializer(instance=instance).data
class Meta:
model = Container
fields = '__all__'
depth = 1
I did the same for the Load Serializer.
and then for my ContainerLoad Serializer I just assign the FK fields to the new classes I created:
class ContainerLoadSerializer(serializers.ModelSerializer):
cl_container = ContainerSerializer.ContainerFieldSerializer()
cl_load = LoadSerializer.LoadFieldSerializer()
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2

How to filter Django serializer data?

I'm trying to filter data based on userName in JWT. This is how I've been trying to do it:
views.py:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user)
except ValidationError as v:
print("validation error", v)
return Response(person[0])
This works as I can get the Person data with print("Person: ", person[0]). The return Response(person[0]) however returns an error: TypeError: Object of type Person is not JSON serializable. I guess I could use a serializer class to return a valid JSON, am I right? I have this in my serializers.py:
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
I just don't know how to use this with my view. If I use serializer instead of person = Person.objects.filter(userName=request.user), how is the filtering supposed to be done?
Please correct me if I'm not on right track at all.
You can add serializer to view, provide query result to serializer and get serialized data:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = TestSerializer # add serializer
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user).last()
data = self.serializer_class(instance=person).data # serialize query result
except ValidationError as v:
print("validation error", v)
raise v
return Response(data) # return serialized response
Here you can find some examples of using serializer with class based views.
Also note that your Person model might have security-sensitive fields like password, etc, so it's better to specify exact fields you need in serializer rather then use fields = '__all__'.
Are you using Django-Rest-Framework (based on your use of ModelSerializer)?
In pure Django, from the documentation on serializers, you can do something like:
from django.core import serializers
json_response = serializers.serialize("json", person[0])
return Response(json_response)
If you are using Django-Rest-Framework:
return Response(TestSerializer(person[0]).data)

Update an order after POST method Django Rest Framework

I just started learning Django Rest Framework together with React and i am stuck on this problem for a couple of weeks now. Hope you guys can help me out.. Here's what i am trying to achieve: When a user clicks on 'Buy Now' there will be an order created with the status of the order set to open. This all works fine. My Orders view & model looks like this:
class OrdersView(ListCreateAPIView):
serializer_class = OrdersSerializer
queryset = Orders.objects.all()
def post(self, request):
serializer = OrdersSerializer(data=request.data)
if serializer.is_valid():
data = serializer.validated_data
price = data['price']
pakket = data['pakket']
payment = mollie_client.payments.create({
'amount': {
'currency': 'EUR',
'value': price,
},
'description': pakket,
'redirectUrl': 'http://127.0.0.1:3000/Thanks',
'webhookUrl': 'http://6efd85bb183c.ngrok.io/api/order-status/',
'method': 'ideal',
})
serializer.save(user=request.user, userID=request.user.id,
status=payment.status, orderID=payment.id)
return Response(status=status.HTTP_201_CREATED, data=payment)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class Orders(models.Model):
userID = models.CharField(max_length=30)
pakket = models.CharField(max_length=30)
price = models.CharField(max_length=30)
created_at = models.DateTimeField(default=one_hour_later)
status = models.CharField(max_length=30)
orderID = models.CharField(max_length=30)
user = models.EmailField(max_length=120)
def __str__(self):
return self.user
My serializer called OrdersSerializer looks like this:
class OrdersSerializer(serializers.ModelSerializer):
class Meta:
model = Orders
fields = '__all__'
read_only_fields = ('user','status','orderID','userID')
After the status of the order has changed (the user made the purchase or not) my webhook is called (a POST) and i am retreiving the order by the id which is POSTED to the webhook. This is all taken care of by using an API client (mollie_client). This all works fine too. My View looks like this:
class OrderStatusView(ListCreateAPIView):
serializer_class = OrdersSerializer
queryset = Orders.objects.all()
permission_classes = [AllowAny,]
def post(self, request):
data = request.data
payment_id = data['id']
payment = mollie_client.payments.get(payment_id)
if payment.is_paid():
return Response(status=status.HTTP_200_OK, data=payment.status)
elif payment.is_pending():
return Response(status=status.HTTP_200_OK, data=payment.status)
elif payment.is_open():
return Response(status=status.HTTP_200_OK, data=payment.status)
else:
return Response(status=status.HTTP_200_OK, data=payment.status)
return Response(status=status.HTTP_400_BAD_REQUEST)
Now my problem is: How do i retreive the original Order (by the id) and update the status of the order.
Grabbing the payment.status works fine, but how do i grab the Order and update its status from the payment.status?
I am able to grab the original order by: Orders.objects.get(orderID=payment_id) inside the view, but from there i am stuck. I am not sure how i can update that specific order and change the status to payment.status.
I am thinking i have to do something with the serializer.save(status=payment.status).
Maybe it is super simple, but i can't get my head around it.
Hope its clear and you guys can help.
Please keep in mind i am still a beginner on this topic.
Thanks in advance!
Greetings
As #Ngoc Pham pointed out It is better to keep payment and order different. What is generally followed is You keep pinging for the payment status of an order and then update the order status with choice field.
In Orders Model you can define a choice field for status and whenever you are buying create an order with its status to constants.PENDING.
Now filter all the orders with status as pending and hit the check_payment_status of api with the order id. And now if the payment status is success then update the order object with constants.SUCCESS. Here's a gist of what I meant.
class Orders(models.Model):
PENDING = 'pending'
SUCCESS = 'success'
FAILURE = 'failure'
ORDER_STATUS_CHOICES = [
(PENDING, "pending"),
(SUCCESS, "success"),
(FAILURE, "failure"),
]
status = models.CharField(
choices=ORDER_STATUS_CHOICES,
max_length=10,
null=True
)
Now whenever you buy make the status as
status=Orders.PENDING
if the payment status is pending. And then fetch the orders object to get the pending objects
Orders.objects.filter(status=Orders.PENDING)
Once you iterate through this queryset you can hit payment_status_check request for a particular id and update the status again through
status=Orders.SUCCESS
Hope this gives you a way to approach this problem :).
First, your OrderStatusView post never throw HTTP_400_BAD_REQUEST. Its have else condition in last. It always return HTTP_200_OK.
you can try edit like this: its can find orders have orderID=payment_id, and if not have anything match, return HTTP_400_BAD_REQUEST. Make sure all status of payment is same with status of Order
class OrderStatusView(ListCreateAPIView):
serializer_class = OrdersSerializer
queryset = Orders.objects.all()
permission_classes = [AllowAny,]
def post(self, request):
data = request.data
payment_id = data['id']
payment = mollie_client.payments.get(payment_id)
orders = self.get_queryset().filter(orderID=payment_id)
if not orders:
return Response(status=status.HTTP_400_BAD_REQUEST)
orders.update(status=payment.status)
return Response(status=status.HTTP_200_OK, data=payment.status)

Django Rest Framework DELETE request responds like a GET request

I'm trying to delete an entry in my data base that is returned by a modelviewset get_queryset. When sending a DELETE request through the DRF web interface and via postman, I receive this response "DELETE /api/remove_self/3 HTTP/1.1" 200 along with the data I am trying to delete. The code that gives this result looks like this:
Models.py
class EventAtendee(models.Model):
"""Lists users atending an event"""
#below connects user profile to event
id = models.AutoField(primary_key=True)
event_id = models.IntegerField(null = True)
user_profile = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
def __str__(self):
return self.event_id
views.py
class RemoveSelfFromEvent(viewsets.ModelViewSet):
"""Remove Yourself From an Event you were attending"""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.EventAtendeeSerializer
permission_classes = (permissions.UpdateOwnStatus, IsAuthenticated)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user"""
#
serializer.save(user_profile=self.request.user)
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
#user_profile = self.kwargs['user_profile']
event_id = self.kwargs['event_id']
return models.EventAtendee.objects.filter(event_id=event_id, user_profile=self.request.user)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
urls.py
router = DefaultRouter(trailing_slash=False)
router.register('events', views.EventAtendeeViewSet, basename='EventAtendee')
urlpatterns = [
path('remove_self/<event_id>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'list'})),
]
Any help is much appreciated!
You are mapping the method DELETE to list in your urls.
path('remove_self/<event_id>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'list'})),
Correct way to do:
path('remove_self/<pk>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'destroy'})),
mapping of various methods:
POST : create
GET : retrieve
PUT : update
PATCH : partial_update
DELETE : destroy

get() returned more than one Sub_Topic -- it returned 3

I have started a project using Django. Where I used add multiple sub-topics under one main topic by taking staticid. When I am giving same staticid to multiple sub-topics, I am getting the error below (get() returned more than one Sub_Topic -- it returned 3!).
Model:
class Sub_Topic(models.Model):
IMPORTANCE_SCORE = (
('LOW','Low'),
('NORMAL', 'Normal'),
('HIGH','High'),
)
staticid = models.ForeignKey(SID,on_delete=models.CASCADE, blank=True, default=None, null=True)
sub_topic = models.CharField(max_length=250)
Num_Of_Sub_subTopics = models.PositiveIntegerField(default=0)
Num_Of_Questions = models.PositiveIntegerField(default=0)
importance = models.CharField(max_length=6, choices= IMPORTANCE_SCORE, default='LOW')
complexity = models.PositiveIntegerField(default=0)
prerequisite = models.CharField(max_length=250)
def __str__(self):
return self.sub_topic
View:
class Sub_TopicDetailView(generics.RetrieveUpdateDestroyAPIView):
"""
GET sub_topic/:id/
PUT sub_topic/:id/
DELETE sub_topic/:id/
"""
queryset = Sub_Topic.objects.all()
serializer_class = Sub_TopicSerializer
def get(self, request, *args, **kwargs):
try:
a_sub_topic = self.queryset.get(staticid=kwargs["staticid"])
return Response(Sub_TopicSerializer(a_sub_topic).data)
except Sub_Topic.DoesNotExist:
return Response(
data={
"message": "Sub_Topic with id: {} does not exist".format(kwargs["staticid"])
},
status=status.HTTP_404_NOT_FOUND
)
#validate_request_data
def put(self, request, *args, **kwargs):
try:
a_sub_topic = self.queryset.get(staticid=kwargs["staticid"])
serializer = Sub_TopicSerializer()
updated_sub_topic = serializer.update(a_sub_topic, request.data)
return Response(Sub_TopicSerializer(updated_sub_topic).data)
except Sub_Topic.DoesNotExist:
return Response(
data={
"message": "Sub_Topic with id: {} does not exist".format(kwargs["staticid"])
},
status=status.HTTP_404_NOT_FOUND
)
Error:
get() returned more than one Sub_Topic -- it returned 3!
How do I overcome this?
If you have a main topic (say, "donuts"), and many subtopics within that ("plain donuts", "chocolate donuts", "vanilla donuts", ...), you cannot reference a subtopic by just saying "donuts", you have to be more specific.
Your sub-topic views should accept a sub-topic ID, not the main topic ID. Try changing this:
a_sub_topic = self.queryset.get(staticid=kwargs["staticid"])
# 'staticid' is the foreign key of the main topic: it is
# the same for many sub-topics!
to this:
a_sub_topic = self.queryset.get(id=kwargs["id"])
# 'id' is the primary key field generated automatically by Django:
# it's unique for every sub-topic
If instead you want to display all sub-topics for a given topic, then you should use filter() instead of get():
sub_topics = self.queryset.filter(staticid=kwargs["staticid"])

Resources