Django Rest Framework PUT request on unique model field - django

I have the following model
class Owner(models.Model):
user = models.OneToOneField(User, default=1, editable=True)
phone = models.CharField(max_length=40, null=True, blank=True)
address = models.CharField(max_length=255, null=True, blank=True)
city = models.CharField(max_length=255, null=True, blank=True)
state = USStateField(null=True, blank=True)
zip = models.CharField(max_length=20, null=True, blank=True)
def __str__(self):
return "%s %s" % (self.user.first_name, self.user.last_name)
class Device(CreationModificationMixin):
_STATUSES = (
('A', 'Active'),
('I', 'Inactive'),
('F', 'Failure'),
)
_TYPES = (
('S', 'Spa'),
('P', 'Pool'),
)
udid = models.CharField(max_length=255, verbose_name="Unique ID / MAC Address", null=False, blank=False, unique=True)
type = models.CharField(max_length=1, choices=_TYPES, null=False, blank=False)
title = models.CharField(max_length=255, null=False, blank=False)
status = models.CharField(max_length=1, default='A', choices=_STATUSES)
pinged = models.DateTimeField(null=True)
owner = models.ForeignKey(Owner, verbose_name="Owner", null=True, blank=True)
def __str__(self):
return self.udid
I have the following serializer
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ('id', 'udid', 'title', 'type', 'status', 'pinged', 'created')
I have the following API View defined:
class DeviceAPIView(APIView):
permission_classes = (IsAuthenticated,) # explicit
code_404 = "Device doesn't exists"
def get(self, request, device_id):
try:
d = Device.objects.get(id=device_id, owner=request.user.owner)
except Device.DoesNotExist:
return Response({'error': self.code_404}, 404)
serializer = DeviceSerializer(d)
return Response(serializer.data)
def put(self, request, device_id):
serializer = DeviceSerializer(data=request.DATA)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
data = serializer.data
data['id'] = id
d = Device(**data).save()
serializer = DeviceSerializer(d)
return Response(serializer.data, status=status.HTTP_200_OK)
PUT request on existing device
{
"udid": "38-2C-4A-47-C2-ED",
"title": "Backyard pool",
"type": "S"
}
gives me back
{
"udid": ["This field must be unique."]
}
However I'm updating the record and passing the same UDID it has. So I'm not getting a duplicate in DB but DRF thinks the other way.
What I need to achieve is
If UDID of the same record is not changed - then no error should be raised
if UDID of the record changes and now it's the same as some of record's UDID then error should be returned.

