DRF update or create object - python

guys in my app each product must have only one record for discount in table.
i have this view for create new record in db .. how can i tell if object with comming product id is availible in db just update the target column otherwise create new object ? some people use def create in serializer should i use that function or something else ???
class DiscountControllAPiView(APIView):
"""
each product may takes a discount code or percent from owner or site administrator
"""
def post(self,request):
serializer = ProductDiscountControllSerializer(data=request.data)
if(serializer.is_valid()):
serializer.save()
return Response(True)
else:
return Response(serializer.errors)
related serilizer :
class ProductDiscountControllSerializer(ModelSerializer):
class Meta:
model = ProductDiscountControll
fields = [
'product',
'discount',
'discount_code',
'discount_code_precent',
]

Try this
class DiscountControllAPiView(APIView):
"""
each product may takes a discount code or percent from owner or site administrator
"""
def post(self,request):
id = request.data.get('id', None):
if not id:
# perform creation
serializer = ProductDiscountControllSerializer(data=request.data)
else:
# perform updation
product_discount_controll = ProductDiscountControll.objects.get(id=int(id))
serializer = ProductDiscountControllSerializer(product_discount_controll, data=request.data)
if(serializer.is_valid()):
serializer.save()
return Response(True)
else:
return Response(serializer.errors)
If id is present in the request, the api will update else it will create new record

Related

Returning JsonResponse from a django rest-framework serializer

My question is how would I return a custom JsonResponse from a rest-framework serializer?
I have a view with this code:
# Send money to outside of Pearmonie
class sendmoney_external(viewsets.ModelViewSet):
# Database model
queryset = User.objects.all()
# Serializer - this performs the actions on the queried database entry
serializer_class = SendMoneyExternalSerializer
# What field in database model will be used to search
lookup_field = 'username'
My serializer is something like this:
# Serializer for sending money to another Pearmonie user
class SendMoneyExternalSerializer(serializers.ModelSerializer):
# This is the function that runs for a PUT request
def update(self, instance,validated_data):
# Verifies the requesting user owns the database account
if str(self.context['request'].user) != str(instance.username) and not str(self.context['request'].user) in instance.userprofile.banking_write:
raise exceptions.PermissionDenied('You do not have permission to update')
account_number = self.context['request'].data['to_account']
amount = self.context['request'].data['amount']
to_bank = self.context['request'].data['to_bank']
# Creates the order in the database
from_user = BankingTransaction.objects.create(
user = instance,
date = timezone.now(),
from_account_num = instance.userprofile.account_number,
from_account_name = instance.userprofile.company,
to_account = self.context['request'].data['to_account'],
to_bank = to_bank,
amount_transferred = amount,
description = self.context['request'].data['description'],
trans_reference = self.context['request'].data['trans_reference'],
status = self.context['request'].data['status'],
)
instance.userprofile.notification_count += 1
instance.userprofile.save()
# Creates the notification for the supplier in the database
from_notification = Notifications.objects.create(
user=instance,
date=timezone.now(),
read=False,
message=f'You have paid N{amount} to account number {account_number} ({to_bank})'
)
return instance
class Meta:
model = User
fields = ['pk']
This just simple returns the User model's primary key in json... how would I make it return something custom?
I want to make a request to an external api, and send that external api's response in my django's response.
You can use SerializerMethodField for custom logic.
class SendMoneyExternalSerializer(serializers.ModelSerializer):
# This is the function that runs for a PUT request
custom_data = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['pk', 'custom_data']
def get_custom_data(self, obj):
# Your coustom logic here. (obj) represent the User object
return f'{obj.first_name} {obj.username}'

Django Rest Framework invalid serializer data, can not figure out why

