Field 'customer_id' expected a number but got <Customer: Lary> - django

I have these two models with their serializers:
class ChronicPrescription
chronic_prescription_id = models.AutoField(primary_key=True)
date = models.DateField(default=datetime.date.today, validators=[no_future_date, no_old_date])
# This field is for the prescription duration in days
duration = models.PositiveIntegerField(default=90, validators=[MaxValueValidator(90), MinValueValidator(30)])
customer = models.ForeignKey('customers.Customer', on_delete=models.CASCADE, related_name="chronic_prescription",
validators=[prescriptions_for_patient_only])
class Customer(models.Model):
id = models.BigAutoField(primary_key=True)
customer_name = models.CharField(max_length=100, null=False, blank=False, unique=True)
phones = ArrayField(models.CharField(max_length=10, validators=[validate_phone_number, prevent_replicated_phone]),
default=list, null=True, blank=True)
customer_type = models.CharField(max_length=10,default='patient', choices=CUSTOMER_TYPE)
The problem is when i try to create a prescription serializer during a unit test (the test suppose to fail due to duration, it should not exceed 90) during a unit test:
def test_upper_bound_duration(self):
customer = Customer.objects.create(customer_name="Lary")
prescr_serializer = ChronicPrescriptionSerializer(data={'duration': 1000, 'customer': customer.id})
if prescr_serializer.is_valid():
prescr_serializer.save()
self.assertFalse(prescr_serializer.is_valid())
self.assertEqual(set(prescr_serializer.errors), set(['duration']))
I got an unexpected error:
Field 'id' expected a number but got <Customer: Lary>.
Even i'm providing the customer id not the customer itself, What is weird though, it were all god, but suddenly it doesn't work anymore.
The Prescription serializer:
class ChronicPrescriptionSerializer(serializers.ModelSerializer):
drugs = PrescriptionItemSerializer(many=True, read_only=True)
notification_status = serializers.BooleanField(default=True)
left_days = serializers.SerializerMethodField()
def get_left_days(self, obj):
return obj.count_left_days()
class Meta:
model = ChronicPrescription
fields = ('chronic_prescription_id', 'date', 'duration', 'notification_status', 'left_days', 'drugs', 'customer')

Your code should be something like this:
def test_upper_bound_duration(self):
customer = Customer.objects.create(customer_name="Lary")
customer.save()
prescr_serializer = ChronicPrescriptionSerializer(data={'duration': 1000, 'customer': customer.id})
if prescr_serializer.is_valid():
prescr_serializer.save()
self.assertFalse(prescr_serializer.is_valid())
self.assertEqual(set(prescr_serializer.errors), set(['duration']))

I guess the root problem was in the validation function for ChronicPrescription model, that's why the code was working before it:
def prescriptions_for_patient_only(value):
customer = Customer.objects.get(pk=value)
if customer.customer_type != 'patient':
raise ValidationError('Only patient customer can have prescriptions.')
When we serialize the prescription the validation method got a customer instance as a parameter, that's why i got the error : expected a number but got Customer: Lary>. due to
customer = Customer.objects.get(pk=value)
So i refactor the validation method as follow:
def prescriptions_for_patient_only(value):
if isinstance(value, int):
customer = Customer.objects.get(pk=value)
else:
customer = value
if customer.customer_type != 'patient':
raise ValidationError('Only patient customer can have prescriptions.')
And it worked fine.

Related

Filtering output of foreign key data

