Free dates in reservation system - django

I am working on a reservation system, and I can not find a good way to select free dates. Here is my model:
class rental_group(models.Model):
group_title = models.CharField(max_length = 30)
description = models.TextField()
class rental_units(models.Model):
group = models.ForeignKey(rental_group)
number = models.CharField(max_length = 6)
class Reservations(models.Model):
#name = models.ForeignKey(customer)
rental_unit = models.ForeignKey(rental_units)
start_date = models.DateField()
end_date = models.DateField()
I tried to select available rooms with this query:
rental_units.objects.filter(group__rental_group = 'Bungalow').exclude(Q(Reservations__start_date__lt = arrival)&Q(Reservations__end_date__gt = arrival)|Q(Reservations__start_date__lt = departure)&Q(Reservations__end_date__gt = departure))
This works perfect when there is only one reservation. When there are more reservations on the same number, things go wrong. For example when I have two reservations on number 120 this query returns 120 twice when everything is available. And 120 is returns once if the the new reservation is between one of the old reservetaion dates (should be zero "not availble")
Is this possible with a query? Or should I iter over the reservations, and remove the reservated house from a list (which could take a lot of time when there are lots of reservations)

You want Reservations, not rental_units, so filter them:
Reservations.objects\
.filter(rental_unit__group__group_title='Bungalow')\
.exclude(
Q(start_date__lt=arrival) & \
Q(end_date__gt=arrival) | \
Q(start_date__lt=departure) & \
Q(end_date__gt=departure)
)

Ok it seems that it is not possible to do what I wanted. So I choose a different approach:
Make a list of all rental_units: all_units
Make a list of rental_units from the query below, and remove these list items from all_units:
rental_units.objects.filter(group__rental_group = 'Bungalow').filter(Q(Reservations__start_date__gt = arrival)&Q(Reservations__start_date__lt = departure)|Q(Reservations__end_date__gt = arrival)&Q(Reservations__end_date__lt = departure))
I enhanced the queryset, so it differs from the topic start.
The queryset returns all Bungalows that have a reservation between the arrival and departure date.

Related

Django annotation on compoundish primary key with filter ignoring primary key resutling in too many annotated items

