Django next/prev instance of a model with a certain condition? - python

I want to find next instance of an object model but with a certain condition.
Models.py:
class Pin(models.Model):
submitter = models.ForeignKey(User)
url = models.TextField(blank=True, null=True)
price = models.DecimalField(blank=True, null=True, max_digits=10, decimal_places=2)
published = models.DateTimeField(auto_now_add=True)
I know pk of one instance to get the next instance I can do:
pin = Pin.objects.get(pk=123)
pin_next = pin.get_next_by_published()
But I want the next pin which has price not equal to null, so kind of an and condition. Next instance but with price not null. I can use a loop to keep on finding the next of next untill it has price not null. But is there any direct way?

You can pass additional lookup keyword args to the get_next_by_XXX methods, so in your above case pin.get_next_by_published(price__isnull=False) should work. If you have more complex conditions or want a non date-based ordering you'll have to write your own method.

You'll have to write the query yourself but it's fairly trivial :)
Note that since published might not be unique this might not always work as you would expect. I would recommend pk based navigation because of that.
class Pin(models.Model):
submitter = models.ForeignKey(User)
url = models.TextField(blank=True, null=True)
price = models.DecimalField(blank=True, null=True, max_digits=10, decimal_places=2)
published = models.DateTimeField(auto_now_add=True)
def others(self):
return self.objects.exclude(pk=self.pk)
def others_with_price(self):
return self.others().filter(price__isnull=False)
# By primary key:
def get_next(self):
return self.others_with_price(pk__gt=self.pk).order_by('pk')[0]
def get_prev(self):
return self.others_with_price(pk__lt=self.pk).order_by('-pk')[0]
# By published:
def get_next_published(self):
return self.others_with_price(published__gte=self.published).order_by('published')[0]
def get_prev_published(self):
return self.others_with_price(published__lte=self.published).order_by('-published')[0]

Related

How do I filter django models based on product fields?

I am trying to run a filter command, using related fields; and am unsure how to go about it:
class Listing(models.Model):
name = models.CharField(max_length=150)
slug = models.SlugField()
description = models.TextField()
catchphrase = models.TextField(null=True, blank=True)
picture_0 = models.ImageField(upload_to = "mainimages")
picture_1 = models.ImageField(null=True, blank=True, upload_to = "./product/static/product/imgs")
picture_2 = models.ImageField(null=True, blank=True, upload_to = "./product/static/product/imgs")
short_term_price = models.IntegerField(default=0)
long_term_price = models.IntegerField(default=0)
tax = models.IntegerField(default=0)
tag = models.ForeignKey('Tag', on_delete=models.PROTECT)
def __str__(self):
return self.name
class Order(models.Model):
listing = models.ForeignKey(Listing, on_delete=models.PROTECT)
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
lease_date = models.DateField()
clear_date = models.DateField()
price = models.IntegerField(default=0) #cents
def __str__(self):
return self.listing.name
def get_display_price(self):
return "{0:.2f}".format(self.price / 100)
The general idea is that the user provides a start date and an end date and Django only returns the listings that aren't already in an order in that timeframe. I am unsure how to go about the view function:
def search_products(request, start_date, end_date):
listing = Listing.objects.select_related('order').all()
I will provide an answer as if you are using the lease_date to do the filtering. There is a couple of ways to achieve this. One is:
listing_qs = Listing.objects.filter(
pk__in=Order.objects.exclude(lease_date__range(start_date,end_date)).select_related('listing').values_list('listing__pk')
)
Code breakdown:
retrieve the orders by excluding those whose lease date is in between the provided timeframe
selecting the listing's pk (via values('listing__pk')) you can select any other attribute you want
using the result of the 2 previous instructions to get the Listing objects since we have the list of pk.
Another way:
Just exclude all the Listing objects whose lease date is in between the provided timeframe
Listing.objects.exclude(order_set__lease_date__range=(start_date,end_date))
I hope this helps.

What's the most efficient way to retrieve django queryset with the hightest number of posts for a related name?