Since I am making a online futsal booking system, i am currently doing timeslot validation. If a timeslot is already booked, the user should not be able to book that timeslot again.
models.py
class futsals(models.Model):
futsal_name = models.CharField(max_length=20)
futsal_address = models.CharField(max_length=40)
owner_email = models.EmailField(max_length=25)
owner_name = models.CharField(max_length=25)
def __str__(self):
return f'{self.futsal_name}'
class timeslot(models.Model):
timesslot = models.CharField(max_length=15)
name = models.CharField(max_length=15)
def __str__(self):
return f'{self.timesslot}'
class Booking(models.Model):
user_book = models.ForeignKey(User, on_delete=models.CASCADE)
futsal = models.ForeignKey(futsals, on_delete=models.CASCADE)
time_slot = models.ForeignKey(timeslot, on_delete=models.CASCADE)
def validate_date(date):
if date < timezone.now().date():
raise ValidationError("Date cannot be in the past")
booking_date = models.DateField( default=None, validators=[validate_date])
def __str__(self):
return f'{self.user_book}'
forms.py
def timeslot_validation(value):
v = Booking.objects.all().values_list('time_slot')
k = timeslot.objects.filter(pk__in=v)
if value == k:
raise forms.ValidationError("This timeslot is already booked!!")
else:
return value
But I am not able to do the validation. Since the output of variable 'k' looks like:
<QuerySet [<timeslot: 19:00 - 20:00>, <timeslot: 18:00 - 19:00>, <timeslot: 17:00 - 18:00>]>
The above shown timeslot is the timeslot booked by users. Now if another user enters this timeslot, it should show 'this timeslot is already booked.'
Now, I want this data to be shown as
[(19:00 - 20:00), (18:00 - 19:00), (17:00 - 18:00)]
Any help would be appreciated, or if anyone could provides me a better solution for validation?
You can add a unique constraint to the time_slot and booking_date field and your ModelForm will handle validating that the time_slot and booking_date is unique
class Booking(models.Model):
class Meta:
unique_together = (
('booking_date', 'time_slot'),
)

Django Rest Framework - get foreignkey id from value when inserting

I can not get a clear answer after two days of searching for what must probably be one of the most common things to do with a DRF:
I have the following model:
class ProcessedStockAmounts(models.Model):
prodName = models.ForeignKey(Productlist, on_delete=models.CASCADE, blank=False, unique=False)
amount = models.CharField(unique=False, max_length=255)
time = models.ForeignKey(StockTakingTimes, on_delete=models.CASCADE, blank=False, unique=False, default=1)
def __str__(self):
return str(self.prodName)
And I am returning a JSON object via my API that looks like this:
[{'prodName': 'SV1', 'amount': '1111111', 'time' : 1}]
When I insert my prodName with a value it has no problem, but obviously my user will not know the prodName ID and only the prod name. So when I try to insert the above I get the following error:
ValueError: Cannot assign "'SV1'": "ProcessedStockAmounts.prodName" must be a "Productlist" instance.
This was the closest I got to an answer and when I do the following it actually inserts:
p = ProcessedStockAmounts(amount='33', prodName = Productlist.objects.get(productid = 'SV1'), time = StockTakingTimes.objects.get(times='06:00'))
p.save()
but giving data this way is obviously defeating the purpose.
My serializer looks like the following:
class TestSerializer(serializers.ModelSerializer):
# time = serializers.SlugRelatedField(read_only=True, slug_field='time')
prodName = serializers.CharField()
# prodName = serializers.SlugRelatedField(read_only=True, slug_field='prodName')
class Meta:
model = ProcessedStockAmounts
fields = ('prodName','amount','time')
With my view:
class InsertMultiProcessedStock(APIView):
def post(self, request, format='json'):
serializer = TestSerializer(data = request.data, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors)
Productlist model:
class Productlist(models.Model):
productid = models.CharField(unique=True, max_length=20) # Field name made lowercase.
proddescription = models.CharField(db_column='prodDescription', max_length=255, blank=True, null=True) # Field name made lowercase.
packaging = models.ForeignKey(Packaging, on_delete=models.CASCADE, blank=True, null=True)
unitweight = models.FloatField(db_column='unitWeight', blank=True, null=True)
def __str__(self):
return self.productid
This would have been easier if you had the related model. But the commented-out slugrelatedfield is the way you should do it, using the actual field name:
prodName = serializers.SlugRelatedField(read_only=False, slug_field='productid')
Your serializer is wrong, You must use relationship serializer.
prodName = ProductlistSerializer(many = False)
But I found Your model defintion is very confusing

