Validating a form field against another model - python

I'm trying to build an auctions website that allows users to bid on listings. For a user to successfully place a bid they must input an amount higher than the current highest bid and if there is no current highest bid because no user has placed a bid yet, that first bid input must be higher than the listing start price.
I want to add a form validation to my BidForm to raise an error if the input doesn't fit these conditions but I have a pylint error on listing.id in def clean_bid_input and it says it is an undefined variable so I feel my form validation isn't quite right. Please could someone have a look and see if my form validation is following the logic I'm hoping it will?
models.py
class Listing(models.Model):
class NewManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='active')
options = (
('active', 'Active'),
('closed', 'Closed'),
)
title = models.CharField(max_length=64)
description = models.TextField(max_length=64)
start_price = models.DecimalField(max_digits=9, decimal_places=2, validators=[MinValueValidator(0.99)])
image = models.URLField(max_length=200, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="listings")
lister = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="lister_user")
date_added = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=10, choices=options, default="active")
winner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET(get_sentinel_user), related_name="winner_user", null=True)
favourites = models.ManyToManyField(User, related_name="favourite", default=None, blank=True)
objects = models.Manager()
listingmanager = NewManager()
def __str__(self):
return f"{self.title} ({self.pk}, £{self.start_price}, {self.lister})"
class Bid(models.Model):
bidder = models.ForeignKey(User, on_delete=models.CASCADE, related_name="bidders")
bid_item = models.ManyToManyField(Listing, related_name="bid_items", default=None)
bid_input = models.DecimalField(max_digits=9, decimal_places=2, default=None)
time = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"Bid amount: {self.bid_input}"
forms.py
class NewListingForm(forms.ModelForm):
class Meta:
model = Listing
fields = ["title", "description", "start_price", "image", "category"]
title = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'}))
description = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'}))
start_price = forms.DecimalField(label='Starting Bid Price (£)')
image = forms.URLField(widget=forms.URLInput(attrs={'autocomplete':'off'}))
category = forms.ModelChoiceField(queryset=Category.objects.all())
class BidForm(forms.ModelForm):
class Meta:
model = Bid
fields = ["bid_input"]
labels = {"bid_input": ""}
widgets = {
"bid_input": forms.NumberInput(attrs={'placeholder': 'Enter bid (£)'})
}
def clean_bid_input(self):
data = self.cleaned_data['bid_input']
highest_bid = Bid.objects.filter(bid_item=listing.id).aggregate(Max('bid_input'))
listing_price = Listing.start_price.get(bid_item=listing.id)
if highest_bid is None:
if data < listing_price:
raise ValidationError('Bid must be higher than listing start price')
if data < highest_bid:
raise ValidationError('Bid must be higher than current highest bid')

In clean_bid_input method replace this:
listing_price = Listing.start_price.get(bid_item=listing.id)
with this
listing_price = Listing.objects.get(bid_item=listing.id).start_price

Related

Multiple For Loops Django

