testing django forms with pytest failing - python

I am not sure why the assertion below is failing.
Can someone please give me a feedback, I am new to pytest with django.
test.forms
#pytest.fixture
def product():
month = Month.objects.create(name="november", slug="november")
user = User.objects.create(username="james", password="password")
obj = Product.objects.create(
user=user,
name="broom",
price=19.99,
quantity=1,
month=month,
)
data = {
'user':user,
'name':obj.name,
'price':obj.price,
'quantity':obj.quantity,
'month':month
}
form = ProductForm(data=data)
yield form
def test_product_form_with_data(product):
assert True is not None
assert True == product.is_valid()
Below is what is am getting from the terminal.
Traceback error.
_______________________________________________________________________________________ test_product_form_with_data ________________________________________________________________________________________
product = <ProductForm bound=True, valid=False, fields=(name;price;quantity;month)>
def test_product_form_with_data(product):
assert True is not None
> assert True == product.is_valid()
E assert True == False
E -True
E +False
tests/test_forms.py:42: AssertionError
models.py
class Product(models.Model):
month = models.ForeignKey(Month, on_delete=models.CASCADE, related_name='months')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='users')
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=30, db_index=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.PositiveSmallIntegerField(default=0)
created = models.DateTimeField(default=datetime.datetime.now())
is_active = models.BooleanField(default=True)
forms.py
class ProductForm(ModelForm):
class Meta:
model = Product
fields = [
'name',
'price',
'quantity',
'month',
]
I would really appreciate a feedback to help me understand what I am doing wrong trying to test this form with data while using fixture.

The problem is that month is a ForeignKey so you need to pass the object ID in data, something like this:
data = {
'user':user,
'name':obj.name,
'price':obj.price,
'quantity':obj.quantity,
'month':month.pk,
}
You would have the same problem with user but actually user is not in the list of fields handled by your form (look at ProductForm.Meta.fields), so it will be ignored. You can decide to either removed it from data or add it to fields.

Related

Django REST: ignoring custom fields which are not part of model