Serialize Many to Many Relationship with Extra fields

Please I'm stuck trying to get around this issue. Guess there is something I'm not getting after looking at other similar questions.
I have these models:
class Dish(BaseModel):
class Meta:
verbose_name_plural = 'dishes'
name = models.CharField(_('dish'), max_length=100)
dish_type = models.CharField(_("dish type"), max_length=100)
price = models.PositiveIntegerField(_("price"))
def __str__(self):
return f"{self.name} costs {self.price}"
class Order(BaseModel):
dishes = models.ManyToManyField(Dish, through='DishOrder')
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
discount = models.PositiveIntegerField(_("total discount"), blank=True)
total = models.PositiveIntegerField(_("total"), blank=True)
shipping = models.PositiveIntegerField(_("shipping cost"), blank=True)
grand_total = models.PositiveIntegerField(_("grand total"), blank=True)
country = models.CharField(_('country code'), max_length=2)
def __str__(self):
return f"order from {self.customer} at {self.total}"
def get_absolute_url(self):
return reverse('order-details', kwargs={'pk': self.pk})
class DishOrder(models.Model):
dish = models.ForeignKey(Dish, on_delete=models.CASCADE, related_name='dishes')
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='dishes')
quantity = models.PositiveIntegerField(_("quantity"))
discount = models.PositiveIntegerField(_("discount"))
price = models.PositiveIntegerField(_('price'))
And the corresponding serializers like so:
class DishOrderSerializer(serializers.ModelSerializer):
class Meta:
model = DishOrder
fields = (
"quantity",
"discount",
"price"
)
class OrderSerializer(serializers.ModelSerializer):
dishes = DishOrderSerializer(source='dish', many=True)
class Meta:
model = Order
fields = (
"id",
"country",
"customer",
"dishes",
"total",
"discount",
"grand_total",
"voucher"
)
So as can be seen, I have a m2m relationship via a through table. However I can't get the serializer to work. This is the error I keep getting:
Got AttributeError when attempting to get a value for field dishes
on serializer OrderSerializer. The serializer field might be named
incorrectly and not match any attribute or key on the Order
instance. Original exception text was: 'Order' object has no attribute
'dish'.
I have been looking through this for some time trying to figure out what the error is. I will appreciate any help
Since you are using related_name='dishes' in model you should use dishes as source to the manytomany objects:
class OrderSerializer(serializers.ModelSerializer):
dishes = DishOrderSerializer(source='dishes', many=True)
or simple:
class OrderSerializer(serializers.ModelSerializer):
dishes = DishOrderSerializer(many=True)
Since source='dishes' redundant in case you named serializer's field dishes also.

Django REST framework, dealing with related fields on creating records