I am creating a simple model with a many-to-many field. The model works fine and I can create model through the admin panel, and I can make a get request to see that model (except that it only returns user IDs instead of the user models/objects). My problem is when creating a post request to create said model.
I get one of the two errors depending on the changes I make, The serializer field might be named incorrectly and not match any attribute or key on the 'str' instance. or AssertionError: You cannot call '.save()' on a serializer with invalid data., either way it has something to do with my serializer. The following is my model,
class Schema(models.Model):
week = models.PositiveIntegerField(primary_key=True,
unique=True,
validators=[MinValueValidator(1), MaxValueValidator(53)],
)
users = models.ManyToManyField(MyUser, related_name="users")
class Meta:
ordering = ('week',)
My View,
class SchemaView(APIView):
permission_classes = (SchemaPermissions,)
def get(self, request):
schemas = Schema.objects.all()
serializer = SchemaSerializer(schemas, many=True)
return Response(serializer.data)
def post(self, request):
data = request.data
serializer = SchemaSerializer(data=data)
serializer.is_valid()
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
And my serializer,
class SchemaSerializer(serializers.ModelSerializer):
class Meta:
model = Schema
fields = ('week', 'users')
def create(self, validated_data):
users_data = validated_data.pop('users')
users = MyUser.objects.filter(id__in=users_data)
schema = Schema.objects.create(week=validated_data.week, users=users)
return schema
def update(self, instance, validated_data):
users_data = validated_data.pop('users')
users = MyUser.objects.filter(id__in=users_data)
instance.users.clear()
instance.users.add(*users)
instance.saver()
return instance
The idea is that if a week number already exists then it should call the update() function and then it should simply overwrite the users related to that week number, otherwise it should call create() and create a new week number with relations to the given users. The following is the result of printing the serializer after initializing it in the view.
SchemaSerializer(data={'week': 32, 'users': [1, 2, 3]}):
week = IntegerField(max_value=53, min_value=1, validators=[<UniqueValidator(queryset=Schema.objects.all())>])
users = PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=MyUser.objects.all())
It seems to me that the serializer should be valid for the given model? I am perhaps missing some concepts and knowledge about Django and DRF here, so any help would be greatly appreciated!
First you need set the field for saving users in the SchemaSerializer. And you don't need to customize the create and update method because the logic could be coded in the views.
class SchemaSerializer(serializers.ModelSerializer):
users = UserSerializer(read_only = True, many = True)
user_ids = serializers.ListField(
child = serializers.IntegerField,
write_only = True
)
class Meta:
model = Schema
fields = ('week', 'users', 'user_ids',)
# remove create and update method
And in views.py,
class SchemaView(APIView):
permission_classes = (SchemaPermissions,)
def get(self, request):
...
def post(self, request):
data = request.data
serializer = SchemaSerializer(data=data)
if serializer.is_valid():
input_data = serializer.validated_data
week = input_data.get('week')
user_ids = input_data.get('user_ids')
if Schema.objects.filter(week = week).count() > 0:
schema = Schema.objects.get(week = week).first()
else:
schema = Schema.objects.create(week = week)
schema.users.set(user_ids)
schema.save()
return Response(SchemaSerializer(schema).data, status=status.HTTP_200_OK)
else:
print(serializer.errors)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
And of course, the payload data should be
{'week': 23, 'user_ids': [1,2,3]}

DRF: trying to append and delete users in a many to many field model