As per the comments implementing the put method closer to the reference implementation in the docs should fix the issue.
def put(self, request, pk, format=None):
device = self.get_object(pk)
serializer = DeviceSerializer(device, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Saving instances has a bit more information on creating, updating and saving instances by using the serializer class methods.

Related

Error in the POST method of Writable Nested serializers in django with form data

models.py
class Client(models.Model):
client_id = models.CharField(max_length=50,default=uuid.uuid4, editable=False, unique=True, primary_key=True)
org = models.ForeignKey(Organisation, on_delete=models.CASCADE, related_name='org',null=True)
product = models.ManyToManyField(Product,related_name='product')
client_name = models.CharField(unique=True,max_length=100)
client_code = models.CharField(unique=True,max_length=20)
client_logo = models.ImageField(upload_to=upload_to, null=True, blank=True,)
currency = models.IntegerField(null=True)
currency_type = models.CharField(max_length=100,choices=CURRENCY_CHOICES,default='Indian Rupee')
billing_method = models.ForeignKey(Billing_Method, on_delete=models.CASCADE, related_name='client_billingmethod', null=True)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
email_id = models.EmailField(max_length=100)
contact_no = models.CharField(max_length=20)
class Billing_Method(models.Model):
billing_id = models.CharField(max_length=50, default=uuid.uuid4, editable=False, unique=True, primary_key=True)
billing_name = models.CharField(max_length=50)
description = models.TextField(max_length=250)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
serializers.py
class Billingmethod_Serializers(serializers.ModelSerializer):
class Meta:
model = Billing_Method
fields = ('billing_id', 'billing_name', 'description')
class Clientpost_Serializers(serializers.ModelSerializer):
billing_method = Billingmethod_Serializers()
def create(self, validated_data):
billing_method_data = validated_data.pop('billing_method')
billing_method = Billing_Method.objects.create(**billing_method_data)
client = Client.objects.create(billing_method=billing_method,**validated_data)
return client
class Meta:
model = Client
fields = ('client_id','currency','currency_type','billing_method','first_name','last_name',...)
view.py
class Clientlist(APIView):
renderer_classes = (CustomRenderer,)
parser_classes = [parsers.MultiPartParser, parsers.FormParser]
def get(self, request, format=None):
clients = models.Client.objects.all()
serializer = serializers.Client_Serializers(clients, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = serializers.Clientpost_Serializers(data=request.data)
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)
When i was trying to do POST in client I was getting an errro as
"
TypeError at /api/onboarding/client/
django.db.models.manager.BaseManager._get_queryset_methods..create_method..manager_method() argument after ** must be a mapping, not list
Request Method: POST
Request URL: http://127.0.0.1:8000/api/onboarding/client/
Django Version: 3.2.12
Exception Type: TypeError
Exception Value:
django.db.models.manager.BaseManager._get_queryset_methods..create_method..manager_method() argument after ** must be a mapping, not list
Exception Location: F:\PM-Onboarding-Service\Onboarding-Service\microservices\onboarding\serializers.py, line 34, in create
"
I was doing a Post method in form data as below,
Please help me to solve this error, and let me know how to post the billing method in the form data.
I think you uploaded billing_method as a list in POST API.
But in serializer, you defined it as not a list, but a dictionary.
So you should upload billing _method as an object in postman like the
following.
{
...
"client_code": "...",
"product": "...",
"billing_method": {
"billing_name": "...",
"description": "...",
...
}
}

Django Rest Framework partial=True not working

I am a beginner in Django & DRF and I have a very dumb problem with my project, which doesn't allow me to do the partial update. I am using generic views (UpdateAPIView) and I have been stuck with it for 2 weeks now.
It allows me to update, however, I have to fill every field but what I want to do is to pop email & mobile numbers if they are the same as the stored value in the database.
Hoping someone might help and thank you in advance.
Model:
class BusinessPartner(models.Model):
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150)
mobile_number = models.CharField(max_length=150, unique=True)
email = models.EmailField(max_length=150, unique=True)
business_name = models.CharField(max_length=255, blank=True)
type_of_business = models.CharField(max_length=150, blank=True)
street_address = models.CharField(max_length=255, blank=True)
city = models.CharField(max_length=150, blank=True)
state_province = models.CharField(max_length=150, blank=True)
postal_zip = models.IntegerField(null=True, blank=True)
services_offered = models.TextField(null=True, blank=True)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
class Meta:
ordering = ["-account"]
def __str__(self):
return self.first_name + " " + self.last_name
Serializer:
class BusinessPartnerSerializer(serializers.ModelSerializer):
class Meta:
model = BusinessPartner
fields = [
"id",
"first_name",
"last_name",
"mobile_number",
"email",
"business_name",
"type_of_business",
"street_address",
"city",
"state_province",
"postal_zip",
"services_offered",
"account",
]
extra_kwargs = {"id": {"read_only": True}}
def update(self, instance, validated_data):
partner = BusinessPartner.objects.get(account=instance)
print("Previous mobile number: ", getattr(partner, "mobile_number"))
print("New mobile number: ", validated_data["mobile_number"])
if getattr(partner, "mobile_number") == validated_data["mobile_number"]:
validated_data.pop("mobile_number")
if getattr(partner, "email") == validated_data["email"]:
validated_data.pop("email")
for (key, value) in validated_data.items():
setattr(partner, key, value)
partner.save()
return partner
View:
class UpdatePartnerProfileView(generics.UpdateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = BusinessPartnerSerializer
queryset = BusinessPartner.objects.all()
def update(self, request, *args, **kwargs):
serializer = self.serializer_class(
request.user, data=request.data, partial=True
)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.error, status=status.HTTP_400_BAD_REQUEST)

ValueError: Cannot assign "2": "Model.field" must be a "Model" instance