Please see EDIT1 below, as well.
Using Django 3.0.6 and python3.8, given following models
class Plants(models.Model):
plantid = models.TextField(primary_key=True, unique=True)
class Pollutions(models.Model):
pollutionsid = models.IntegerField(unique=True, primary_key=True)
year = models.IntegerField()
plantid = models.ForeignKey(Plants, models.DO_NOTHING, db_column='plantid')
pollutant = models.TextField()
releasesto = models.TextField(blank=True, null=True)
amount = models.FloatField(db_column="amount", blank=True, null=True)
class Meta:
managed = False
db_table = 'pollutions'
unique_together = (('plantid', 'releasesto', 'pollutant', 'year'))
class Monthp(models.Model):
monthpid = models.IntegerField(unique=True, primary_key=True)
year = models.IntegerField()
month = models.IntegerField()
plantid = models.ForeignKey(Plants, models.DO_NOTHING, db_column='plantid')
power = models.IntegerField(null=False)
class Meta:
managed = False
db_table = 'monthp'
unique_together = ('plantid', 'year', 'month')
I'd like to annotate - based on a foreign key relationship and a fiter a value, particulary - to each plant the amount of co2 and the Sum of its power for a given year. For sake of debugging having replaced Sum by Count using the following query:
annotated = tmp.all().annotate(
energy=Count('monthp__power', filter=Q(monthp__year=YEAR)),
co2=Count('pollutions__amount', filter=Q(pollutions__year=YEAR, pollutions__pollutant="CO2", pollutions__releasesto="Air")))
However this returns too many items (a wrong number using Sum, respectively)
annotated.first().co2 # 60, but it should be 1
annotated.first().energy # 252, but it should be 1
although my database guarantees - as denoted, that (plantid, year, month) and (plantid, releasesto, pollutant, year) are unique together, which can easily be demonstrated:
pl = annotated.first().plantid
testplant = Plants.objects.get(pk=pl) # plant object
pco2 = Pollutions.objects.filter(plantid=testplant, year=YEAR, pollutant="CO2", releasesto="Air")
len(pco2) # 1, as expected
Why does django return to many results and how can I tell django to limit the elements to annotate to the 'current primary key' in other words to only annotate the elements where the foreign key matches the primary key?
I can achieve what I intend to do by using distinct and Max:
energy=Sum('yearly__power', distinct=True, filter=Q(yearly__year=YEAR)),
co2=Max('pollutions__amount', ...
However the performance is inacceptable.
I have tested to use model_to_dict and appending the wanted values "by hand" to the dict, which works for the values itself, but not for sorting the resulted dict (e.g. by energy) and it is acutally faster than the workaround directly above.
It conceptually strikes to me that the manual approach is faster than letting the database do, what it is intended to do.
Is this a feature limitation of django's orm or am I missing something?
EDIT1:
The behaviour is known as bug since 11 years.
Even others "spent a whole day on this".
I am now trying it with subqueries. However the forein key I am using is not a primary key of its table. So the kind of "usual" approach to use "pk=''" does not work. More clearly, trying:
tmp = Plants.objects.filter(somefilter)
subq1 = Subquery(Yearly.objects.filter(pk=OuterRef('plantid'), year=YEAR)) tmp1 = tmp.all().annotate(
energy=Count(Subquery(subq1))
)
returns
OperationalError at /xyz
no such column: U0.yid
Which definitely makes sense because Plants has no clue what a yid is, it only knows plantids. How do I adjust the subquery to that?

Django advanced join / query, how to filter foreign keys?

I have this two models.
class City(models.Model):
city = models.CharField(max_length=200)
country = models.CharField(max_length=200)
class CityTranslation(models.Model):
city = models.ForeignKey(City)
name = models.CharField(max_length=200)
lang = models.CharField(max_length=2)
prio = models.IntegerField()
Every city can have multiple translated names within one language.
So I want to get all City's with country="Poland". If a corresponding City has one or more CityTranslations with lang=name. I want to get only the first ordered by prio.
I am doing something like that now.
City.objects.filter(country="Poland", citytranslation__land="pl").annotate(transname=F("alt_names__name"))
But this is not working, because:
If there is a City without a CityTranslation it want be listed
If there are multiple CityTranslation's they all will be shown. But I just want the first. (... .ordered_by('prio').first())
Any idea?
EDIT:
Solved it by using a #property field, which is ordering my CityTranslation by prio and picks the first one:
#propert
def transcity(self):
return self.citytranslation.filter(lang="pl").order_by('-prio').first()
def magic(passed_country="Poland", passed_lang="pl")
# I want to get all City's with country="Poland".
cities = City.objects.filter(country=passed_country)
# If a corresponding City has one or more CityTranslations with lang=name. I want to get only the first ordered by prio.
suitable_cities = cities.filter(citytranslation__lang=passed_lang)
if suitable_cities.exists()
first_matching_city = suitable_cities.orderby('prio').first()
else:
first_matching_city = cities.orderby('prio').first()
return first_matching_city
May need to set up a relatedname on citytranslation.
May not need orderby if you plan on ordering by ID anyways.

Trying to filter based upon a value in another table

I have 2 tables as
class ItemFollowers(models.Model):
item = models.ForeignKey(Items, models.DO_NOTHING, db_column='item')
user = models.ForeignKey(AuthUser, models.DO_NOTHING, db_column='user')
And the other one is
class UsrPosts(models.Model):
item = models.ForeignKey('Items', models.DO_NOTHING, db_column='item')
# Some other fields
How can I select the UsrPosts related to the items followed by some user? i.e. I can have records in ItemFollowers like (item0, user0), (item1, user0), (item5, user0). I need to filter UsrPosts based upon the user (aka. request.user.id)
Here is a inefficient non-working way to get UsrPostts
itms = ItemFollowers.objects.filter(user_id=request.user.id)
qry = Q(item_id=itms[0].item.id) | ..... | Q(item_id=itms[N].item.id)
posts = UsrPosts.objects.filter(qry)
Is there some filter magic to get it in one transaction?
itms = ItemFollowers.objects.filter(user_id=request.user.id).values‌​_list('item')
posts = UsrPosts.objects.filter(item__in = itms)

Django complex query which may need union

I try to do a complex query in Django through these models.
class JobTitleStatus():
PENDING = 0
CONFIRMED = 1
BANNED
class Employer(models.Model):
name = models.CharField(max_length=1000)
isValidated = models.BooleanField(null=False)
eminence = models.IntegerField(blank=False,null=False,default=4)
class JobTitle(models.Model)
name = models.CharField(max_length=1000)
employer = models.ForeignKey(Employer,null=True,blank=True)
status = models.IntegerField(null=False, choices=JobTitleStatus)
I try to list all validated employers depending on size of their confirmed jobtitles.
If there is no Conmfirmed jobtitle of an employer it should be at end of the list.
I try to do
eList = Employer.objects.filter(name__icontains=emp).filter(isValidated=True)
eList.filter(jobtitle__status=JobTitleStatus.CONFIRMED).annotate(jtt_count=Count('jobtitle')).order_by('-jtt_count','eminence')
This query does what I want more or less however, as you expect, employers which doesn't have Confirmed job titles are eliminated.
How can I add those Employers at the end of that query efficiently ?
Thanks
eList.annotate(jtt_count=Count('jobtitle')).order_by('jobtitle__status','-jtt_count','eminence')
should work, I think.

Is it possible to annotate a Django ORM query with a complex query

I have three models, Contender, Week and Vote, each contender can have votes based on week,
class Contender(models.Model)
week = models.ManyToManyField(Week)
class Week(models.Model):
date_start = models.DateField()
class Vote(models.Model):
contender = models.ForeignKey(Contender)
week = models.ForeignKey(Week)
I would like to add something to the Contender, so I did this:
c = Count('vote', vote__week__date_start = "2011-01-03")
contenders = Contender.objects.all().annotate(vote_count=c).order_by('-vote_count')
contenders[0].vote_count
the problem is that when I add a vote with another Week (that has diferent date_start) the .vote_count value is changes and thus it seems like the extra parameters I pass to the Count object does not matter.
How do I do this type of annotation in the Django ORM?
You could start from Vote:
votes = Vote.objects.filter(week__date_start = "2011-01-03") \
.values_list('contender') \
.annotate(cnt=Count('week')).order_by('-cnt')
contender_pks = [d[0] for d in votes]
contenders_dict = Contender.objects.in_bulk(contender_pks)
contenders = []
for pk, vote_count in votes:
contender = contenders_dict[pk]
contender.vote_count = vote_count
contenders.append(conteder)
Also, you can do some denormalization - add
class VoteCount(models.Model):
contender = models.ForeignKey(Contender)
week = models.ForeignKey(Week)
count = models.IntegerField(default=0)
and count votes in it (overriding Vote.save() or using post_save signal), then you will just do:
VoteCount.objects.filter(week__date_start = "2011-01-03") \
.select_related('contender') \
.order_by('-count')
It will be much more efficient performancewise if you do such statistics often.