I have the following two models ASPBoookings and Athlete. The Athlete model is linked to the ASPBookings model by the foreign key named athlete.
I am trying to create a loop that will cycle through all of the bookings in the ASPBooking table and find out which is the most recent booking by each athlete (the table can contain multiple bookings each to the same or different athletes (athlete_id).
Once I have this information (booking_date and athlete_id), I then want to be able to automatically update the "Lastest ASP Session Field" in the Athlete Model.
This is what I have tried so far. I can cycle through the bookings in the ASPBookings table and retrieve and update the "Latest ASP Session Field" using the booking_date and athlete_id, but I cannot do this for multiple different athletes that are within the table. Currently the view just identifies the latest booking and the assigned athlete_id and then updates the field.
Thanks in advance for any help.
Below is the code:
ASPBookings Model
class ASPBookings(models.Model):
asp_booking_ref = models.CharField(max_length=10, default=1)
program_type = models.CharField(max_length=120, default='asp')
booking_date = models.DateField()
booking_time = models.CharField(max_length=10, choices=booking_times)
duration = models.CharField(max_length=10, choices=durations, default='0.5')
street = models.CharField(max_length=120)
suburb = models.CharField(max_length=120)
region = models.CharField(max_length=120, choices=regions, default='Metro')
post_code = models.CharField(max_length=40)
organisation_type = models.CharField(max_length=120,choices=organisation_types, default='Government School')
audience_number = models.CharField(max_length=10)
presentation_form = models.CharField(max_length=120, choices=presentation_form_options, default='Face to Face')
contact_name = models.CharField(max_length=80)
email = models.EmailField()
phone_number = models.CharField(max_length=120)
comments = models.TextField()
status = models.CharField(max_length=80, choices=statuses, default='TBC')
email_sent = models.BooleanField(default=False)
athlete = models.ForeignKey(Athlete, default= '1', on_delete=models.CASCADE)
def __str__(self):
return self.contact_name
# return URL after the POST has been submitted.
def get_absolute_url(self):
return reverse('vistours:success')
Athlete Model
class Athlete(models.Model):
athlete_ref = models.CharField(max_length=10, default=1)
athlete_name = models.CharField(max_length=80)
email = models.EmailField()
phone_number = models.CharField(max_length=120)
home = models.CharField(max_length=120)
education = models.CharField(max_length=120)
sport = models.CharField(max_length=120, choices=sports, default='1500m Runner')
notes = models.TextField(default='None')
gender = models.CharField(max_length=120, choices=genders, default='Not Specified')
para_athlete = models.BooleanField(blank=True)
working_with_children = models.BooleanField(blank=True)
expiry_date = models.DateField(blank=True, null=True)
available = models.BooleanField(blank=True)
available_from = models.DateField(blank=True, null=True)
bfbw = models.BooleanField(blank=True)
latest_bfbw_session = models.DateField(blank=True, null=True)
number_bfbw_sessions = models.CharField(blank=True, null=True, max_length=10)
asp = models.BooleanField(blank=True)
latest_asp_session = models.DateField(blank=True, null=True)
number_asp_sessions = models.CharField(blank=True, null=True, max_length=10)
tours = models.BooleanField(blank=True)
latest_tours_session = models.DateField(blank=True, null=True)
number_tours_sessions = models.CharField(blank=True, null=True, max_length=10)
def __str__(self):
return self.athlete_name
# return URL after the POST has been submitted.
def get_absolute_url(self):
return reverse('home')
View
# Complete first loop for inital values.
for date in asp_data:
if date.booking_date != None:
first_loop = date.booking_date
athl_id = date.athlete_id
break
# If next value is greater than inital value, replace current values.
for date in asp_data:
if date.booking_date != None:
if date.booking_date > first_loop:
first_loop = date.booking_date
athl_id = date.athlete_id
print(first_loop)
print(athl_id)
update_date = Athlete.objects.get(id=athl_id)
update_date.latest_asp_session = first_loop
update_date.save()
No need for loops. You can do this for all athletes in just one go using subqueries to leave all the heavy-lifting to your database:
from django.db.models import F, OuterRef, Subquery
bookings = ASPBookings.objects.filter(
athlete=OuterRef('pk')
).order_by('-booking_date')
Athlete.objects.annotate(
latest_asp_booking_date=Subquery(
bookings.values('booking_date')[:1]
)
).update(
latest_asp_session=F('latest_asp_booking_date')
)

One To Many and models

i am trying to build my first project, a CRM website to handle orders and inventory.
and i got stuck, i was able to link orders to customer.
but when i try to build order that contain multi items. for some reason i didn't find a way to do it.
hope you can assist me.
so I have User>>Order>>Multi Items.
questions:
1) does the best practice here is just use ForeignKey ?
this my model's code:
from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=200, null=True)
phone = models.CharField(max_length=200, null=True)
email = models.CharField(max_length=200, null=True)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
def date_createdview(self):
return self.date_created.strftime('%B %d %Y')
class Product(models.Model):
CATEGORY = (
('General', 'General'),
('SmartHome', 'SmartHome'),
('Software', 'Software'),
('Mobile', 'Mobile'),
)
name = models.CharField(verbose_name='שם', max_length=200, null=True)
price = models.FloatField(verbose_name='מחיר', null=True)
category = models.CharField(max_length=200, null=True, choices=CATEGORY, verbose_name='קטגוריה')
description = models.CharField(max_length=200, null=True, verbose_name='מלל חופשי')
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Order(models.Model):
STATUS = (
('New', 'New'),
('Work in progress', 'Work in progress'),
('completed', 'completed'),
)
customer = models.ForeignKey(Customer, null=True, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True, null=True)
status = models.CharField(max_length=200, null=True, choices=STATUS)
def date_createdview(self):
return self.date_created.strftime('%d/%m/%Y')
class OrderItem(models.Model):
product = models.ForeignKey(Product, null=True, on_delete=models.CASCADE)
order = models.ForeignKey(Order, null=True, on_delete=models.CASCADE)
quantity = models.IntegerField(null=True)
2)how should I build my views or forms?
i want to make it dynamic, when i enter the order i can insert items and see the new item get add to a list of the items in the order.
how can save the order number and add new items?
this is my product view, it's working. I can add new products.
#login_required(login_url="login")
def products(request):
form = ProductForm(request.POST or None)
if form.is_valid():
form.save()
products = Product.objects.all()
context = {'form': form, 'products': products}
return render(request, 'accounts/products.html', context)
hope you can direct me to the right place.
thank you!
if form.is_valid():
order = get_object_or_404(Order, id=id)
instance = form.save(commit=False)
instance.order=order
instance.save()
just need to do:
commit=False
and then link the order.