I'm currently working on a website where advertisements will be posted to display vehicles for sale and rent. I would like to retrieve a queryset that highlights only one car brand (i.e. Audi) which has the highest number of posts for the respective model. Example:
Displaying the Audi brand because it has the highest number of related posts.
My question is, what's the most efficient way of doing this? I've done some work here but I'm pretty sure this is not the most efficient way. What I have is the following:
# Algorithm that is currently retrieving the name of the brand and the number of related posts it has.
def top_brand_ads():
queryset = Advertisement.objects.filter(status__iexact="Published", owner__payment_made="True").order_by('-publish', 'name')
result = {}
for ad in queryset:
# Try to update an existing key-value pair
try:
count = result[ad.brand.name.title()]
result[ad.brand.name.title()] = count + 1
except KeyError:
# If the key doesn't exist then create it
result[ad.brand.name.title()] = 1
# Getting the brand with the highest number of posts from the result dictionary
top_brand = max(result, key=lambda x: result[x]) # Returns for i.e. (Mercedes Benz)
context = {
top_brand: result[top_brand] # Retrieving the value for the top_brand from the result dict.
}
print(context) # {'Mercedes Benz': 7} -> Mercedes Benz has seven (7) related posts.
return context
Is there a way I could return a queryset instead without doing what I did here or could this be way more efficient?
If the related models are needed, please see below:
models.py
# Brand
class Brand(models.Model):
name = models.CharField(max_length=255, unique=True)
image = models.ImageField(upload_to='brand_logos/', null=True, blank=True)
slug = models.SlugField(max_length=250, unique=True)
...
# Methods
# Owner
class Owner(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
telephone = models.CharField(max_length=30, blank=True, null=True)
alternate_telephone = models.CharField(max_length=30, blank=True, null=True)
user_type = models.CharField(max_length=50, blank=True, null=True)
payment_made = models.BooleanField(default=False)
expiring = models.DateTimeField(default=timezone.now)
...
# Methods
# Advertisement (Post)
class Advertisement(models.Model):
STATUS_CHOICES = (
('Draft', 'Draft'),
('Published', 'Published'),
)
owner = models.ForeignKey(Owner, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=150, blank=True, null=True)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, blank=True, null=True)
publish = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Draft')
...
# Other fields & methods
Any help would be greatly appreciated.
Since you need brands, let's query on Brand model:
Brand.objects.filter(advertisement__status__iexact="Published").\
filter(advertisement__owner__payment_made=True).\
annotate(published_ads=Count('advertisement__id')).\
order_by('-published_ads')
However, even in your proposed solution, you can improve a little bit:
Remove the order_by method from your queryset. It doesn't affect the final result but adds some overhead, especially if your Advertisement model is not indexed on those fields.
Every time you call ad.brand you are hitting the database. This is called the N+1 problem. You are in a loop of n, you make n extra db access. You can use select_related to avoid such problems. In your case: Advertisement.objects.select_related('brand')...
Did you try the count method?
from django.db.models import Count
Car.objects.annotate(num_views=Count('car_posts_related_name')).order_by('num_views')

How to create a function that counts total string objects in my ArrayField column for my model?

Trying to create a column in my model called, stock_count, that finds the sum of the total string objects in my ArrayField(), aka stock_list. Here is my function.
def total_stocks_calc(self):
self.stock_count = Bucket.objects.aggregate(Sum('stock_list', distinct=True))
self.save()
However it doesn't seem to be doing anything, no calculating, leaving the field blank in my model, admin page, and DRF interface...
EDIT: updated post with new implementation.
Here is my model.
class Bucket(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='buckets')
users = models.ManyToManyField(settings.AUTH_USER_MODEL)
category = models.CharField(max_length=30, choices=category_options)
name = models.CharField(max_length=35)
created = models.DateTimeField(default=timezone.now)
slug = models.SlugField(unique=True, blank=True)
stock_count = models.IntegerField(blank=True, null=True)
stock_list = ArrayField(models.CharField(max_length=6,null=True),size=30,null=True)
about = models.CharField(max_length=75)
objects = models.Manager()
bucketobjects = BucketObjects()
class Meta:
ordering = ('-created',)
def total_stocks_calc(self):
self.stock_count = Bucket.objects.annotate(stock_count=F('stock_list__len'))
self.save()
def __unicode__(self):
return self.stock_list
Would like to know the proper way to count total items in ArrayField(), thank you in advance.
The ArrayField provides the len lookup field, through that you can get the count
like
from django.db.models import F
Bucket.objects.annotate(stock_count=F('stock_list__len'))

Get max value from a set of rows