I have a registration system that works on otp.
I have a custom user model
class User(AbstractUser):
password = models.CharField(max_length=128, blank=True, null=True)
email = models.EmailField(max_length=254, unique=True)
dial_code_id = models.CharField(max_length=100)
mobile_number = models.CharField(max_length=100, blank=True, null=True)
username = models.CharField(max_length=150, unique=True, blank=True, null=True)
is_resource = models.BooleanField(default=False)
is_customer = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
skills = models.ManyToManyField(Skills)
class Meta:
db_table = "my_user"
def __str__(self):
return self.mobile_number
I have created an 'otp' model to save otp.
class Otp(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
otp = models.IntegerField()
created_on = models.DateTimeField(default=django.utils.timezone.now)
class Meta:
db_table = "otp"
My views looks like this
class UserCreateResponseModelViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
custom_data = {
"status": True,
"message": 'Successfully registered your account.',
"data": serializer.data
}
generate_otp(serializer.data['id'])
return Response(custom_data, status=status.HTTP_201_CREATED)
else:
custom_data = {
"status": False,
"message": serializer.errors,
}
return Response(custom_data, status=status.HTTP_200_OK)
class UserCreateViewSet(UserCreateResponseModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
I have a function to generate and save otp
def generate_otp(user_id):
try:
otp_obj = Otp.objects.get(user_id=user_id)
except Otp.DoesNotExist:
otp_obj = None
if otp_obj is None:
otp = random.randrange(1000, 9999)
otp_obj = Otp(user=user_id, otp=otp)
otp_obj.save()
Serializer looks like
class UserSerializer(serializers.ModelSerializer):
mobile_number = serializers.CharField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'email', 'dial_code_id','mobile_number', 'is_resource', 'is_customer']
The user is being registered successfully, but when it comes to saving otp in 'generate_otp()' I'm getting an error like
raise ValueError(ValueError: Cannot assign "2": "Otp.user" must be a "User" instance.
How can I overcome this?
Is this the right way to do it?
It looks like the problem is here:
otp_obj = Otp(user=user_id, otp=otp)
you're assigning an id (int) to the user field. not the user_id field

Saving data once (to be able to load to images to server) then updating the same instance after some changes

I'm saving the data from the post request a first time because I need the images to be uploaded to the server like the "upload_image" function suggests.
At the same time I have a couple of fields that are null and need to have some values via some external functions.
And then I need to save the whole object in the database.
The problem that I'm facing is that it saves the data twice in the database.
And the first saved object has all attributes null except for passengerPhoto, passengerPassport and agent. ( No errors shown btw)
Any ideas, please?
Thanks a lot!
#VIEW
class IdentityCheckView(CreateAPIView, generics.ListAPIView):
serializer_class = IdentityCheckSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self):
request = self.request
qs = IdentityCheck.objects.all()
query = self.request.GET.get('q')
if query is not None:
qs = qs.filter(name__icontains=query)
return qs
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if(serializer.is_valid()):
res = self.create(request, *args, **kwargs)
image = FaceRecognition.imageMatch(res.data['passengerPhoto'], res.data['passengerPassport'])
wanted = WantedPro.criminalMatch(res.data['passengerPhoto'])
passport_json = OCR.passportMatch(res.data['passengerPassport'])
image_json = json.loads(image)
firstName = passport_json['names']
lastName = passport_json['surname']
nationality = passport_json['country']
birthDate = passport_json['date_of_birth']
gender = passport_json['sex']
ableToBoard = bool(wanted) & bool(image_json['match']) & bool(passport_json['valid_expiration_date'])
serializer.update(
id=res.data['id'],
firstName=firstName,
lastName=lastName,
nationality=nationality,
birthDate=birthDate,
gender=gender,
ableToBoard=ableToBoard)
return Response({"image": image_json, "passport": passport_json, "wanted": wanted}, status=200)
def perform_create(self, serializer):
res = serializer.save(agent=self.request.user)
#SERIALIZER
class IdentityCheckSerializer(serializers.ModelSerializer):
class Meta:
model = IdentityCheck
fields = '__all__'
read_only_fields = ['agent', 'id']
#MODEL
def upload_image(instance, filename):
return "media/check/{agent}/{date}/{filename}".format(agent=instance.agent,date=datetime.datetime.today().strftime('%d-%m-%Y'), filename=filename)
class IdentityCheckQuerySet(models.QuerySet):
pass
class IdentityCheckManager(models.Manager):
def get_queryset(self):
return IdentityCheckQuerySet(self.model,using=self._db)
class IdentityCheck(models.Model):
agent = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
passengerPhoto = models.ImageField(upload_to=upload_image, null=False, blank=False)
passengerPassport = models.ImageField(upload_to=upload_image, null=False, blank=False)
lastName = models.CharField(max_length=255, null=True, blank=True)
firstName = models.CharField(max_length=255, null=True, blank=True)
birthDate = models.DateField(null=True, blank=True)
nationality = models.CharField(max_length=255, null=True, blank=True)
gender = models.CharField(max_length=1, null=True, blank=True)
ableToBoard = models.BooleanField(null=False, blank=False, default=False)
timestamp = models.DateTimeField(auto_now_add=True)
objects = IdentityCheckManager()

How to pass a json to a serializer to store in model

my record/model.py is,
class HistoricalRecords(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE, null=True, blank=True)
role = models.CharField(max_length=255, null=True, blank=True)
model = models.CharField(max_length=255, null=True, blank=True)
torque = models.IntegerField(null=True, blank=True)
car = models.TextField(null=True, blank=True)
date_time = models.DateTimeField(default=timezone.now)
my record/serializer.py is
class SaveAuditRecordSerializer(serializers.ModelSerializer):
class Meta:
model = HistoricalRecords
fields = ('user', 'role', 'model', 'torque', 'car', 'date_time')
and my record/views.py is,
def AuditRecord(request):
serializer = SaveAuditRecordSerializer(data=request, partial=True)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors,
status=status.HTTP_201_CREATED)
I am trying to call this AuditRecord(req) from another view of another app, like
rec = {}
audit_record['user'] = request.user
audit_record['role'] = "Role"
audit_record['model'] = "M"
audit_record['torque'] = 22222
audit_record['action'] = "car created"
audit_record['date_time'] = datetime.now()
AuditRecord(audit_record)
I dont get any error but it is not getting saved to db
is there any mistake in my approch?
If there's a model in your other app that points to your AuditRecord and you wanna create the record on it's create enpoint, you can use a serializer of this other app to create an instance of AuditRecord.
OtherSerializer(serializers.Serializer):
audit_record = AuditRecordSerializer(required=False, write_only=True)
def create(self, validated_data):
audit_record_data = validated_data.pop('audit_record')
audit_record = AuditRecordSerializer().create(audit_record_data)
validated_data.update({'audit_record': audit_record})
return super(OtherSerializer, self).create(validated_data)