My TimeReport model looks like this:
class TimeReport(models.Model):
minutes_spent = models.PositiveIntegerField()
task = models.ForeignKey(Task, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
reported_for = models.DateField()
note = models.TextField(null = True, blank=True)
status = models.CharField(max_length=50, choices=State.choices, default=State.new)
user = models.ForeignKey(User, on_delete=models.PROTECT)
And my model serializer:
class TimeReportCreateSerializer(serializers.ModelSerializer):
class Meta:
model = TimeReport
fields = (
'id',
'minutes_spent',
'reported_for',
'note',
'status',
'task_custom_id',
)
task_custom_id = serializers.CharField()
def create(self, validated_data):
user = User.objects.get(auth_user_id = self.context['user_id'])
task = Task.objects.filter(custom_id = validated_data['task_custom_id']).filter(user = user.id).first()
report = TimeReport(**validated_data)
report.user = user
report.task = task
report.save()
return report
So, the problem is, that I want to take a custom value in a serializer, which is not a part of a model and do some custom logic with it - in this case search for the right 'task' in the database. But when I try to parse the model by using report = TimeReport(**validated_data), it gives me an exception:
TypeError at /api/report/
TimeReport() got an unexpected keyword argument 'task_custom_id'
Im kind of new to Django and python itself, so - what is the best approach?
If you are going to use that field only for creation, you should use write_only option.
task_custom_id = serializers.CharField(write_only=True)
See the docs here https://www.django-rest-framework.org/api-guide/fields/#write_only
You just need to remove task_custom_id from the dictionary
class TimeReportCreateSerializer(serializers.ModelSerializer):
class Meta:
model = TimeReport
fields = (
'id',
'minutes_spent',
'reported_for',
'note',
'status',
'task_custom_id',
)
task_custom_id = serializers.CharField()
def create(self, validated_data):
user = User.objects.get(auth_user_id = self.context['user_id'])
task_custom_id = validated_data.pop("task_custom_id")
task = Task.objects.filter(custom_id = task_custom_id).filter(user = user.id).first()
report = TimeReport(**validated_data)
report.user = user
report.task = task
report.save()
return report
task = Task.objects.filter(custom_id = validated_data.pop('task_custom_id')).filter(user = user.id).first()
the **validated_data will return (task_custom_id=value, field1=value1 ...) and task_custom_id it's not a TimeReport field so all u need is to pop it from validated_data before calling the constructor TimeReport

How to compare two serializer field and show whichever is higher in django rest

I have product serializer which return category_offer_price & product_offer_price,
before getting this response I want to compare both price and only return whichever is highest price.
#Serilaizer.py
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
product_offer_price = SerializerMethodField()
category_offer_price = SerializerMethodField()
class Meta:
model = Products
fields = [
"id",
"product_name",
"slug",
"category",
"description",
"category_offer_price",
"product_offer_price",
"base_price",
"stock",
"is_available",
"created_date",
"images",
"images_two",
"images_three",
]
def get_product_offer_price(self, obj):
try:
product_offer = ProductOffer.objects.get(product=obj)
if product_offer.is_active:
offer_price = product_offer.product_offer_price()
return offer_price
except Exception:
pass
return None
def get_category_offer_price(self, obj):
try:
category_offer = CategoryOffer.objects.get(category=obj.category)
if category_offer.is_active:
offer_price = category_offer.category_offer_price(obj)
return offer_price
except Exception:
pass
return None
#Models.py
class Products(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
product_name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=100, unique=True)
description = models.TextField(max_length=500)
base_price = models.IntegerField()
images = models.ImageField(upload_to="photos/products")
images_two = models.ImageField(upload_to="photos/products")
images_three = models.ImageField(upload_to="photos/products")
stock = models.IntegerField()
is_available = models.BooleanField(default=True)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Products"
def __str__(self):
return self.product_name
I'd like to know is it possible to compare serializer fields in a serializer class?
You can move into one method, to validate your field. Also, substitute your try:except with get-object-or-404 method and your serializer fields with all value since you are using everything, to have a much cleaner code.
from django.shortcuts import get_object_or_404
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
price = SerializerMethodField()
class Meta:
model = Products
fields = '__all__'
def get_price(self, obj):
product_offer = get_object_or_404(ProductOffer, product=obj)
category_offer = get_object_or_404(CategoryOffer, category=obj.category)
if product_offer.is_active and category_offer.is_active:
if product_offer.product_offer_price() > category_offer.category_offer_price(obj):
return product_offer.product_offer_price()
else:
return category_offer.category_offer_price(obj)
elif product_offer.is_active and not category_offer.is_active:
return product_offer.product_offer_price()
elif category_offer.is_active and not product_offer.is_active:
return category_offer.category_offer_price(obj)
EDIT: As you can see I used the classic if/else in this solution, although since Python 3.10 you can use the Match case statement to substitute these conditions chain.
In case of objects do not exist:
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
price = SerializerMethodField()
class Meta:
model = Products
fields = '__all__'
def get_price(self, obj):
try:
product_offer = ProductOffer.objects.filter(product=obj).first()
category_offer = CategoryOffer.objects.filter(category=obj.category).first()
if not product_offer and not category_offer:
return obj.base_price
elif not category_offer:
return product_offer.product_offer_price()
elif not product_offer:
return category_offer.category_offer_price(obj)
elif category_offer and product_offer:
if category_offer.is_active and not product_offer.is_active:
return category_offer.category_offer_price(obj)
elif product_offer.is_active and not category_offer.is_active:
return product_offer.product_offer_price()
elif category_offer.is_active and product_offer.is_active:
if category_offer.category_offer_price(obj) > product_offer.product_offer_price():
return category_offer.category_offer_price(obj)
else:
return product_offer.product_offer_price()
except:
return obj.base_price
Although, to be honest, if there could be no objects then the is_active field is redundant.
You can override to_representation()
Example:
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
product_offer_price = SerializerMethodField()
category_offer_price = SerializerMethodField()
...
...
def to_representation(self, instance):
data = super().to_representation(instance)
# access required fields like this
product_offer_price = data['product_offer_price']
category_offer_price = data['category_offer_price']
# do calculations here and returning the desired field as `calculated_price`
if category_offer_price > product_offer_price:
data['calculated_price'] = category_offer_price
else:
data['calculated_price'] = product_offer_price
return data
Not sure it s what you want but you could use a field of type SerializerMethodField which allow you to add a computed field that you could call category_offer_higher_price. Its value is computed by a function that return the highest one. See following link : https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

How can I check User Input value again Django Models and Forms

I don't know how to actually write the logic but I want to check user inputs especially price form field against Product model (price property).
I have model the django form as below:
class SalesForm(forms.ModelForm):
class Meta:
model = Sales
fields = ['product', 'customer', 'quantity', 'price']
Here is the Product Model
class Product(models.Model):
name = models.CharField(max_length=100, null=True)
category = models.CharField(max_length=100, choices=CATEGORY, null=True)
cost = models.PositiveIntegerField(null=True)
price = models.PositiveIntegerField(null=True)
quantity = models.PositiveIntegerField(null=True)
Here is what I am trying to do logically in views.py
def user_add_sales(request):
sales = Sales.objects.all().order_by('-salesdate')[:5]
if request.method == 'POST':
sales_form = SalesForm(request.POST)
if sales_form.is_valid:
sales_price = sales_form.cleaned_data('price')
try:
user_input = Product.objects.get(price = sales_price)
sales_form.save()
messages.success(request, 'Sales Added Successfully')
return redirect('dashboard-user-sales')
except user_input.DoesNotExist:
sales_form = SalesForm()
else:
sales_form = SalesForm()
context = {
'sales' : sales,
'sales_form' : sales_form,
}
return render(request, 'dashboard/user_sales.html', context)
When Try running the code, says 'SalesForm' object has no attribute 'cleaned_data'. Someone should please help me on how I can check whether what the user enters in price field of the SalesForm is not less than the price set for that product in Product Model.
The form is validated by the sales_form.is_valid() method, not by an attribute, the if condition is thus:
if sales_form.is_valid():
# …
# …
In your form, you can check if the given price is at least the price in the related attribute with:
class SalesForm(forms.ModelForm):
# …
def clean(self, *args, **kwargs):
data = super().clean(*args, **kwargs)
if data['price'] < data['product'].price:
raise ValidationError('The price of the sale is below the price of the product')
return data

Django rest framework - NOT NULL constraint on a foreign Key

I have this Error :
IntegrityError at /api/post_flight_schedule/
NOT NULL constraint failed: flights_tailnumber.aircraft_type_id
When I try to add a new PosFlightSchedule object to DB over http://127.0.0.1:8000/api/pos_flight_schedule (Website/APIView)
I have the below serializer :
class PosFlightScheduleModelSerializer(ModelSerializer):
class Meta:
model = PosFlightSchedule
fields = ['pos_route_id', 'tail_number', 'pos_flight_number', 'pos_flight_departure_time', 'pos_flight_date',
'pax_count']
class PosFlightScheduleSerializer(serializers.Serializer):
pos_route_id = serializers.CharField(source='pos_route_id.route_id', read_only=False)
tail_number = serializers.CharField(source='tail_number.tail_number', read_only=False)
pos_flight_number = serializers.CharField(source='pos_flight_number.flight_number', read_only=False)
pos_flight_departure_time = serializers.CharField(source='pos_flight_departure_time.flight_departure_time', allow_null=True,
read_only=False)
pos_flight_date = serializers.CharField(source='pos_flight_date.flight_date', read_only=False)
pax_count = serializers.IntegerField(read_only=False)
def create(self, validated_data):
tail_number_data = validated_data.pop("tail_number")
tail_number = TailNumber.objects.create(**tail_number_data)
flight_number_data = validated_data.pop("pos_flight_number")
flight_number = FlightSchedule.objects.create(**flight_number_data)
flight_departure_time_data = validated_data.pop("pos_flight_departure_time")
print "DEP_TIME" + str(flight_departure_time_data)
flight_departure_time = FlightSchedule.objects.create(**flight_departure_time_data)
route_id_data = validated_data.pop("pos_route_id")
route_id = FlightScheduleDetail.objects.create(**route_id_data)
flight_date_data = validated_data.pop("pos_flight_date")
flight_date = FlightScheduleDetail.objects.create(**flight_date_data)
pax_count = validated_data.pop("pax_count")
schedule_obj = PosFlightSchedule.objects.create(**validated_data)
# if tail_number:
schedule_obj.set_tail_number(tail_number)
schedule_obj.set_pos_flight_number(flight_number)
schedule_obj.set_pos_flight_departure_time(flight_departure_time)
schedule_obj.set_pos_route_id(route_id)
schedule_obj.set_pos_flight_date(flight_date)
schedule_obj.set_pax_count(pax_count)
schedule_obj.save()
return schedule_obj
def update(self, instance, validated_data):
tail_number = validated_data.pop("tail_number")
flight_number = validated_data.pop("pos_flight_number")
flight_departure_time = validated_data.pop("pos_flight_departure_time")
route_id = validated_data.pop("pos_route_id")
flight_date = validated_data.pop("pos_flight_date")
pax_count = validated_data.pop("pax_count")
instance.__dict__.update(validated_data)
if tail_number:
instance.set_tail_number(tail_number)
if flight_number:
instance.set_pos_flight_number(flight_number)
if flight_departure_time:
instance.set_pos_flight_departure_time(flight_departure_time)
if route_id:
instance.set_pos_route_id(route_id)
if flight_date:
instance.set_pos_flight_date(flight_date)
if pax_count:
instance.set_pax_count(pax_count)
instance.save()
return instance
The model of the field which is giving error looks like :
class TailNumber(models.Model):
tail_number_id = models.AutoField(null=False, primary_key=True)
tail_number = models.CharField(max_length=20, null=False, blank=False, unique=True)
aircraft_type = models.ForeignKey(AircraftType, null=False, blank=False)
def __unicode__(self):
return u'%s' % self.tail_number
class Meta:
verbose_name_plural = "Tail Numbers"
I am not understanding what is going wrong here.
The error you get is probably due to the fact that the dictionary tail_number_data does not contain the keyword aircraft_type, which is expected by TailNumber.objects to create the row in the db, since you defined it with no possibility to be null
aircraft_type = models.ForeignKey(AircraftType, null=False, blank=False)
^^^^^
Check that the key "aircraft_type" does exist in the dictionary tail_number_data, or allow for it to be null. Furthermore, if you consider the latter option and that this information is supposed to come from a UI, you may also want to allow for aircraft_type to be blank. See differentiate null=True, blank=True in django for details.

How to compare the changes in a modelform with model (django)

I do not know how to compare if a modelform is equal to a model in django.
thank you very much
models.py
class Person(models.Model):
name = models.CharField(max_length=45)
lastname = models.CharField(max_length=45)
dni = models.BigIntegerField()
email = models.EmailField(max_length=30)
status = models.BooleanField()
departament = models.ForeignKey(Departament) #char
forms.py
class Form_Person(forms.ModelForm):
class Meta:
model = models.Person
fields = ['name', 'lastname', 'dni', 'address', 'phone', 'email', 'position', 'status', 'departament']
views.py
#auth.decorators.login_required(login_url='login')
def persons_person(request,id='id'):
page_name = 'Persons'
try:
person = models.Person.objects.get(id=id)
list_departaments = models.Departament.objects.all()
list_departaments = list_departament.exclude(name = person.departament)
if request.method == 'POST':
form_person = forms.Form_Person(request.POST, initial='person')
Here the comparison would be implemented
### code to compare ###
# if form_persona.is_valid() and form_person.has_changed(): #Something like that
# ***how to compare***
# form_person.save()
except models.Person.DoesNotExist as e:
person = None
list_departaments = None
return render(request, 'app/persons/person.html',
{'page_name':page_name,
'person':person,
'list_departaments':list_departaments})
The link in the duplicate flag suggests using save method on object (same can be done in form also). I would personally suggest using signals with pre_save option to check before saving.

Categories