I have three models: Business, Offers and OfferPlan:
Business:
class Business(models.Model):
name_of_business = models.CharField(max_length=255)
Offers:
class Offers(models.Model):
business = models.ForeignKey(Business, related_name="business_offer",
on_delete=models.CASCADE)
title = models.CharField(max_length=255)
subtext = models.CharField(max_length=255)
OfferPlan:
class OfferPlan(models.Model):
WEEKDAYS = [
(1, _("Monday")),
(2, _("Tuesday")),
(3, _("Wednesday")),
(4, _("Thursday")),
(5, _("Friday")),
(6, _("Saturday")),
(7, _("Sunday")),
]
offer = models.ForeignKey(Offers, related_name="business_offer_plan",
on_delete=models.CASCADE)
weekday = models.IntegerField(
choices=WEEKDAYS,
)
from_hour = models.TimeField()
to_hour = models.TimeField()
I have a ListView which search for businesses open based on different params such as city, category etc. I also want to now search by weekday, say which business is open on Monday will be displayed and which are not wont be displayed on that day. Weekday information is stored in OfferPlan and there could be multiple timings for the offers that day in OfferPlan table, but I want to query (filter, exclude) the businesses who has even a single entry on that weekday number.
Here is my ListView:
class SearchListView(ListView):
template_name = 'search/search.html'
model = Business
def get_queryset(self):
# queryset = Business.objects.filter(business_address__city=AppLocations.objects.first().city)
if 'city' in self.request.GET:
queryset = Business.objects.filter(business_address__city=self.request.GET.get('city'))
if 'category' in self.request.GET:
queryset = queryset.filter(category__code=self.request.GET.get('category'))
# if 'date' not in self.request.GET:
# queryset = B
raise
return queryset
How could this be possible? Also looked into https://docs.djangoproject.com/en/1.8/ref/models/conditional-expressions/ but not able to figure out.
Thanks
Update 1
After researching more in the web, I figured out this is how it could be achieved, but need to know for sure from other Django enthusiasts here that it is right.
queryset.filter(business_offer__business_offer_plan__weekday=1).annotate(count_entry=Count('business_offer__business_offer_plan__weekday')).filter(count_entry__gt=1)
Solution
Jefferson's solution was tagged as right answer as it provided more insights, about which query is fast and what wrong was with my previous update, so here is the proper solution to which we both agreed:
queryset.filter(business_offer__business_offer_plan__weekday=1).annotate(count_entry=Count('business_offer__business_offer_plan__weekday')).filter(count_entry__gte=1)
def get_query(weekday):
businesses = Business.objects.filter(business_offer__in=Offers.objects.filter(
business_offer_plan__in=OfferPlan.objects.filter(weekday=weekday))).distinct()
return businesses
There's a heavy query, but it works.
There's no conditional expression here - and your annotation is much too complicated. You just need an additional filter.
queryset.filter(business_offer__business_offer_plan__weekday=self.request.GET['weekday'])
Related
class Dishes(models.Model):
""" 菜品"""
cuisine_list = ((0, '川菜'), (1, '粤菜'), (2, '徽菜'), (3, '湘菜'))
name = models.CharField('菜名', max_length=100)
material = models.TextField('材料')
cuisine = models.IntegerField('菜系', choices=cuisine_list)
price = models.IntegerField('价格')
def __str__(self):
return self.name
how to select : Information about the most expensive dish in each cuisine, including dish names and ingredients
Dishes.objects.values('cuisine').annotate(max_price=Max("price"))
In this way, we can only find the information with the highest price in each cuisine, excluding the names and ingredients of the dishes. It would be fine if we could query for cuisine and max_price from inner join, but what should we write in ORM?
If using Postgresql you may be able to use the following :
Dishes.objects.all().order_by().order_by(
'cuisine', # to group by this value
'-price' # descending by price will put highest first
).distinct('cuisine')
See https://docs.djangoproject.com/en/3.0/ref/models/querysets/#distinct for a description. Also see https://www.semicolonworld.com/question/61934/django-group-by-one-field-only-take-the-latest-max-of-each-group-and-get-back-the-orm-objects for a further discussion on similar problem and source for this suggestion.
Decent explanation of Distinct On available at: https://www.geekytidbits.com/postgres-distinct-on/
I have a History model like below
class History(models.Model):
class Meta:
app_label = 'subscription'
ordering = ['-start_datetime']
subscription = models.ForeignKey(Subscription, related_name='history')
FREE = 'free'
Premium = 'premium'
SUBSCRIPTION_TYPE_CHOICES = ((FREE, 'Free'), (Premium, 'Premium'),)
name = models.CharField(max_length=32, choices=SUBSCRIPTION_TYPE_CHOICES, default=FREE)
start_datetime = models.DateTimeField(db_index=True)
end_datetime = models.DateTimeField(db_index=True, blank=True, null=True)
cancelled_datetime = models.DateTimeField(blank=True, null=True)
Now i have a queryset filtering like below
users = get_user_model().objects.all()
queryset = users.exclude(subscription__history__end_datetime__lt=timezone.now())
The issue is that in the exclude above it is checking end_datetime for all the rows for a particular history object. But i only want to compare it with first row of history object.
Below is how a particular history object looks like. So i want to write a queryset filter which can do datetime comparison on first row only.
You could use a Model Manager method for this. The documentation isn't all that descriptive, but you could do something along the lines of:
class SubscriptionManager(models.Manager):
def my_filter(self):
# You'd want to make this a smaller query most likely
subscriptions = Subscription.objects.all()
results = []
for subscription in subscriptions:
sub_history = subscription.history_set.first()
if sub_history.end_datetime > timezone.now:
results.append(subscription)
return results
class History(models.Model):
subscription = models.ForeignKey(Subscription)
end_datetime = models.DateTimeField(db_index=True, blank=True, null=True)
objects = SubscriptionManager()
Then: queryset = Subscription.objects().my_filter()
Not a copy-pastable answer, but shows the use of Managers. Given the specificity of what you're looking for, I don't think there's a way to get it just via the plain filter() and exclude().
Without knowing what your end goal here is, it's hard to say whether this is feasible, but have you considered adding a property to the subscription model that indicates whatever you're looking for? For example, if you're trying to get everyone who has a subscription that's ending:
class Subscription(models.Model):
#property
def ending(self):
if self.end_datetime > timezone.now:
return True
else:
return False
Then in your code: queryset = users.filter(subscription_ending=True)
I have tried django's all king of expressions(aggregate, query, conditional) but was unable to solve the problem so i went with RawSQL and it solved the problem.
I have used the below SQL to select the first row and then compare the end_datetime
SELECT (end_datetime > %s OR end_datetime IS NULL) AS result
FROM subscription_history
ORDER BY start_datetime DESC
LIMIT 1;
I will select my answer as accepted if not found a solution with queryset filter chaining in next 2 days.
I have this "Jobs Server" model that i'm building. I want to include a field that will save which days of the week this job will run on. Ultimately in the UI, i would like the user to be able to have a series of check boxes(one for each day) that they can select. What would be the best way to represent this "days-of-week" data in my mode?
class Job(models.Model):
name = models.CharField(max_length=32, unique=True)
package = models.ForeignKey(Package)
binary = models.ForeignKey(Binary)
host = models.ForeignKey(Host)
colo = models.ForeignKey(Location)
group = models.ForeignKey(Group)
type = models.ForeignKey(Type)
start = models.TimeField()
end = models.TimeField()
days = ?
You may want to create DayOfTheWeek field type, which you can improve in various ways.
This code cause to translate automatically into the local language using the multilingual tools.
#myFields.py
from django.utils.translation import ugettext as _
DAY_OF_THE_WEEK = {
'1' : _(u'Monday'),
'2' : _(u'Tuesday'),
'3' : _(u'Wednesday'),
'4' : _(u'Thursday'),
'5' : _(u'Friday'),
'6' : _(u'Saturday'),
'7' : _(u'Sunday'),
}
class DayOfTheWeekField(models.CharField):
def __init__(self, *args, **kwargs):
kwargs['choices']=tuple(sorted(DAY_OF_THE_WEEK.items()))
kwargs['max_length']=1
super(DayOfTheWeekField,self).__init__(*args, **kwargs)
#models.py
import myFields
(..)
dayOfTheWeek = myFields.DayOfTheWeekField()
(..)
Something like this would work.
#models.py
DAYS_OF_WEEK = (
(0, 'Monday'),
(1, 'Tuesday'),
(2, 'Wednesday'),
(3, 'Thursday'),
(4, 'Friday'),
(5, 'Saturday'),
(6, 'Sunday'),
)
days = models.CharField(max_length=1, choices=DAYS_OF_WEEK
#forms.py
widgets = { 'days': forms.CheckboxSelectMultiple }
Or to save multiple days
#models.py
class Days(models.Model):
day = models.CharField(max_length=8)
days = models.ManyToManyField(Days)
#forms.py
widgets = { 'days': forms.CheckboxSelectMultiple }
If you want a checkbox for each one, then the easiest thing to do is to create BooleanFields for each of them. If you want to store it as a more complex value (eg. comma separated list or something), create your own widget and play with javascript, then you could go that route.
Just implemented django-weekday-field. Works great!
Hopefully this helps other people who stumble upon this question
EDIT: updated link to pypi since the bitbucket repo was deleted.
It hasn't been updated since 2014 but looking at the code is a great way to get started on answering this question
I'm working on a project that needs a ratings system where they can rate the quality of the houses. They'd be doing it from the admin panel, so it's not something that a visitor would rate.
For example, for each listing, they can rate it from 1-5 stars:
Location:
Room:
Dining options:
Community:
Overall:
In my models.py file, I have this setup:
Class Listing (models.Model):
...
RATING_CHOICES = (
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
)
location_rating = models.DecimalField(choices = RATING_CHOICES, max_digits=3, decimal_places=2)
room_rating = models.DecimalField(choices = RATING_CHOICES, max_digits=3, decimal_places=2)
dining_options_rating = models.DecimalField(choices = RATING_CHOICES, max_digits=3, decimal_places=2)
community_rating = models.DecimalField(choices = RATING_CHOICES, max_digits=3, decimal_places=2)
recreation_rooms_rating = models.DecimalField(choices = RATING_CHOICES, max_digits=3, decimal_places=2)
def __unicode__(self):
return self.name
def get_avg_rating(self):
avg_rating = (self.location_rating + self.room_rating + self.recreation_rooms_rating + self.dining_options_rating + self.community_rating) / 5
return avg_rating
I'd plan to display the ratings with a little CSS. Just putting room_rating or avg_rating in a template tag doesn't work, so I'm assuming I'd have to add some lines of code to views.py. I've checked the Django docs, but I'm still not sure how to go about this.
This seems like something that should be easy to do, but I'm not really sure where to start.
A potential problem with the direction you taking is that you're only storing one set of ratings for each Listing. If user B wants to rate a Listing, he'll have to overwrite User A's ratings. Instead of putting the ratings inside the Listing, you'll need to break out your ratings into a separate Ratings table, and use a ManytoMany relationship between your Listings and Ratings. That way, you can have multiple users rate a particular listing, and then you can calculate and show the averages for each listing. See this discussion for reference.
It might be better to use an existing ratings app than starting from scratch. They already have documentation so you can get them up and running quickly, and they handle the database design for you.
Django Ratings
Django Valuate
What I want is to be able to get this weeks/this months/this years etc. hotest products. So I have a model named ProductStatistics that will log each hit and each purchase on a day-to-day basis. This is the models I have got to work with:
class Product(models.Model):
name = models.CharField(_("Name"), max_length=200)
slug = models.SlugField()
description = models.TextField(_("Description"))
picture = models.ImageField(upload_to=product_upload_path, blank=True)
category = models.ForeignKey(ProductCategory)
prices = models.ManyToManyField(Store, through='Pricing')
objects = ProductManager()
class Meta:
ordering = ('name', )
def __unicode__(self):
return self.name
class ProductStatistic(models.Model):
# There is only 1 `date` each day. `date` is
# set by datetime.today().date()
date = models.DateTimeField(default=datetime.now)
hits = models.PositiveIntegerField(default=0)
purchases = models.PositiveIntegerField(default=0)
product = models.ForeignKey(Product)
class Meta:
ordering = ('product', 'date', 'purchases', 'hits', )
def __unicode__(self):
return u'%s: %s - %s hits, %s purchases' % (self.product.name, str(self.date).split(' ')[0], self.hits, self.purchases)
How would you go about sorting the Products after say (hits+(purchases*2)) the latest week?
This structure isn't set in stone either, so if you would structure the models in any other way, please tell!
first idea:
in the view you could query for today's ProductStatistic, than loop over the the queryset and add a variable ranking to every object and add that object to a list. Then just sort after ranking and pass the list to ur template.
second idea:
create a filed ranking (hidden for admin) and write the solution of ur formula each time the object is saved to the database by using a pre_save-signal. Now you can do ProductStatistic.objects.filter(date=today()).order_by('ranking')
Both ideas have pros&cons, but I like second idea more
edit as response to the comment
Use Idea 2
Write a view, where you filter like this: ProductStatistic.objects.filter(product= aProductObject, date__gte=startdate, date__lte=enddate)
loop over the queryset and do somthing like aProductObject.ranking+= qs_obj.ranking
pass a sorted list of the queryset to the template
Basically a combination of both ideas
edit to your own answer
Your solution isn't far away from what I suggested — but in sql-space.
But another solution:
Make a Hit-Model:
class Hit(models.Model):
date = models.DateTimeFiles(auto_now=True)
product = models.ForeignKey(Product)
purchased= models.BooleanField(default=False)
session = models.CharField(max_length=40)
in your view for displaying a product you check, if there is a Hit-object with the session, and object. if not, you save it
Hit(product=product,
date=datetime.datetime.now(),
session=request.session.session_key).save()
in your purchase view you get the Hit-object and set purchased=True
Now in your templates/DB-Tools you can do real statistics.
Of course it can generate a lot of DB-Objects over the time, so you should think about a good deletion-strategy (like sum the data after 3 month into another model MonthlyHitArchive)
If you think, that displaying this statistics would generate to much DB-Traffic, you should consider using some caching.
I solved this the way I didn't want to solve it. I added week_rank, month_rank and overall_rank to Product and then I just added the following to my ProductStatistic model.
def calculate_rank(self, days_ago=7, overall=False):
if overall:
return self._default_manager.all().extra(
select = {'rank': 'SUM(hits + (clicks * 2))'}
).values()[0]['rank']
else:
return self._default_manager.filter(
date__gte = datetime.today()-timedelta(days_ago),
date__lte = datetime.today()
).extra(
select = {'rank': 'SUM(hits + (clicks * 2))'}
).values()[0]['rank']
def save(self, *args, **kwargs):
super(ProductStatistic, self).save(*args, **kwargs)
t = Product.objects.get(pk=self.product.id)
t.week_rank = self.calculate_rank()
t.month_rank = self.calculate_rank(30)
t.overall_rank = self.calculate_rank(overall=True)
t.save()
I'll leave it unsolved if there is a better solution.