Preliminary note: this is a rather newbie question, though I have not found a sufficient answer on StackOverflow; many similar questions, but not this one. So I am asking a new question.
The problem: I'm having difficulty creating records where one field is a foreign key to an existing record, and I do not know what I'm doing wrong in my code.
In my app there are two models in question, a one-to-many relationship between Company and BalanceSheet:
models:
class Company(models.Model):
cik = models.IntegerField(default=0, unique=True)
symbol = models.CharField(max_length=4, unique=True)
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.StringRelatedField()
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
Views:
class BalanceSheetCreate(generics.CreateAPIView):
model = BalanceSheet
queryset = BalanceSheet.objects.all()
serializer_class = BalanceSheetSerializer
urls include:
url(r'^(?P<symbol>[A-Z]{1,4})/create-balance-sheet/$', views.BalanceSheetCreate.as_view(),
name='create_balance_sheet'),
To this point, I have zero problem reading data. However, when trying to create records, I get errors I don't understand:
curl http://localhost:8000/financials/AAPL/create-balance-sheet/ -X POST -d "company=AAPL&date=1968-04-17&profit=1&loss=1"
IntegrityError at /financials/AAPL/create-balance-sheet/
null value in column "company_id" violates not-null constraint
Dropping the company data from that curl command results in the same error.
How do I get around this error? I thought I was telling the api what company I'm interested in, both explicitly in the url and in the post data.
Using python3.6, django 1.11, and djangorestframework 3.7.7
You get the IntegrityError because your code will try to create a new BalanceSheet without a company. That's because StringRelatedField is read-only (see docs) and therefore it's not parsed when BalanceSheetSerializer is used in write mode.
SlugRelatedField is what you need here:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.SlugRelatedField(slug_field='symbol')
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
To answer my own question, here's what I wound up with. Thanks again go to dukebody.
models:
class Company(models.Model):
cik = models.IntegerField(default=0)
symbol = models.CharField(max_length=4)
name = models.CharField(max_length=255)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
class BalanceSheetSerializer(serializers.ModelSerializer):
company = CompanySerializer(many=False)
class Meta:
model = BalanceSheet
fields = ('company', 'date', 'profit', 'loss')
def create(self, validated_data):
company_data = validated_data['company']
company, created = Company.objects.get_or_create(**company_data)
validated_data['company'] = company
sheet = BalanceSheet.objects.create(**validated_data)
return sheet
I also had to include the full company data within my curl statement as a nested dict.

Django Rest Framework how to save a manyToMany with Related Field

I have some problem with Django Rest Framework and manyToMany relation with Related Field. Whenn i list I have a error and when I save, it fill data in databasebut it cant display the result.
My model.py
class Product(models.Model):
name = models.CharField(max_length=100,unique=True)
description = models.TextField()
price = models.DecimalField(max_digits=9,decimal_places=3)
created = models.DateTimeField(auto_now_add=True)
edited = models.DateTimeField(auto_now_add=True)
state = models.ForeignKey('ProductState', default=1)
tax = models.ForeignKey('Tax')
categories = models.ManyToManyField('ProductCategory')
class OrderProduct(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
order = models.ForeignKey(Order, on_delete=models.CASCADE)
qty = models.IntegerField()
tax = models.ForeignKey('Tax')
class Order(models.Model):
ref = models.CharField(max_length=100,blank=True)
created = models.DateTimeField(auto_now_add=True)
state = models.ForeignKey('OrderState')
user = models.ForeignKey(User)
orderproducts = models.ManyToManyField(Product, through='OrderProduct')
my serializer.py
class OrderProductSerializer(serializers.ModelSerializer):
product = ProductSerializer(required=True)
class Meta:
model = OrderProduct
exclude = ()
read_only_fields = ('order','tax',)
def create(self, validated_data):
product = validated_data.pop('product')
return OrderProduct.objects.create(tax=product['product'].tax, **product)
class OrderSerializer(serializers.ModelSerializer):
orderproducts = OrderProductSerializer(many=True, required=True)
def create(self, validated_data):
products = validated_data.pop('orderproduct')
order = Order.objects.create(**validated_data)
for product in products:
op = OrderProduct.objects.create(product=product['product'], tax=product['product'].tax, order=order, qty=product['qty'] )
op.save()
order.save()
return order;
class Meta:
model = Order
exclude = ()
read_only_fields = ()
class ProductSerializer(serializers.ModelSerializer):
categories = ProductCategorySerializer(many=True, required=True)
tax = TaxSerializer(many=False, required=True)
state = OrderStateSerializer(many=False, required=False)
class Meta:
model = Product
exclude = ()
and my view.py
class ClientOrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
when i try to list I get this error:
Exception Type: AttributeError at /api/order/
Exception Value: Got AttributeError when attempting to get a value for field product on serializer OrderProductSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Product instance.
Original exception text was: 'Product' object has no attribute 'product'.
Request information: