Please help me with Django model object validation - python

I'm very new to Django and programming in general. I'm trying to do some Django admin model object validations. I'm implementing bid system. User must be able to bid and edit bids straight from admin page. The code may be far from perfect...
Here's models.py:
class User(AbstractUser):
pass
class category(models.Model):
category = models.CharField(max_length=50, default='Enter new category')
def __str__(self):
return f"{self.category}"
class bid(models.Model):
listing = models.ForeignKey('listing', on_delete=models.CASCADE)
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
bid = models.DecimalField(max_digits=6, null=True, decimal_places=2)
def clean(self):
if self.bid <= self.listing.Price:
raise ValidationError('Please place a bid higher than starting price')
if self.bid <= ??? #How should I code this?
raise ValidationError('Please place a bid higher than the current highest bid')
def __str__(self):
return f"{self.user}, {self.listing} {self.bid}"
class listing(models.Model):
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
Title = models.CharField(max_length=50)
Description = models.CharField(max_length=300)
Price = models.DecimalField(max_digits=6, decimal_places=2)
category = models.ForeignKey(category, on_delete=models.CASCADE, related_name="categories")
def __str__(self):
return f"{self.Title}"

clean is for a form not a model, this can be done by implementing a custom ModelAdmin for class bid with its custom validation and do the validation in that form

Related

How to get specific objects based on ManyToMany field match

I'm doing a cookbook app, which help users find meal thay can do with their ingridients. I'm using Django RestFramework, and i need to return list of avaliable meals that user can do, but don't know how to do search by ingridients
My models.py:
#models.py
class Meal(models.Model):
name = models.CharField(max_length=250)
description = models.TextField(blank=True, null=True)
recipe = models.TextField()
is_published = models.BooleanField(default=False)
category = ForeignKey('Category', on_delete=models.CASCADE, null=True)
user = ForeignKey(User, verbose_name='User', on_delete= models.CASCADE)
difficulty = ForeignKey('Difficulty', on_delete=models.PROTECT, null=True)
ingridients = models.ManyToManyField('Ingridient')
class Ingridient(models.Model):
name = models.CharField(max_length=100, db_index=True)
ico = models.ImageField(upload_to="photos/%Y/%m/%d/", blank=True, null=True)
category = ForeignKey('CategoryIngridients', on_delete=models.CASCADE, null=True)
def __str__(self):
return self.name
class CookBookUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
ingridients = models.ManyToManyField('Ingridient')
serializer.py
class MealSerializer(serializers.ModelSerializer):
class Meta:
model = Meal
fields = "__all__"
views.py
class CraftWithUsersIngridientsListAPIView(generics.ListAPIView):
serializer_class = MealSerializer
def get_queryset(self):
return Meal.objects.filter(ingridients=CookBookUser.objects.filter(user_id = self.request.user.id).ingridients)
CraftWithUsersIngridientsListAPIView isn't working and I get AttributeError 'QuerySet' object has no attribute 'ingridients', can someone help fix this?
I tried building different serializer but it doesn't help
class CraftWithUsersIngridientsListAPIView(generics.ListAPIView):
serializer_class = MealSerializer
def get_queryset(self):
user_ingredients = CookBookUser.objects.get(user=self.request.user).ingredients.all()
return Meal.objects.filter(ingredients__in=user_ingredients)
This way, you first get the CookBookUser instance for the current user, then get all of their ingredients, and finally, filter the Meal objects that contain those ingredients. The __in query lookup is used to check if the meal ingredients are in the user's ingredients.

Validating a form field against another model

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

How to get year-month costs summary for certain category in DetailView, Django?

I have a table with expenses categories and when I click on the certain category it redirects me to this DetailView page. Then I want to show the year-month costs summary for chosen category to look similiar to this what I already got (summary per year-month but not for certain category). How can I achieve that?
This is what I got for now and got stucked:
def category_summary():
summary_by_year_month = Expense.objects.annotate(
month=TruncMonth('date')).values('date').annotate(
amount=Sum('amount')).order_by()
return summary_by_year_month
MODELS.PY
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return f'{self.name}'
class Expense(models.Model):
class Meta:
ordering = ('date', '-pk')
category = models.ForeignKey(Category, null=True, blank=True,
on_delete=models.CASCADE)
name = models.CharField(max_length=50)
amount = models.DecimalField(max_digits=8, decimal_places=2)
date = models.DateField(default=datetime.date.today, db_index=True)
def __str__(self):
return f'{self.date} {self.name} {self.amount}'
The most confusing part about this problem for me is how can I retrieve this information only for chosen category and show it to user in DetailView? Thank you in advance for any help.

Accessing other model field when validating model field in Django

So I have a Django auctions app, which has 4 models: Users, Listings, Bids, Comments.
When a user tries to place a bid on some listing, I want to check whether bid_amount field in Bid model is smaller than start starting_bid field in Listing model. Also, I wanted to ask, what is the best practice for this kinda stuff? AFAIK, you can validate a form field in forms.py. Thanks!
models.py
class Listing(models.Model):
"""Auction listing"""
user = models.ForeignKey(User, verbose_name='user owner', on_delete=models.CASCADE, related_name="usr_listings")
title = models.CharField(max_length=64)
description = models.TextField(max_length=160)
starting_bid = models.PositiveIntegerField()
bids_number = models.PositiveIntegerField(default=1)
img_url = models.URLField("Image URL", max_length=200, blank=True)
category = models.CharField(max_length=64, blank=True)
date_listed = models.DateTimeField(default=timezone.now)
class Meta:
verbose_name = 'auction listing'
ordering = ['-date_listed']
def __str__(self):
return self.title
def get_absolute_url(self):
print('loading... get_absolute_url')
return reverse('listing_detail', kwargs={'pk': self.pk})
class Bid(models.Model):
"""Bids made on auction listing"""
listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name="listing_bids")
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_bidder")
bid_amount = models.PositiveIntegerField(default=None)
class Meta:
verbose_name = 'Bid'
def __str__(self):
return f'{self.bid_amount} by {self.user} on {self.listing}'
def clean(self):
super().clean()
# do something to access Listing.starting bid, and Listing.user
adding this to the clean method of Bid solve the problem for you.
from django.core.exceptions import ValidationError
...
if self.bid_amount < self.listing.starting_bid:
raise ValidationError("bid_amount must be greater than or equal to starting_bid of the listing")

Able to add the same user multiple times to the another model

I have a model which adds(assigns) users to the academy (academy user), the issue is I am able to add the same user multiple times to the academy. What am I doing wrong here?
class AcademyPlayer(models.Model):
academy = models.ForeignKey(Academy, on_delete=models.CASCADE)
player = models.ForeignKey('player.Player', on_delete=models.CASCADE)
date_joined = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.player.user.name
I am adding the player.Player model for reference:
class Player(models.Model):
count = models.IntegerField(
_('count'),
null=True,
blank=True
)
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, related_name='player_user')
def __str__(self):
return self.user.email
class AcademyPlayer(models.Model):
academy = models.OneToOneField(Academy, on_delete=models.CASCADE)
player = models.OneToOneField('player.Player', on_delete=models.CASCADE)
date_joined = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.player.user.name
use OneToOneField for this
and dont forget to makemigrations and migrate after this change

Categories