hi I'm new to django rest framework where I'm creating a update and delete request in which a list of users will be passed in request and then either the one specified in the request will be appended to the existing or will be deleted from the existing users list
my files looks like this:
models.py
class team(models.Model):
teamname = models.CharField(max_length=64)
description = models.CharField(max_length=128)
creationtime = models.DateTimeField(auto_now_add=True)
admin = models.ForeignKey(user, related_name='admin', on_delete=models.CASCADE, to_field="id")
members = models.ManyToManyField(user,help_text='Select members for this team')
def __str__(self) -> str:
return self.teamname
def display_members(self):
return ', '.join(members.name for members in self.members.all()[:])
display_members.short_description = 'Users'
serializer.py
class TeamUserSerializer(serializers.ModelSerializer):
class Meta:
model = team
fields = ['id', 'members']
views.py
It looks like this:
"""
:param request: A json string with the team details
{
"id" : "<team_id>",
"users" : ["user_id 1", "user_id2"]
}
:return:
Constraint:
* Cap the max users that can be added to 50
"""
the code for views.py is like this of what i attempted:
def patch(self, request, pk=None, format=None):
id = pk
if 'members' in request.data.keys():
new_members = request.data.get('members')
existing_members = team.objects.get('id').values_list('members', flat=True)
print(new_members)
print(existing_members)
all_memebers = new_members + existing_members
request.data.update({'members': set(all_memebers)})
serializer = TeamUserSerializer(usser, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({"MSG":"Team Updated Successfully"})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
edit:
url would be like this: http://localhost:8000/teams/{id}/id culd be like- 1,2,3 or so on...
then in data what i would be passing as json would be like this:
{
"members":[1,2,3,4]
}
supposedly the model field already has values like under members column would be like
"members": [9,8,11,41,5] in this case of append: it should become in db like-> "members":[1,2,3,4,9,8,11,41,5]
and in case of delete the one that are passed as json shall be removed from array of existing users in db
ManyToManyField need different approach to add and remove new objects.
for ex -
def patch(self, request, pk=None, format=None):
id = pk
if 'members' in request.data.keys():
new_members = request.data.get('members')
existing_members = team.objects.get('id').values_list('members', flat=True)
print(new_members)
print(existing_members)
if serializer.is_valid():
objectCreated = serializer.save()
for memberToBeAdded in new_members:
objectCreated.members.add(memberToBeAdded)
return Response({"MSG":"Team Updated Successfully"})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST
Check the add method here.
You can also use the remove function of ManyToManyField when you want to remove an object.
Check this stackoverflow answer
You can always modify this to use in your case.

New objects are made every time

I made a single function for register and login with mobile and otp. The register part is in the else part of the function, and the if part is the login function. Every time I log in with the already registered number, it makes a new object in the database, and I don't want that. I want to just update the otp part from when the number was registered in the database.
views.py
class RegistrationAPIView(APIView):
permission_classes = (AllowAny,)
serializer_class = ProfileSerializer
def post(self, request):
mobile = request.data['mobile']
data = Profile.objects.filter(mobile = mobile).first()
if data:
serializer = self.serializer_class(data=request.data)
mobile = request.data['mobile']
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
content = {'mobile': instance.mobile, 'otp': instance.otp}
mobile = instance.mobile
otp = instance.otp
print("Success")
send_otp(mobile,otp)
return Response(content, status=status.HTTP_201_CREATED)
else:
return Response({"Error": "Login in Failed"}, status=status.HTTP_400_BAD_REQUEST)
else:
serializer = self.serializer_class(data=request.data)
mobile = request.data['mobile']
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
content = {'mobile': instance.mobile, 'otp': instance.otp}
mobile = instance.mobile
otp = instance.otp
send_otp(mobile,otp)
return Response(content, status=status.HTTP_201_CREATED)
else:
return Response({"Error": "Sign Up Failed"}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['mobile']
def create(self, validated_data):
instance = self.Meta.model(**validated_data)
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
instance.otp = str(random.randint(1000 , 9999))
instance.save()
return instance
models.py
'''
class Profile(models.Model):
mobile = models.CharField(max_length=20)
otp = models.CharField(max_length=6)
'''
I'm assuming here that you're using DRF Serializers. If that's the case, note from the documentation that :
Calling .save() will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class:
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
As an aside, you probably want to do a get_or_create or create_or_update with defaults instead of having the if/else clause.
Option A: First thing first. If there is only and only one mobile number for each Profile you should add a unique constraint for your Profile model as well. Something like:
class Profile(models.Model):
mobile = models.CharField(max_length=20, unique=True)
otp = models.CharField(max_length=6)
Do not forget to make migration and migrate these new changes to your database (also note that there might be some duplicate records in Profile table so if you're not on production server first delete all of your records and then make this migration).
And then make this change to your serializer's create method:
def create(self, validated_data):
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
instance = self.Meta.model.objects.update_or_create(**validated_data, defualts=dict(otp=str(random.randint(1000 , 9999))))[0]
return instance
with update_or_create now you're sure that if the record with specific mobile exists you will update that and if not you will create new one.
Option B: But if you don't want to make this change to your database for any reason you can just simply do this:
def create(self, validated_data):
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
if self.Meta.model.objects.filter(**validated_data).exists():
instance = self.Meta.model.objects.filter(**validated_data).last()
instance.otp = str(random.randint(1000 , 9999))
instance.save()
else:
instance = self.Meta.model(**validated_data)
instance.otp = str(random.randint(1000 , 9999))
instance.save()
return instance
Note that there might be multiple records in your table with same mobile number as long as there isn't any constraint on that model and here we are only updating latest record in your Profile table. I hope these two options solve your problem.

Create a related object in django

I have a model Inventory related to another model Product
class Inventory(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
whenever I create a "Product" I want to create a blank entry of "Inventory" as well.
How can I do that?
Views.py
class ProductCreateAPIView(CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = ProductSerializer
parser_classes = (FormParser, MultiPartParser)
def post(self, request, *args, **kwargs):
owner = request.user.pk
d = request.data.copy()
d['owner'] = owner
serializer = ProductSerializer(data=d)
if serializer.is_valid():
serializer.save()
print("Serializer data", serializer.data)
Inventory.objects.create(product=serializer.data['id'], quantity = 0, owner = owner) <= Will this work?
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Usually it is solved by using a signal, however, in this case you will need to add user to the product model like "added_by" to know who added the product and assign inventory owner to that product in your signal. It is not an option, then your request is not possible to implement, because user (owner) is only known in your view and your view will be the only place where you can create the default inventory.
Assuming you added added_by value to your product, your signal:
#receiver(post_save)
def create_default_product_inventory(sender, instance, created, **__):
if created and instance.added_by:
Inventory.objects.create(product=product, owner=instance.added_by)
To make it better, instead of the created flag, you can check if the product has added_by value, but not an inventory item, then create the default inventory item. This will make sure to create a default inventory whenever a user is assigned to a product.
As long as you need the user that created the product to be its inventory owner, you will need to save that user in the product details or just create the related models in the view directly.

Categories

Resources