Django Queryset - complex relationship parameter as variable? - django

I have a geographic object that is used in proximity searches. That object is keyed to different other objects via various paths.
Examples:
blog.geo_object
user.profile.geo_object
group.event.geo_object
Right now, i do a bounding box search, which strings like this:
radius_queryset = base_queryset.filter(
user__profile__geo__lat__gte = bounding_box.lat_min,
user__profile__geo__lat__lte = bounding_box.lat_max,
user__profile__geo__lon__gte = bounding_box.lon_min,
user__profile__geo__lon__lte = bounding_box.lon_max,
and then on other objects:
radius_queryset = base_queryset.filter(
blog__geo__lat__gte = bounding_box.lat_min,
blog__geo__lat__lte = bounding_box.lat_max,
blog__geo__lon__gte = bounding_box.lon_min,
blog__geo__lon__lte = bounding_box.lon_max,
)
This follows a general format of:
radius_queryset = base_queryset.filter(
[lookup_path]__geo__lat__gte = bounding_box.lat_min,
[lookup_path]__geo__lat__lte = bounding_box.lat_max,
[lookup_path]__geo__lon__gte = bounding_box.lon_min,
[lookup_path]__geo__lon__lte = bounding_box.lon_max,
)
# where lookup_path = "blog" or "user__profile" in the two above examples
I'm writing enough of these (3 so far, more to come) to want to generalize the queries -- encapsulation being a good friend of maintainability and an ally in the fight against typo-bugs.
So, to my question: short of using exec and eval (which just look ugly), is there a way to get the filter parameter name to sub-in a variable? Am I missing something simple here?

**kwargs is your answer!
def generate_qs(lookup_path, bounding_box):
return base_queryset.filter(**{
lookup_path + '__geo__lat__gte' : bounding_box.lat_min,
lookup_path + '__geo__lat__lte' : bounding_box.lat_max,
lookup_path + '__geo__lon__gte' : bounding_box.lon_min,
lookup_path + '__geo__lon__lte' : bounding_box.lon_max,
})
radius_queryset = generate_qs('blog', bounding_box)

I think you can use Python's **kwargs syntax to put this nastiness away in a helper function. Something like:
def helper(lookup_path, bounding_box):
return dict([ ("%s__geo__%s" % (lookup_path, lkup_left, ),
getattr(bounding_box, lkup_right), )
for lkup_left, lkup_right in
(("lat__gte", "lat_min", ),
("lat__lte", "lat_max", ),
("lon__gte", "lon_min", ),
("lon__lte", "lon_max", ),
) ])
qs = base_queryset.filter(**helper(lookup_path, bounding_box))

Related

Django Calculate Mean Of 2 Fields Inside Multiple Objects

I'm trying to do a very simple math problem but I don't know how to convert it into python. Basically I need to calculate the mean entry price for a trade based on multiple "buy" entries. To do that all one needs to do is calculate
∑ (entry.amount * entry.price) / ∑ (entry.amount)
This should be the variable "total_entry_price2" in the end.
Where am I going wrong with the calculation? How Can I add all the ∑'s together?
Is this the best way to do it?
models.py
class Trade(models.Model):
...
class Entry(models.Model):
...
trade = models.ForeignKey(Trade, on_delete=models.CASCADE)
amount = models.FloatField()
price = models.FloatField()
entry_type = models.CharField(max_length=3, choices=ENTRY_TYPE_CHOICES, default=BUY)
views.py
#login_required
def trade_detail_view(request, pk):
logger.info('trade detail view')
if request.method == 'GET':
trade = get_object_or_404(Trade, pk=pk)
entries = Entry.objects.filter(trade=trade)
entries_buy = Entry.objects.filter(trade=trade, entry_type="buy")
patterns = Pattern.objects.filter(trade=trade)
for entry in entries_buy:
total_entry_price = Sum(entry.amount * entry.price)
total_entry_price2 = total_entry_price / entries_buy.aggregate(Sum('amount'))
print(total_entry_price2)
context = {
'trade': trade,
'entries': entries,
'patterns': patterns,
'max_amount': entries_buy.aggregate(Sum('amount')),
'total_fees': entries.aggregate(Sum('fee')),
'entry_price': entries_buy.aggregate(Avg('price'))
}
Current Terminal print:
Sum(Value(60.0)) / Value({'amount__sum': 40.0})
Sum(Value(10.0)) / Value({'amount__sum': 40.0})
Example data
The correct answer should be $1.75
(30 * 2 + 10 * 1) / 40 = 1.75
Final Solution (added upon from Oleg Russkin's Answer)
The revisions I did are as follows:
total_entry_cost = entries_buy.annotate(
s=F('amount') * F('price')
).aggregate(
total_entry_cost=ExpressionWrapper(
Sum(
Cast('s', output_field=models.FloatField())
) / Sum('amount'),
output_field=models.FloatField()
)
)['total_entry_cost']
print(total_entry_cost)
Example query to calculate required value.
Cast() to float may be avoided if the result of Sum is float, not an integer.
from django.db import models
from django.db.models import ExpressionWrapper, F, Sum
from django.db.models.functions import Cast
total_entry_price2 = Entry.objects.annotate(
s=F('amount')+F('price')
).aggregate(
price2=ExpressionWrapper(
Sum(
Cast('s',output_field=models.FloatField())
) / Sum('amount'),
output_field=models.FloatField()
)
)['price2']
# Actual result of the query is dictioanry
# so we get the key
# {'price2': 0.59633706227207}
Updated Answer By OP
This answer almost got us all the way. It was my fault not to be more clear on the exact answer I was looking for. I updated my question near the end to reflect it.
The revisions I did are as follows:
total_entry_cost = entries_buy.annotate(
s=F('amount') * F('price')
).aggregate(
total_entry_cost=ExpressionWrapper(
Sum(
Cast('s', output_field=models.FloatField())
) / Sum('amount'),
output_field=models.FloatField()
)
)['total_entry_cost']
print(total_entry_cost)

What is an effective method to get a Django class reference when there are duplicated class names in different modules?

I have a function that takes a class reference to create an SQL string.
I can get the class reference from globals() as long as the class name is unique.
Example call.
module_name_a.models.get_insert_string(globals()['DriverShiftPreference'])
If there are duplicate class names in different modules then I get collisions.
For example, if there is module_name_a.models.Driver and module_name_b.models.Driver then
module_name_a.models.get_insert_string(globals()['Driver'])
returns a result from module_name_b.models.Driver.
What is an effective method to get a Django class reference when there are duplicated class names in different modules?
Found using inspect gets most of the way there.
# in a module called junction
import inspect
def get_insert_string(class_reference):
fields = []
arguments = []
for field in class_reference._meta.fields:
if field.primary_key:
pass
elif field.help_text:
fields.append('"%s"' % field.help_text)
arguments.append('?')
result = 'insert into %s ( %s ) values ( %s )' % ( class_reference.filemaker_table_name, u','.join(fields), u','.join(arguments))
return result
def make_models_map_from_module(module_):
result = {}
for item in inspect.getmembers(module_.models):
key = item[0]
value = item[1]
if key == '__builtins__':
break
else:
result[key] = value
return result
Sample usage:
import junction
import depot_maestro
depot_maestro_model_map = junction.models.make_models_map_from_module(depot_maestro)
junction.models.get_insert_string(depot_maestro_model_map['Driver'])
u'insert into driver ( "DEPOT_ID","uuid","ABN_number","account_name","address_1","advance_payment_days","bank_account","barred_flag","biometric_print","bsb","change_log","comm_fixed_percent","company","contract_end_date","contract_fixed","contract_notice_period","contract_start_date","contract_terms","country","date_activated","DOB","default_roster_template_id","EFT_code","email","external_id","fax","first_name","fuel_pin","fuel_split_percent","gender","is_owner","known_as","last_contacted","middle_name","nationality","Next_of_kin","next_of_kin_mobile","Next_of_kin_phone","next_of_kin_relationship","next_of_kin_work","passport","password","pay_in_choice","phone","pref_age","pref_body_type","pref_name_display","pref_transmission","remit_gst_for_ATO_who_remits","roster_template_id","status","surname","toll_serial_no","tsl_owner","unreliable_flag","www_LastKnown_GPS","www_LastKnown_Status","www_LastKnown_Timestamp","creationAccount","creationTimeStamp","modificationAccount","modificationTimeStamp" ) values ( ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? )'
Found a much simpler way. Just pass in the model.
Example.
junction.models.get_insert_string(depot_maestro.models.Group)
which returns
insert into groups ( "DEPOT_ID","uuid","Notes","Percentage","abn","address","admin_fee","bank_details","bookings_percent","creationAccount","creationTimeStamp","email","fax","group_account_name","group_colour","group_logo","group_logo_2","group_name","group_number","group_service_mark_as_paid","gst","message","meter","modificationAccount","modificationTimeStamp","omit_from_group_reports","parts_at_cost","phone","remit_all_cash","rollover_amount","roster_group","sort_order","vehicle_fk" ) values ( ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? )'

How make conditional expression on Django with default value of object?

See https://docs.djangoproject.com/en/dev/ref/models/conditional-expressions/
conditional expression with Case;
o = Object.annotate(
custom_price=Case(
When(price=1, then=Value(2)),
default=0,
output_field=DecimalField(),
)
)
How use set 'default' - current value of Object?
Now it writed only as const: 0
Want something like:
if price =1:
custom_price = 2
else:
custom_price = Object.price
F is used to perform this. So, your code should look like:
from django.db.models import Case, F, Value, When, DecimalField
o = Object.objects.annotate(custom_price=Case(
When(price=1, then=Value(2)),
default=F('price'),
output_field=DecimalField(),
)
)

Django Q Objects - Return both True and False matches

I am building a search feature with multiple T/F filter options, like so:
Search: ________________
Filters:
___ Open to Public
___ Parking Available
_x_ Free Entrance
In this case the user is specifically looking for events that are free to get into. I would like my Q object to return keyword matched objects where free_entrance is set to True.
My thought is to define the variables in my search function:
search_public = None
search_parking = None
search_free_entrance = True
and set the Q object up like so:
q_objects.append(
Q(
name__icontains=search_term,
public__icontains=search_public,
parking__icontains=search_parking,
free_entrance=search_free_entrance
)
)
However, I want all objects (True or False) to be returned for the unfilitered variables (instead of only objects set to None). Is there a keyword I can insert, or is there a Q object filter type that I am missing?
UPDATE:
In addition to the posted answer, *args can also be used for complex (OR) Q objects:
From:
http://www.nomadjourney.com/2009/04/dynamic-django-queries-with-kwargs/
args = ( Q( name__icontains = 'search_term' ) | Q( company__icontains = 'search_term' ) )
Use kwargs:
search = {'name__icontains': search_term}
if search_public is not None:
search.update({'public__icontains': search_public})
if search_parking is not None:
search.update({'parking__icontains': search_parking})
if search_free_entrance is not None:
search.update({'pree_entrance__icontains': search_free_entrance})
q_objects.append(Q(**search))
or more complicated example (your question from comment):
search_kwargs = {'name__icontains': search_term}
search_args = tuple()
if search_public is not None:
search_args += (Q(Q(company__icontains=search_public) | Q(other__icontains=search_public)),)
q_objects.append(Q(*search_args, **search_kwargs))

How to include "None" in lte/gte comparisons?

I've got this complex filtering mechanism...
d = copy(request.GET)
d.setdefault('sort_by', 'created')
d.setdefault('sort_dir', 'desc')
form = FilterShipmentForm(d)
filter = {
'status': ShipmentStatuses.ACTIVE
}
exclude = {}
if not request.user.is_staff:
filter['user__is_staff'] = False
if request.user.is_authenticated():
exclude['user__blocked_by__blocked'] = request.user
if form.is_valid():
d = form.cleaned_data
if d.get('pickup_city'): filter['pickup_address__city__icontains'] = d['pickup_city']
if d.get('dropoff_city'): filter['dropoff_address__city__icontains'] = d['dropoff_city']
if d.get('pickup_province'): filter['pickup_address__province__exact'] = d['pickup_province']
if d.get('dropoff_province'): filter['dropoff_address__province__exact'] = d['dropoff_province']
if d.get('pickup_country'): filter['pickup_address__country__exact'] = d['pickup_country']
if d.get('dropoff_country'): filter['dropoff_address__country__exact'] = d['dropoff_country']
if d.get('min_price'): filter['target_price__gte'] = d['min_price']
if d.get('max_price'): filter['target_price__lte'] = d['max_price']
if d.get('min_distance'): filter['distance__gte'] = d['min_distance'] * 1000
if d.get('max_distance'): filter['distance__lte'] = d['max_distance'] * 1000
if d.get('available_on'): # <--- RELEVANT BIT HERE ---
filter['pickup_earliest__lte'] = d['available_on'] # basically I want "lte OR none"
filter['pickup_latest__gte'] = d['available_on']
if d.get('shipper'): filter['user__username__iexact'] = d['shipper']
order = ife(d['sort_dir'] == 'desc', '-') + d['sort_by']
shipments = Shipment.objects.filter(**filter).exclude(**exclude).order_by(order) \
.annotate(num_bids=Count('bids'), min_bid=Min('bids__amount'), max_bid=Max('bids__amount'))
And now my client tells me he wants pickup/drop-off dates to be 'flexible' as an option. So I've updated the DB to allow dates to be NULL for this purpose, but now the "available for pickup on" filter won't work as expected. It should include NULL/None dates. Is there an easy fix for this?
Flip the logic and use exclude(). What you really want to do is exclude any data that specifies a date that doesn't fit. If pickup_latest and pickup_earliest are NULL it shouldn't match the exclude query and wont be removed. Eg
exclude['pickup_latest__lt'] = d['available_on']
exclude['pickup_earliest__gt'] = d['available_on']
Most database engines don't like relational comparisons with NULL values. Use <field>__isnull to explicitly check if a value is NULL in the database, but you'll need to use Q objects to OR the conditions together.
Don't think that's actually a django-specific question. Variable 'd' is a python dictionary, no? If so, you can use this:
filter['pickup_latest__gte'] = d.get('available_on', None)