django rest framework: How to validate a rule in serializers using a related field

I have these models:
product.py
class Product(models.Model):
product_title = models.CharField(max_length=100)
product_price = models.DecimalField(max_digits=12, decimal_places=2)
product_multiple = models.PositiveIntegerField(blank=True, null=True)
order_item.py
class OrderItem(models.Model)
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items', null=True, blank=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
price = models.FloatField(default=0)
quantity = models.BigIntegerField(default=1)
profitability = models.CharField(max_length=50, null=True, blank=True)
I want to validate in serializers.py if the order_item.quantity % product_multiple != 0
serializers.py
class OrderItemSerializer(serializers.ModelSerializer):
class Meta:
model = OrderItem
fields = ('id', 'order', 'product', 'price',
'quantity', 'profitability')
def validate_quantity(self, value):
data = self.get_initial()
quantity = data.get("quantity")
# This is where the `product.product_multiple` value is needed
if int(quantity) % product.product_multiple != 0:
raise serializers.ValidationError("Quantity is not multiple")
return value
How can I get the actual product_multiple inside the validate function?
As you are doing a validation that involves multiple fields, you should use the validate method as follows:
def validate(self, data):
quantity = data.get('quantity')
product = data.get('product')
if int(quantity) % product.product_multiple != 0:
raise serializers.ValidationError("Quantity is not multiple")
return data

Save a many to many object inside a django model?

