I have a model called CarDetailsAdd and VehicleDetails (field - description), VehicleDetails is a Foreignkey to CarDetailsAdd. I am using Nested Relationship in this serializer. Using this update function, I can update existing vehicle details. Add and update runs on the same screen, depending on the UI. If the user has updated the field, it should be updated or user has added a new field, it should be added. How do I do that if the user updates and adds at the same time.?
# Serializer
class CarDetailsSerializer(serializers.ModelSerializer):
vehicle_details = VehicleDetailsSerializer(many=True)
class Meta:
model = CarDetailsAdd
fields = (
'id', 'name', 'year', 'color', 'fuel_type',
'vehicle_details',
)
read_only_fields = ['id']
def update(self, instance, validated_data):
vehicle_data = validated_data.pop('vehicle_details')
vehicle = (instance.vehicle_details).all()
vehicle = list(vehicle)
instance.name = validated_data.get('name', instance.name)
instance.year = validated_data.get('year', instance.year)
instance.color = validated_data.get('color', instance.color)
instance.fuel_type = validated_data.get('fuel_type', instance.fuel_type)
instance.save()
for vehicle_data in vehicle_data:
vehicle = vehicle.pop(0)
vehicle.description = vehicle_data.get('description', vehicle.description)
vehicle.save()
return instance
# View
class CarDetailsView(APIView):
permission_classes = [IsAuthenticated]
def put(self, request, pk, format=None):
try:
car = self.get_object(pk)
serializer = CarDetailsSerializer(car, data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'response': 'Update Success', 'result': serializer.data})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except:
return Response({'response': 'Oops! Something Went Wrong'}, status=status.HTTP_400_BAD_REQUEST)
Related
I am trying to update a foreign-field with a PUT request on a resource. My serializer.data and the http response is correct after callig .is_valid, but the object doesn't get updated.
View
def put(self, request, user_pk):
try:
userById = getUserById(user_pk)
except ChatUser.DoesNotExist:
raise Http404
serializer = ChatUserInputSerializer(userById, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Serializer
class IdentificationInputSerializer(serializers.ModelSerializer):
class Meta:
model = Identification
fields = "__all__"
read_only_fields = ["id"]
class ChatUserInputSerializer(serializers.ModelSerializer):
identification = IdentificationInputSerializer()
class Meta:
model = ChatUser
fields = ["id", "userId", "identification"]
read_only_fields = ["id", "userId"]
def update(self, instance, validated_data):
identification = validated_data.pop('identification')
instance.identification.salutation = identification.get('salutation', instance.identification.salutation)
instance.identification.firstname = identification.get('firstname', instance.identification.firstname)
instance.identification.name = identification.get('name', instance.identification.name)
instance.identification.street = identification.get('street', instance.identification.street)
instance.identification.plz = identification.get('plz', instance.identification.plz)
instance.identification.city = identification.get('city', instance.identification.city)
instance.identification.country = identification.get('country', instance.identification.country)
instance.save()
return instance
I pretty sure that django won't save related models when you call .save().
Try instance.identification.save()
I am using a multipart/form-data in a form which have a manytomany relation as well as multiple file upload. But the validated data doesn't contains the array data
Views.py
class ExpenseCreateAPIView(CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
parser_classes = ( MultiPartParser,)
def post(self, request, *args, **kwargs):
owner = request.user.pk
d = request.data.copy()
d['owner'] = owner
serializer = ExpenseSerializer(data=d)
print("exp")
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)
Serializers.py
class ExpenseSerializer(serializers.ModelSerializer):
transactions = ExpenseTransactionsSerializer(many=True, required=False)
bill = ExpenseFilesSerializer(many=True, required=False)
class Meta:
model = Expense
fields = "__all__"
def create(self, validated_data):
print("validated data", validated_data)
items_objects = validated_data.pop('transactions', None)
files_objects = validated_data.pop('bill', None)
prdcts = []
files = []
for item in items_objects:
i = ExpenseTransactions.objects.create(**item)
prdcts.append(i)
if files_objects == None:
pass
else:
for item in files_objects:
i = ExpenseFiles.objects.create(**item)
files.append(i)
instance = Expense.objects.create(**validated_data)
instance.transactions.set(prdcts)
instance.bill.set(files)
return instance
How else should I use the MultiPartParser class in the views ?
I keep getting the error:
TypeError: 'NoneType' object is not iterable
at
for item in items_objects:
Make sure you have transactions in validated_data. If transactions can empty or not required, just change:
validated_data.pop('transactions', None)
to
validated_data.pop('transactions', [])
It means that if transactions not in validated_data then pop [] (an empty list), instead of None, then empty list can keep iterate in next your code.
I think you need to try validated_data.get instead of validated_data.pop
I have a TransactionType model and I've implemented a viewset method to create transaction type as shown also below. Currently I can only post single credit_account or debit_account items as shown in this payload:
{"name":"Repair and Maintenance","credit_account":16,"debit_account":38}
I would to post multiple credit_accounts and debit_accounts such that my payload looks something like this:
{"name":"Repair and Maintenance","credit_account":[16,4,5],"debit_account":[38,7]}
Which is the efficient way of do this?
class TransactionType(models.Model):
name = models.CharField(max_length=255)
organization = models.IntegerField(null=True, blank=False)
credit_account = models.ManyToManyField(Account,related_name='credit_account', verbose_name="Account to Credit")
debit_account = models.ManyToManyField(Account,related_name='debit_account',verbose_name="Account to Debit")
def __str__(self):
return '{}'.format(self.name)
viewset method
def create(self, request, format=None):
name = request.data['name']
try:
trans_type_obj = TransactionType.objects.create(name=name,
credit_account=Account.objects.get(id=request.data['credit_account'
]),
debit_account=Account.objects.get(id=request.data['debit_account'
]), organization=get_auth(request))
serializer = CreateTransactionTypeSerializer(trans_type_obj)
except Exception, e:
raise e
return Response(data=serializer.data,
status=status.HTTP_201_CREATED)
Use ManyToManyField.add() as below,
def create(self, request, format=None):
name = request.data['name']
try:
trans_type_obj = TransactionType.objects.create(name=name, organization=get_auth(request))
trans_type_obj.credit_account.add(*[credit_obj for credit_obj in Account.objects.filter(id__in=request.data['credit_account'])])
trans_type_obj.debit_account.add(*[debit_obj for debit_obj in Account.objects.filter(id__in=request.data['debit_account'])])
serializer = CreateTransactionTypeSerializer(trans_type_obj)
except Exception, e:
raise e
return Response(data=serializer.data,
status=status.HTTP_201_CREATED)
UPDATE-1
as #Daniel Roseman said, it's also possible to do the same without list comperhension as
trans_type_obj.credit_account.add(*Account.objects.filter(id__in=request.data['credit_account']))
trans_type_obj.debit_account.add(*Account.objects.filter(id__in=request.data['debit_account']))
def create(self, request,*args, **kwargs):
name = request.data.pop('name')
credits = request.data.pop('credit_account')
debits = request.data.pop('debit_account')
try:
trans_type_obj = TransactionType.objects.create(name=name, organization=get_auth(request))
for item in credits:
trans_type_obj.credit_account.add(item)
for item in debits:
trans_type_obj.debit_account.add(item)
serializer = TransactionTypeSerializer(trans_type_obj)
except Exception as e:
raise e
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
you can create one serializer with following fields
class TransactionTypeSerializer(serializers.ModelSerializer):
credit_account = serializers.PrimaryKeyRelatedField(queryset=Account.objects.all(), many=True)
debit_account = serializers.PrimaryKeyRelatedField(queryset=Account.objects.all(), many=True)
class Meta:
model = TransactionType
fields = __all__
now in views
def create(self, request, *args, **kwargs):
serializer = TransactionTypeSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
Running into a little snag here with my DRF backend.
I am populating fields with choices on certain models.
I have a foreign key requirement on one model. When I create the model I want to save it under the foreign id.
When I request the models, I want the model with whatever the choice field maps to.
I was able to do this with SerializerMethodField, however when I try to create a model, I get a 400 error because the block is not valid. If I remove the SerializerMethodField, I can save, but get the number stored in the db from the request.
Any help would be appreciated.
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = serializers.BlockSerializer(data=data, context={'request': request})
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
class WorkoutGoalSerializer(serializers.ModelSerializer):
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
The above code returns the correct choice, but I can't save under it. Remove the goal = WorkoutGoalSerializer() and it saves but doesn't return the mapped choice.
I think this will work like a charm,
class WorkoutGoalSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' in self.context and self.context['request'].method == 'GET':
self.fields['goal'] = serializers.SerializerMethodField(read_only=True, source='get_goal')
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal') # remove this line
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
How this Work?
It will re-initiate the goal field with SerializerMethodField, if the reuested method is GET.
Remember one thing, you should remove the line,
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
serializers.py
class BlockCreateSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
views.py
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def get_serializer_class(self):
if self.action == 'create':
return serializers.BlockCreateSerializer
else:
return self.serializer_class
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = self.get_serializer(data=data)
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
override get_serializer_class to return different serializer_class for create and other action(list\retrieve\update\partial_update)
i am trying to update field in another model by serializer.py but i get this error:
duplicate key value violates unique constraint "sales_company_company_name_8f098bd8_uniq"
DETAIL: Key (company_name)=(fdsfasf) already exists.
serializer.py code:
Company model is a FK in Customer model
class CompanySerializer(serializers.ModelSerializer):
model = models.Company
fields = ('company_name',)
class CustomersIsCompanySerializer(serializers.ModelSerializer):
company = CompanySerializer
company_name = serializers.CharField(max_length=255)
def update(self, instance, validated_data):
instance.company_name = models.Company.objects.update(company_name=validated_data.get('company_name', instance.company_name))
instance.phone = validated_data.get('phone', instance.phone)
instance.mobile = validated_data.get('mobile', instance.mobile)
instance.email = validated_data.get('email', instance.email)
instance.website = validated_data.get('website', instance.website)
instance.save()
return instance
class Meta:
model = models.Customers
fields = (
'id',
'company_name',
'phone',
'mobile',
'email',
'website',
)
extra_kwargs = {
'phone': {'validators': []},
'mobile': {'validators': []},
'email': {'validators': []},
'website': {'validators': []},
}
and my view code :
class CustomerIsCompanyGetIdPutPatchView(generics.RetrieveAPIView,
mixins.UpdateModelMixin):
permission_classes = (AllowAny,)
queryset = models.Customers.objects.all()
serializer_class = CustomersIsCompanySerializer
def get_object(self):
id = self.kwargs['id']
obj = generics.get_object_or_404(User, id=id)
return obj
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
but if i replace update and but create it work fine but it make a new line in the DB not update the old line.
for example if i replace that code :
instance.company_name = models.Company.objects.update(company_name=validated_data.get('company_name', instance.company_name))
by this code :
instance.company_name = models.Company.objects.create(company_name=validated_data.get('company_name', instance.company_name))
it work fine but not updated the old , it create new
just i want update the old in the company model
i found the solution for this problem this is what i want:
instance.company_name, created = models.Company.objects.get_or_create(company_name=validated_data.get('company_name', instance.company_name))
i used here created and get_or_create to see if it exist before made it directly PUT and edit The FK of the exist Company Name OR If it not exist create new one and PUT the FK of the new company name