Queryset difference based on one field - python

I am trying to compare two querysets based on a single field. But I can't figure out most efficient way to do it.
This is my model and I want to check if old and new room_scans(ForeignKey) has PriceDatum's with the same checkin date. if not, create PriceDatum with that checkin date related to the new room_scan.
class PriceDatum(models.Model):
"""
Stores a price for a date for a given currency for a given
listingscan
Multiple such PriceData objects for each day for next X months are created in each Frequent listing scan
"""
room_scan = models.ForeignKey(RoomScan, default=1, on_delete=models.CASCADE)
room = models.ForeignKey(Room, on_delete=models.CASCADE)
checkin = models.DateField(db_index=True, help_text="Check in date", null=True)
checkout = models.DateField(db_index=True, help_text="checkout date", null=True)
price = models.PositiveSmallIntegerField(help_text="Price in the currency stated")
refund_status = models.CharField(max_length=100, default="N/A")
# scanned = models.DateTimeField(db_index=True, help_text="Check in date", null=True)
availability_count = models.PositiveSmallIntegerField(help_text="How many rooms are available for this price")
max_people = models.PositiveSmallIntegerField(help_text="How many people can stay in the room for this price")
meal = models.CharField(max_length=100, default="N/A", help_text="Tells if breakfast is included in room price")
Below is the code what I am trying to do:
previous_prices_final = previous_prices.filter(refund_status='refund',
current_prices_final=current_prices.filter(
refund_status='refund', max_people=max_people_count, meal=meal).order_by().order_by('checkin')
if len(previous_prices_final) > len(current_prices_final):
difference=previous_prices_final.difference(current_prices_final)
for x in difference:
PriceDatum.objects.create(room_scan=x.room_scan,
room=x.room,
checkin=x.checkin,
checkout=x.checkout,
price=0,
refund_status='refund',
availability_count=0,
max_people=max_people_count,
meal='not_included',
)
The thing is that I get all queries as different, because room_scan foreign key has different time created.
My question is: How do I use difference(), based only on checkin field.

Don't select field that contains creating time. Limit your QS with values.

Related

Django: query filter

I have two models that are related: one is a list of participants. The other is a list of times they have checked in or out of an office.
The table (Checkin) has one record for every checkin/checkout pair. So, there can be many records for any participant.
How can I retrieve only the very last (most recent) record for a participants checkin and then pass the participant and only that most recent Checkin record to my template?
From what I can tell there's no ability to do something like a last() in my template, so how would I go about filtering to get just that single record?
Thank you.
Models:
class Participant(models.Model):
first_name = models.CharField(max_length=50)
middle_initial = models.CharField(max_length=50, blank=True)
class CheckIn(models.Model):
adult = models.ForeignKey(
Participant, on_delete=models.CASCADE, blank=True, null=True, related_name='adult_checkin')
checkin = models.DateTimeField(blank=True, null=True)
checkout = models.DateTimeField(blank=True, null=True)
View snipit:
p_checkins = Participant.objects.all().order_by('created')
queryset = p_checkins
context_object_name = "the_list"
template_name = 'list_of_checkins.html'
You can fetch data through most recent checkin or checkout.
For checkin :
p_checkins = CheckIn.objects.all().order_by('-checkin')[0]
For checkout :
p_checkins = CheckIn.objects.all().order_by('-checkout')[0]
To get the participant name by :
name = p_checkins.adult.first_name
When you use (-) your latest update will be query from database.
p_checkins = CheckIn.objects.all().order_by('-checkin')
or
p_checkins = CheckIn.objects.all().order_by('-checkout')
you can annotate the latest value via a subquery to the participant
from django.db.models import OuterRef, Subquery
checkin_q = CheckIn.objects.filter(adult=OuterRef('pk')).order_by('-checkin')
queryset = Participant.objects.annotate(last_checkin=Subquery(checkin_q.values('checkin')[:1]))
see https://docs.djangoproject.com/en/4.0/ref/models/expressions/#subquery-expressions
Most of the answers so far are correct in several aspects. One thing to note is that if your check_in or check_out values (whichever you use) isn't chronological (and by "most recent", you mean the last added), you'll want to add a created_at datetime field with auto_now option True, or order by the pk.
In addition to the other answers provided and my comment above, you can also get the most recent check in by using the related manager on the participant object.

Django Filter by date and status

My first question here, I've been looking around but couldn't find a solution.
I am building a reservation system, my models are
class Room(models.Model):
number = models.IntegerField()
beds = models.IntegerField()
capacity = models.IntegerField()
category = models.ForeignKey(
RoomCategory, on_delete=models.CASCADE, blank=True, null=True)
class Booking(models.Model):
room = models.ForeignKey(Room, on_delete=models.CASCADE, blank=True, null=True)
check_in = models.DateField()
check_out = models.DateField()
status = models.IntegerField(blank=True, null=True)
if request.method == 'POST':
no_disponible = "No hay habitaciones disponibles, selecciona otras fechas"
if form.is_valid():
room_list = Room.objects.filter(category=1).exclude(booking__check_in__lt=form.cleaned_data['check_out'],
booking__check_out__gt=form.cleaned_data['check_in'], booking__status__gt=0)
I am changing status when the customer confirms so I want to check if dates are available when status is not 1 (I change status to 1 once the payment is approved.
However booking__status__gt=0 doesn't seem to work here
Based on your query, you want to exclude rooms with:
Bookings with check ins that are less than the requested check out.
Bookings with check outs that are greater than the requested check in.
Bookings with status greater than 0.
Your current query is:
Room.objects.filter(category=1).exclude(
booking__check_in__lt=form.cleaned_data['check_out'],
booking__check_out__gt=form.cleaned_data['check_in'],
booking__status__gt=0,
)
This will remove all rooms that have status greater than 0, as well as rooms with invalid checkin/checkout dates, even though some of those rooms with invalid checkin/checkout dates have status as 0. This is because of how exclude works when it spans multi-valued relationships like this.
To fix this, change your query to get all the bookings with the exclusions you want by using Booking, and then compare it against the reverse relationship:
Room.objects.filter(category=1).exclude(
booking__in=Booking.objects.filter(
check_in__lt=form.cleaned_data['check_out'],
check_out__gt=form.cleaned_data['check_in'],
status__gt=0,
)
)
Have a read here to understand the behaviour of exclude in this case more.

Django Fast Access for Time Series Data

I'm working on a web application with Django & PostgreSQL as Backend tech stack.
My models.py has 2 crucial Models defined. One is Product, and the other one Timestamp.
There are thousands of products and every product has multiple timestamps (60+) inside the DB.
The timestamps hold information about the product's performance for a certain date.
class Product:
owner = models.ForeignKey(AmazonProfile, on_delete=models.CASCADE, null=True)
state = models.CharField(max_length=8, choices=POSSIBLE_STATES, default="St.Less")
budget = models.FloatField(null=True)
product_type = models.CharField(max_length=17, choices=PRODUCT_TYPES, null=True)
name = models.CharField(max_length=325, null=True)
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name="children")
class Timestamp:
product = models.ForeignKey(Product, null=True, on_delete=models.CASCADE)
product_type = models.CharField(max_length=35, choices=ADTYPES, blank=True, null=True)
owner = models.ForeignKey(AmazonProfile, null=True, blank=True, on_delete=models.CASCADE)
clicks = models.IntegerField(default=0)
spend = models.IntegerField(default=0)
sales = models.IntegerField(default=0)
acos = models.FloatField(default=0)
cost = models.FloatField(default=0)
cpc = models.FloatField(default=0)
orders = models.IntegerField(default=0)
ctr = models.FloatField(default=0)
impressions = models.IntegerField(default=0)
conversion_rate = models.FloatField(default=0)
date = models.DateField(null=True)
I'm using the data for a dashboard, where users are supposed to be able to view their products & the performance of the products for a certain daterange inside a table.
For example, a user might have 100 products inside the table and would like to view all data from the past 2 weeks. For this scenario, I'll describe the code's proceedure below:
Make call to the backend / server
Server has to filter & aggregate all Timestamps for each Product
Server sends data back to client
Client updates table values
The problem is, that step 2. takes a huge amount of time, and I do not know how to improve the performance.
products = Product.objects.filter(name="example")
for product in products:
product.report_set.filter(date_gte="2021-01-01", date__lte="2011-01-14").aggregate(
Sum("clicks"),
Sum("cost"),
Sum("sales"))
That is how the server is currently retrieving the timestamp values for the displayed products.
Any ideas how to retrieve & structure the data in a more efficient way?
It's slow because of the multiple queries you need to make to the database (in the loop).
See if grouping and annotating is better(one query then perhaps queries for fetching each product):-
Timestamp.objects.filter(daterange=["2011-01-01", "2011-01-15"]).values('product').annotate(sum_clicks=Sum("clicks")).annotate(sum_cost=Sum("cost")).annotate(sum_sales=Sum("sales"))
I don't know if this is possible but if it is it would be even better:-
Timestamp.objects.filter(daterange=["2011-01-01", "2011-01-15"]).values('product').annotate(sum_clicks=Sum("clicks")).annotate(sum_cost=Sum("cost")).annotate(sum_sales=Sum("sales")).select_related('product')
Edit:-
After looking back perhaps this might be better:-
products = Product.objects.filter(name="example", report_set__daterange=["2011-01-01", "2011-01-15"]).annotate(sum_clicks=Sum("report_set__clicks")).annotate(sum_cost=Sum("report_set__cost")).annotate(sum_sales=Sum("report_set__sales"))
Without more detail all i can recommend is to optimize, for database optimization i would follow the instructions listed here but know as you speed up the query there will likely be an increase in memory usage.

How to automatically mark a specific field either False or true after certain time has been elapsed?

I'm creating a Hotel table reservation web app in Django 3.x.x
I have a Table Model with isBooked field that is set to either False or True depending on a table status. Code for this model:
class Table(models.Model):
title = models.CharField(max_length=50, null=True)
t_id = models.IntegerField("Table ID", null=True, unique=True)
isBooked = models.BooleanField('Is Booked', default=False)
chairs_booked = models.IntegerField("Chairs Booked", default=0)
max_chairs = models.IntegerField("Total Chairs", default=0)
max_book = models.IntegerField("Least Bookable", default=0)
chairs_left = models.IntegerField("empty chairs", default=0)
Now, I have another Model called Reservation that stores data for booked Tables. Here is its code:
class Reservation(models.Model):
t_username = models.CharField("Booked By", max_length=100, null=True)
t_email = models.EmailField("Email Address", null=True)
t_phone = models.IntegerField("Phone Number", default=0, null=True)
t_id = models.IntegerField("Table Id", null=True)
booked_date = models.CharField("Check In Date", null=True, max_length=50) # '2020-10-27'
booked_time = models.CharField("Check in Time", null=True, max_length=50) # '7:00'
def _get_checkout_date(self):
t = self.booked_date
return t
checkout_date = property(_get_checkout_date)
def _get_checkout_time(self):
the_time = dt.datetime.strptime(self.booked_time, '%H:%M')
new_time = the_time + dt.timedelta(hours=3)
return new_time.strftime('%H:%M')
checkout_time = property(_get_checkout_time)
at this point, I have a table with following information stored in variables:
booked_time this holds a string for time showing when table was booked e.g. 7:00
checkout_time this holds a string for time showing when table should be vacated/checked-out e.g 10:00
so based on the above variables, I want to automatically mark a table's isBooked property to False after certain time has passed (e.g. 3 hours) since it was booked. How do I pull off such a thing?
if this is bad design ( It smells like one..) how do I automatically mark a table either booked/Free using this information/fields available in their respective models?
If you really need to update such data in database level, a solution would be using cronjobs, but this means to leave Django's domain.
It's not usual such a need, as often the meaning of the information is useful on application level, rather than database level. This means that you should evaluate how much time has passed, or if the passed time is above/below a threshold within a view function or model function.
An elegant solution for this would be to add a property to your model, and remove the is_booked field:
#property
def is_booked(self):
time_treshold_in_hours = 3
booked_datetime = datetime.strptime('{} {}'.format(self.booked_date, self.booked_time), '%Y-%m-%d %H:%M')
booked_deltatime = datetime.now() - booked_datetime
return booked_deltatime.seconds // 3600 > time_treshold_in_hours
and within the view you are evaluating if a table is booked, you can do:
def book_table(request):
# <get table>
if table.is_booked:
return HttpResponse('This table is booked already')
# <do your booking>
I'd also recommend changing the fields booked_date and booked_time to:
booked_on = models.DecimalField()

Filter using Q in Django , when multiple inputs can be null

I have a model product_details, with the following fields brand_name,model_name, bodystyle, transmission, and min_price, max_price
Example of budget values in the dropdown is
1 - 5
5 - 10
10 - 15
I have 5 select dropdown fields on the basis of which I want to filter my results.
I am using the method below to filter based on first three fields
def select(request):
q1 = request.GET.get('brand')
q2 = request.GET.get('model')
q3 = request.GET.get('bodyStyle')
#q4 = request.GET.get('budget')
cars = product_details.objects.filter(Q(bodystyle__icontains=q3)|Q(brand_name__icontains=q1)|Q(model_name__icontains=q2))
#cars = product_details.objects.filter(bodystyle=q3)
return render(request, 'search/search_results.html', {'cars': cars})
I have two questions
1: How do I filter if only 1 or 2 values are selected from dropdowns. What should be my if condition?
2. How do I filter on the basis of range for budget fields? Since the budget needs to be compared with min_price and max_price.
Any help or guidance would be appreciated.
Model Definition:
class product_details(models.Model):
product_id=models.ForeignKey(products, on_delete=models.CASCADE)
model_id=models.ForeignKey(product_models, on_delete=models.CASCADE)
variant_id=models.CharField(primary_key=True,max_length=10)
brand_name=models.CharField(max_length=255, blank=True, null=True)
model_name=models.CharField(max_length=255, blank=True, null=True)
variant_descr=models.CharField(max_length=255, null=True, blank=True)
transmission=models.CharField(max_length=255, blank=True, null=True)
bodystyle=models.CharField(max_length=255, blank=True, null=True)
min_price=models.FloatField(blank=True, null=True)
max_price=models.FloatField(blank=True, null=True)
Please respect python CamelCase naming convention:
class ProductDetails(models.Model):
# ...
For question 2:
selected_min_price= 50
selected_max_price=200
ProductDetails.objects.filter(additional_filters=...).filter(min_price__gt=selected_min_price, max_price__lt=selected_max_price)
See Object.filter(property__lt=...)
ProductDetails.objects.filter(max_price__lte=200, min_price__gte=100) <---- this should give you between ranges of 100 & 200
You can filter your queryset multiple times to apply multiple conditions based on the selected filters, eg:
cars = product_details.objects.all()
if q1:
cars = cars.filter(brand_name__icontains=q1)
if q2:
cars = cars.filter(model_name__icontains=q2)
if q3:
cars = cars.filter(bodystyle__icontains=q3)
For the budget you can use the same technique, if the budget is selected, first split the two values of the range (min, max), then filter your queryset again:
if q4:
budget_min, budget_max = q4.split(' - ')
cars = cars.filter(
min_price__gte=int(budget_min),
max_price__lte=int(budget_max),
)

Categories