How to update a related model after another model is created? - python

I need to automatically update my Account's amount when a Transaction is created.
I have Transaction model's model.py:
class Transaction(models.Model):
user = models.ForeignKey(User, default=None)
account = models.ForeignKey(Account, default=None)
...
Its serializers.py:
class TransactionSerializer(serializers.ModelSerializer):
class Meta:
model = Transaction
fields = ('id', 'user', 'account_id', 'category_id', 'name', 'amount', 'description', 'created_at')
def create(self, validated_data):
return Transaction.objects.create(**validated_data)
And its views.py:
class TransactionList(APIView):
def get(self, request):
user_id = request.user.pk
transactions = Transaction.objects.filter(user_id=user_id).order_by('-created_at')
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
def post(self, request):
account_id = request.data['account_id']
category_id = request.data['category_id']
serializer = TransactionSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, account_id=account_id, category_id=category_id)
self.update_account(request)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
def update_account(self, request):
account_id = request.data['account_id']
category_id = request.data['category_id']
account = Account.objects.get(pk=account_id)
category = Category.objects.get(pk=category_id)
if category.type == 'expense':
account.amount = (account.amount - int(self.request['amount']))
else:
account.amount = (account.amount + int(self.request['amount']))
# Then what?
I thought of creating a custom method that will be executed within the condition if serializer is valid, that would get the account and category by their id. I could, so far, display the current values they have, such as amount and name, but after that, I don't know what to do. I'm imagining I need to use my AccountSerializer, but I'm not sure.

The easiest way would be to override the save method of your Transaction model:
class Transaction(models.Model):
user = models.ForeignKey(User, default=None)
account = models.ForeignKey(Account, default=None)
...
def save(self, *args, **kwargs):
super(Transaction, self).save(*args, **kwargs)
# Update self.account
See Django documentation for details.

Related

How to send several fields in the response with a PUT request?

I would like when my PUT request is successful it returns me a response with all the fields in my PlantSerializer because currently the response returns me this:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
7
]
}
But, I would like the response to return this instead:
{
"id":48,
"name":"Jar",
"width":"50",
"height":"50",
"exposure":"None",
"qr_code":"",
"garden":65,
"plant":[
"id":7,
"name":"Artichoke",
"image":null
]
}
How can I achieve this result?
Here is my serializers and my model class :
class Plot(models.Model):
name = models.CharField(max_length=50)
garden = models.ForeignKey('perma_gardens.Garden', on_delete=models.CASCADE)
width = models.CharField(max_length=50, blank=True, null=True)
height = models.CharField(max_length=50, blank=True, null=True)
plant = models.ManyToManyField('perma_plants.Plant', related_name='%(class)s_plant', blank=True)
# Here is my serializers :
class GardenSerializer(serializers.ModelSerializer):
class Meta:
model = Garden
fields = ('id', 'name',)
class PlantSerializer(serializers.ModelSerializer):
class Meta:
model = Plant
fields = ('id', 'name', 'image')
class ReadPlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True)
plant = PlantSerializer(many=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'
read_only_fields = [fields]
class WritePlotSerializer(serializers.ModelSerializer):
class Meta:
model = Plot
fields = '__all__'
And here is my views :
class PlotViewSet(viewsets.ModelViewSet):
queryset = Plot.objects.all()
def create(self, request, *args, **kwargs):
serializer = WritePlotSerializer(data=request.data, many=isinstance(request.data, list))
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def partial_update(self, request, *args, **kwargs):
instance = self.queryset.get(pk=kwargs.get('pk'))
serializer = WritePlotSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def get_serializer_class(self):
if self.action in ("list", "retrieve"):
return ReadPlotSerializer
return WritePlotSerializer
In the fuction partial_update you are utilizing the WritePlotSerializer, which only has the plant field implicitly through the fields=__all__ value. This is probably causing drf to use a PrimaryKeyRelatedField, and as such you don't get all the extra fields you defined in your PlantSerializer.
If I understand correctyl you want to use the WritePlotSerializer in the update but use the ReadPlotSerializer when returning the object. You probably should combine both of them in a single serializer by overriding the update method in order to support updating the nested Plant objects. Here is the related documentation.
Alternatively, if you don't want to update the Plants values you can use a slightly modified version of the ReadPlotSerializer in all calls:
class PlotSerializer(serializers.ModelSerializer):
garden = GardenSerializer(required=True, read_only=True)
plant = PlantSerializer(many=True, read_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = Plot
fields = '__all__'

Rest Framework cant save serializer with foreign key

I have next serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer()
class Meta:
model = Post
fields = ['id', 'title', 'text', 'date', 'category']
And here is my view:
#api_view(['POST'])
def create_post(request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors)
return Response(serializer.data)
I want to create new Post object, but when I pass an category id at form it does not work, it is not saving my object. I tried to replace create method at my PostSerializer, to this:
def create(self, validated_data):
category_id = validated_data.pop('category')
post = Post.objects.create(**validated_data, category=category_id)
return post
but this dont work. Using postman formdata it is saying, that category field is required despite I filled it.
Here is my models:
class Category(models.Model):
name = models.CharField(max_length=512)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=512)
text = models.TextField()
date = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category')
You need a category object not just an id, so try this
#api_view(['POST'])
def create_post(request):
category_id = request.data['category'] # or however you are sending the id
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
category = Category.objects.get(id=category_id)
serializer.save(category=category)
else:
return Response(serializer.errors)
return Response(serializer.data)
or you can do something similar in the create method of the serializer

DRF: how to change the value of the model fields before saving to the database

If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.
For example fields email I need set to lowercase and fields nda I need set as null
models.py
class Vendors(models.Model):
nda = models.DateField(blank=True, null=True)
parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)
def clean(self):
if self.nda == "":
self.nda = None
class VendorContacts(models.Model):
....
vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
email = models.CharField(max_length=80, blank=True, null=True, unique=True)
def clean(self):
if self.email:
self.email = self.email.lower()
serializer.py
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
...
'email',)
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
As I learned from the documentation
Django Rest Framework serializers do not call the Model.clean when
validating model serializers
In dealing with this problem, I found two ways to solve it.
1. using the custom method at serializer. For my case, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
def validate(self, attrs):
instance = Vendors(**attrs)
instance.clean()
return attrs
Using full_clean() method. For me, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors(**validated_data)
vendor.full_clean()
vendor.save()
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.
In my case I had the same problem but with validation feature
I used the way below and it works for me (not excludes the way found above):
class CustomViewClass(APIView):
def post(self, request, format=None):
prepared_data_variable = 'some data in needed format'
serializer = CustomSerializer(data=request.data)
if serializer.is_valid(self):
serializer.validated_data['field_name'] = prepared_data_variable
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable
For DRF you can change your serializer before save as below...
First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.
if serializer.is_valid():
serializer.object.user_id = 15 # For example
serializer.save()
UPD!
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
data = request.data
if data['nda'] == '':
data['nda'] = None
for contact in data['contacts']:
if contact['email']:
print(contact['email'])
contact['email'] = contact['email'].lower()
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.
Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.