This question is in relation to project 2 of the cs50 course which can be found here
I have looked at the following documentation:
Django queryset API ref
Django making queries
Plus, I have also taken a look at the aggregate and annotate things.
I've created the table in the template file, which is pretty straight forward I think. The missing column is what I'm trying to fill. Image below
These are the models that I have created
class User(AbstractUser):
pass
class Category(models.Model):
category = models.CharField(max_length=50)
def __str__(self):
return self.category
class Listing(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField()
initial_bid = models.IntegerField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
date_created = models.DateField(auto_now=True)
def __str__(self):
return self.title
class Bid(models.Model):
whoDidBid = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
list_item = models.ForeignKey(Listing, default=0, on_delete=models.CASCADE)
bid = models.IntegerField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
date = models.DateTimeField(auto_now=True)
def __str__(self):
return_string = '{0.whoDidBid} {0.list_item} {0.bid}'
return return_string.format(self)
This is the closest I could come to after a very long time. But the result I get is just the number 2. Ref image below
Listing.objects.filter(title='Cabinet').aggregate(Max('bid'))
Where 'Cabinet' is a Listing object that I have created. And placed two bids on them.
So the question is, how do I get the Maximum bid value(i.e. 110 for this case) for a particular listing? Using the orm. I think if I used a raw sql query, I could build a dict, send it to the template with the queryset. Then while looping through the queryset, get the value for the key, where the key is the name of the listing or something along those lines. Nah, I would like to know how to do this through the ORM please.
Here's answer #1
Bid.objects.filter(list_item__title='Cabinet').prefetch_related('list_item').aggregate(Max('bid'))
What happens when you try this (sorry, I don't have any objects like this to test on):
Bid.objects.values(list_item__title).prefetch_related('list_item').annotate(Max('bid'))

Field 'id' expected a number but got <Listing: Ps4 Pro>

It's my first time creating a Django website with models, and in my first attempt to insert data into my table I'm getting this error.
My models are as follows:
class User(AbstractUser):
pass
#https://docs.djangoproject.com/en/3.1/topics/auth/default/
class Listing(models.Model):
listingID = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="listID")
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="myListing", null=True)
watchers = models.ManyToManyField(User, blank=True, related_name="watchlist")
title = models.CharField(max_length=30)
description = models.TextField()
creation_date = models.DateField(auto_now=True)
img_url = models.URLField()
active = models.BooleanField(default=True)
def __str__(self):
return f"{self.title}"
class Bid(models.Model):
listing = models.ForeignKey(Listing, on_delete=models.SET_NULL, related_name="bidsMadeOnMe", null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="myBids", null=True)
price = models.FloatField()
creation_date = models.DateField(auto_now=True, null=True)
def __str__(self):
return f"Bid={self.price}"
and the view that handles the form submission is this one:
#login_required
def create_listing(request):
if request.method == "POST":
user = User.objects.get(username=request.user.username)
l = Listing(created_by=user,
title=request.POST["title"],
description=request.POST["desc"],
# https://stackoverflow.com/questions/12176585/handling-dates-over-request-get
creation_date=models.DateTimeField(auto_now_add=True),
img_url=request.POST["image_url"]
)
l.save()
b = Bid(l,
user,
request.POST["initial_bid"],
models.DateTimeField(auto_now_add=True)
)
b.save()
return render(request, "auctions/index.html")
I know the problem is the way I'm adding the data but I can't fix it. Can someone give me some light?
Your problem (well, several actually) is this:
b = Bid(l, user, request.POST["initial_bid"], models.DateTimeField(auto_now_add=True))
You're constructing a model instance by positional arguments instead of keyword arguments. This can be done, but then the invisible "id" column that has been added to the Bid model, is the first argument.
In Django we never construct models like that, but always use keyword arguments, so we're not depending on field order:
b = Bid(listing=l, user=user, ...))
Once you're solved that, your next problem is the date field.
Don't assign fields to model instances. Fields are class declarations, they don't belong on instances. Fields describe on a class (= a Model), what kind data to expect. On the instance, you assign that data.
In this case, your definition for the field is wrong on the model and on the instance you shouldn't even assign it - it will be automatically filled.
Overall, it feels like you haven't gone through Django's tutorial or did not fully understand the concepts. I suggest you go through it.

Categories

Resources