I am still learning to use Django and so I am a bit unclear on something.
I have a product model and category model. A product can lie in multiple categories and multiple categories can have the same product.
So, its a many to many relationship. Now, I want to allow the user to select multiple categories from the html and then I want to save the categories and link them to the category object in my product model. I am completely lost about it.
One way would be to use Modelform but I dont want to go that way. Is there any other way I can accomplish this?
models.py:
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50, unique=True,
help_text='Unique value for product page URL, created from name.')
description = models.TextField()
is_active = models.BooleanField(default=True)
meta_keywords = models.CharField("Meta Keywords", max_length=255,
help_text='Comma-delimited set of SEO keywords for meta tag')
meta_description = models.CharField("Meta Description", max_length=255,
help_text='Content for description meta tag')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('catalog:categories', kwargs={'category_slug': self.slug})
class Meta:
ordering = ['-created_at']
verbose_name_plural = 'Categories'
class Product(models.Model):
name = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=255, unique=True,
help_text='Unique value for product page URL, created from name.')
brand = models.CharField(max_length=50)
sku = models.CharField(max_length=50)
price = models.DecimalField(max_digits=9, decimal_places=2)
old_price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, default=0.00)
thumbnail = models.FileField(upload_to='static/images/products/thumbnails')
imageurls = models.CharField(max_length=1000)
is_active = models.BooleanField(default=True)
is_bestseller = models.BooleanField(default=False)
is_featured = models.BooleanField(default=False)
quantity = models.IntegerField()
description = models.TextField()
meta_keywords = models.CharField(max_length=255, help_text='Comma-delimited set of SEO keywords for meta tag')
meta_description = models.CharField(max_length=255, help_text='Content for description meta tag')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
categories = models.ManyToManyField(Category)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('catalog:products', kwargs={'product_slug': self.slug})
def sale_price(self):
if self.old_price > self.price:
return self.price
else:
return None
class Meta:
ordering = ['-created_at']
part of views.py:
if request.method =='POST':
print ('entered')
name = request.POST['name']
brand = request.POST['brand']
sku = request.POST['sku']
price = request.POST['price']
quantity = request.POST['quantity']
description = request.POST['description']
imageurls = request.POST['urls']
print('imageurls',imageurls)
categorylist = request.POST['categories']
print('categorylist',categorylist)
categories = re.findall(r"[\w']+", categorylist)
print categories
imageurls = imageurls.split('~')
print('iageurls',imageurls)
for x in categories:
categoryobj = Category.objects.filter(name=x).values()
print ('categoryobj',categoryobj)
# Product.objects.create(name=name,sku=sku,brand=brand,price=price,quantity=quantity,description=description,imageurls=imageurls,categories=categoryobj)
return HttpResponse('success')
Try to save the above way gives me error.
product=Product.objects.create(name=name,sku=sku,brand=brand,price=price,quantity=quantity,description=description,imageurls=imageurls)
category_queryset = []
for x in categories:
category = Category.objects.filter(name=x).first()
category_queryset.append(category)
product.categories.set(category_queryset)

Convert Bound Method to Decimal

(edit added more code)
Keep getting an error here where its asking me to convert bound method to decimal. I think the problem is this line:
order_item = OrderItem(item=item.item_id, quantity=item.quantity, price=item.price, order=order)
I think Django wants me to convert item.price into a decimal, but I've been unable to figure out how. I tried decimal.Decimal(str(item.price)) which didn't work, and float(item.price) didnt work as well. As always, thanks in advance.
#cart.get_cart_items
def get_cart_items(request):
return Cart.objects.filter(cart_id=_cart_id(request))
#models
class Cart(models.Model):
cart_id = models.CharField(max_length=50)
item_id = models.ForeignKey('store.Item', unique=False)
date_added = models.DateTimeField(auto_now_add=True)
quantity = models.IntegerField(default=1)
class Item(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=75)
slug = models.SlugField(max_length=50, unique=True)
is_active = models.BooleanField(default=True, blank=True)
image1 = models.ImageField(upload_to='img')
image2 = models.ImageField(upload_to='img', blank=True)
image3 = models.ImageField(upload_to='img', blank=True)
image_caption1 = models.CharField(max_length=200, blank=True)
image_caption2 = models.CharField(max_length=200, blank=True)
image_caption3 = models.CharField(max_length=200, blank=True)
price = models.DecimalField(max_digits=8, decimal_places=2)
quantity = models.IntegerField(default=1)
description = models.TextField()
created = models.DateTimeField(auto_now_add=True)
shipping_price = models.DecimalField(decimal_places=2, max_digits=6)
categories = models.ManyToManyField(Category)
#views
def express_payment(request):
user = request.user
cart_subtotal = cart.cart_subtotal(request)
if request.method == "POST":
form = PaymentForm(request.POST)
if form.is_valid():
order = form.save(commit=False)
order.buyer = request.user
order.transaction_id = "12345678901234567890"
order.save()
if order.pk:
cart_items = cart.get_cart_items(request)
for item in cart_items:
order_item = OrderItem(item=item.item_id, quantity=item.quantity, price=item.price, order=order)
order_item.save()
Please provide more error info, or we just cannot provide any meaning help..
Edit:
Assume that the class Item and OrderItem is the same thing, then I think user, name, is required to save a OrderItem instance.

Categories