Implement update method on a Nested Serializer in Django 1.11 and django _rest framework

I Implemented a class based view in the views.py though when I tried to update an employee I realized that it's like I'm trying to create new one yet I have the PUT method defined. I have have an issue updating the user details since user field is a Foreign key.
A user with that username already exists.
Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.
views.py
class EmployeeDetailView(APIView):
permission_classes = [AllowAny]
# queryset = Employee.objects.all()
# serializer_class = EmployeeSerializer
"""
Retrieve, update or delete a employee instance.
"""
def get_object(self, pk):
try:
return Employee.objects.get(pk=pk)
except Employee.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
employee = self.get_object(pk)
serializer = EmployeeSerializer(employee)
return Response(serializer.data)
def put(self, request, pk, format=None):
employee = self.get_object(pk)
serializer = EmployeeSerializer(employee, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class EmployeeSerializer(serializers.ModelSerializer):
user = UserSerializer()
contract_type = ContractSerializer(read_only=True)
company = CompanySerializer(read_only=True)
job_title = JobSerializer(read_only=True)
department = DepartmentSerializer(read_only=True)
skill = SkillSerializer(read_only=True)
unit = UnitSerializer(read_only=True)
class Meta:
model = Employee
fields = ['id', 'user', 'hr_number', 'contract_type', 'company',
'tax_id_number', 'joining_date', 'job_title', 'skill', 'unit',
'department', 'identification_number', 'is_manager', 'active']
For writable nested serializer you need to define update or create methods:
class EmployeeSerializer(serializers.ModelSerializer):
user = UserSerializer()
contract_type = ContractSerializer(read_only=True)
company = CompanySerializer(read_only=True)
job_title = JobSerializer(read_only=True)
department = DepartmentSerializer(read_only=True)
skill = SkillSerializer(read_only=True)
unit = UnitSerializer(read_only=True)
class Meta:
model = Employee
fields = ['id', 'user', 'hr_number', 'contract_type', 'company',
'tax_id_number', 'joining_date', 'job_title', 'skill', 'unit',
'department', 'identification_number', 'is_manager', 'active']
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
if user_data:
instance.user.first_name = user_data.get('first_name')
instance.user.last_name = user_data.get('last_name')
# update other user's fields here
instance.user.save()
employee = super(EmployeeSerializer, self).update(instance, validated_data)
return employee

NOT NULL constraint failed: device_api_devicegroup.owner_id

I am getting not null contraint failed error while posting a group. How should i fix it? I don't want to show the user in the api so i have not used it in the serializer fields. Do i have to compulsorily add it there?
Here is my model, serializer and APIView
class DeviceGroup(models.Model):
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
name = models.CharField(max_length=250, blank=False, null=False)
owner = models.ForeignKey(User, blank=False, null=False)
class DeviceGroupSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = DeviceGroup
fields = ['id','name']
class DevicesGroupsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = DeviceGroupSerializer
def get_object(self, user, token):
try:
return BaseDevice.objects.filter(owner=user).get(token=token)
except ObjectDoesNotExist:
return error.RequestedResourceNotFound().as_response()
def get(self, request, format=None):
"""
Returns a list of groups
"""
reply = {}
try:
groups = DeviceGroup.objects.filter(owner=request.user)
reply['data'] = DeviceGroupSerializer(groups, many=True).data
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
def post(self, request, format=None):
"""
create a new group
"""
print('request.data', request.data)
print('user', request.user)
serializer = DeviceGroupSerializer(data=request.data)
print('serializer', serializer)
if serializer.is_valid():
serializer.save()
return Response(serializers.data, status.HTTP_200_OK)
return Response(serializer.errors, status.HTTP_204_NO_CONTENT)
see this carefully, user is not in request.data:
serializer = DeviceGroupSerializer(data={
'name':request.data['name'],
'owner':request.user.id,
})
also check that the serializer allows the owner to be used
from django.contrib.auth.models import User
class DeviceGroupSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = DeviceGroup
fields = ['id','name', 'owner']

Categories

Resources