MultipartParser is not validating data in Django Rest Framework - django

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

Related

Django REST Framework: How do you get the attributes from a serialized object?

I have a POST method which is going to be used to retrieve a JSON object, which is then going to be used to retrieve the first_name, last_name, and username -- although I can't figure out how to get the fields (i.e. username) after I serialize it. What's the best way to go about that?
views.py
#api_view(['POST'])
def createUser(request):
# Making a Connection w/ MongoClient
client = MongoClient('mongodb+srv://test_user:0FP33TLJVWrjl8Vy#cluster0.5sacp.mongodb.net/sample_clubs?retryWrites=true&w=majority')
# Getting the Database
db = client['sample_clubs']
# Getting the Collection/Table
collection = db['users']
serializer = MyUserSerializer(data=request.data)
# Gives bug if next 2 lines aren't here
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
serializers.py
class MyUserSerializer(serializers.ModelSerializer):
def get_first_name(self, obj):
# obj is model instance
return obj.first_name
def get_last_name(self, obj):
# obj is model instance
return obj.last_name
def get_user_name(self, obj):
# obj is model instance
return obj.user_name
class Meta:
model = MyUser
fields = ['first_name', 'last_name', 'username']
# fields = '__all__'
models.py
class MyUser(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
username = models.CharField(max_length=200)
def __str__(self):
return self.username
A serializer's save method in DRF will return the instance that has been saved. So you can simply call any of its field like this:
if serializer.is_valid():
obj = serializer.save()
print(obj.user_name)
The data will also be available through the serializer's validated data:
if serializer.is_valid():
print(serializer.validated_data.get('user_name')
You can also use the raw JSON that's been generated by serializer:
# note that serializer.data won't be available if 'is_valid()` returns False
print(serializer.data["user_name"])
Also, you shouldn't return serializer.data outside of the is_valid scope. If is_valid() is False, then there won't be any data so you will run to an error. The proper way would be this:
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
If you only want to return the user_name as response:
if serializer.is_valid():
obj = serializer.save()
return Response({"username": obj.user_name})
return Response(serializer.errors)

How to implement create and update in one API view.?

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)

Not to pass ID in URL - UpdateApiView

I want to make update using my API. For that i am passing id in URL which is pk.
http://localhost:8000/api/manager/update/96
Where 96 is primary key. Now instead of passing id in url i want to pass id in body and update data. My url should look like this
http://localhost:8000/api/manager/update
Views.py
class ManagerUpdateAPIView(APIView):
def post(self, request, pk, *args, **kwrgs):
user = get_object_or_404(User, id=pk)
userprofile = get_object_or_404(UserProfile, user=pk)
serializer1 = EmployeeRegisterSerializer(user, data=request.data)
serializer2 = EmployeeProfileSerializer(userprofile, data=request.data)
user_role = ACLRoles.objects.get(id=4)
if serializer1.is_valid() and serializer2.is_valid():
serializer1.save()
serializer2.save()
return Response(status=status.HTTP_200_OK)
print(serializer1.errors)
print(serializer2.errors)
return Response(status=status.HTTP_404_NOT_FOUND)
Serializers.py
class EmployeeProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = [
'user_employee_id',
'user_phone',
'user_payroll_id',
'user_hire_date',
'user_pay_rate',
'user_salaried',
'user_excempt',
'user_groups',
'user_state',
'user_city',
'user_zipcode',
'user_status',
]
class EmployeeRegisterSerializer(serializers.ModelSerializer):
# userprofile = EmployeeProfileSerializer(read_only=True)
class Meta:
model = User
fields = ['first_name','last_name', 'email',]
How can i update data without passing id in url.
Try this code
class ManagerUpdateAPIView(APIView):
def post(self, request, *args, **kwrgs): #change is here
pk = request.data['pk'] #change is here
user = get_object_or_404(User, id=pk)
userprofile = get_object_or_404(UserProfile, user=pk)
serializer1 = EmployeeRegisterSerializer(user, data=request.data)
serializer2 = EmployeeProfileSerializer(userprofile, data=request.data)
user_role = ACLRoles.objects.get(id=4)
if serializer1.is_valid() and serializer2.is_valid():
serializer1.save()
serializer2.save()
return Response(status=status.HTTP_200_OK)
print(serializer1.errors)
print(serializer2.errors)
return Response(status=status.HTTP_404_NOT_FOUND)
and provide pk in your POST payload as
{
"pk":96,
"other_data":"other data of your usual payload"
}

Can I make a serializer fail silently?

I have to serializer a list of email addresses. If one of them contains a wrong character (in one case, it was a ":" at the end of the address) my serializer throws an error, rejects serializing the whole set of addresses and returns a HTTP 400. Is there a way to "pop" the faulty email address from the list but still serialize the remaining correct addresses?
View:
#action(detail=False, methods=['post'])
def match(self, request):
serializer = FriendFinderSerializer(data=request.data, many=True)
if serializer.is_valid():
contacts = serializer.validated_data
matched, unmatched = self.match_contacts(contacts)
serialized_matched = FriendFinderSerializer(matched, many=True)
serialized_unmatched = FriendFinderSerializer(unmatched, many=True)
data = {
'matched': serialized_matched.data,
'unmatched': serialized_unmatched.data,
}
return Response(data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Serializer:
class FriendFinderSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
image = serializers.ImageField(required=False)
record_id = serializers.CharField()
phone_numbers = serializers.ListField(child=serializers.CharField(), required=False)
email_addresses = serializers.ListField(child=serializers.EmailField(), required=False)
relationship_id = serializers.CharField(required=False)
relationship_status = serializers.CharField(required=False)
A place to start would be to iterate over request.data and handle each element within a loop. This is definitely a break from the norm and you'll need to determine how to handle cases where there is good and bad data.
#action(detail=False, methods=['post'])
def match(self, request):
successful_data = []
error_data = []
for element in request.data:
serializer = FriendFinderSerializer(data=element, many=False)
if serializer.is_valid():
contacts = serializer.validated_data
matched, unmatched = self.match_contacts(contacts)
serialized_matched = FriendFinderSerializer(matched, many=True)
serialized_unmatched = FriendFinderSerializer(unmatched, many=True)
successful_data.append({
'matched': serialized_matched.data,
'unmatched': serialized_unmatched.data,
})
else:
error_data.append(serializer.errors)
# Determine what status to return and how to handle successes and errors.
Personally, I'd either make smaller requests rather than posting all the data or handle the case in which an error in one FriendFinderSerializer causes all to fail. What you're attempting to do is likely to cause you more pain than the other options.
This is kind of a hack, but it works.
You need to define a custom field and there you need to override to_internal_value method of the field. Like this:
class CustomField(serializers.Field):
def __init__(self, custom_validation, *args, **kwargs):
self.custom_validation=custom_validation # <-- Here pass your custom validation regex
super(CustomField, self).__init__(*args, **kwargs)
def to_representation(self, obj):
return obj
def to_internal_value(self, obj):
try:
match = re.search(self.custom_validation, obj) # <-- validate the value against regex
if match:
return obj
except Exception as e:
pass
return None # <-- If exception occurs, return None
Or override the EmailField like this:
class CustomEmailField(serializer.EmailField):
def run_validation(self,*args, **kwargs):
try:
return super(CustomEmailField, self).run_validation(*args,**kwargs) # <-- Runs validation, but it fails, returns None
except:
return None
And use it in the Serializer like this:
email = CustomField(r"(^[a-zA-Z0-9_.+-]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)") # regex for email
Or
email = CustomEmailField()
And if you want to pop the invalid values from ListField, then you can override to_representation like this:
class CustomListField(serializers.ListField):
def to_representation(self, data):
childs = super(CustomListField, self).to_representation(data)
new_childs = []
for child in childs:
if child:
new_childs.append(child)
return new_childs
Then, use it in the Serializer:
email_addresses = CustomListField(child=CustomEmailField(), required=False)

Django rest framework : POST on many to many items

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)