Django: calculate average, and sort by field in submodel - python

I have a Product model, with a one-to-many relation with a Rating model. I was previously storing an average_stars field in Product model, which I would update everytime a new Rating was added for that Product model. I would have a function in views.py which would return a QuerySet of all Product instances, ordered by average_star. Is there a way to do this more dynamically using a combination of .aggregate and .order_by, or anything along these lines.
In other words, is there a way to calculate the average for each product from all its respective Rating models, and sort them by that attribute? And which approach is better?

Assuming that your Rating model has a stars field, then you should use annotate:
from django.db.models import Avg
Product.objects.annotate(average_stars = Avg('rating__stars')).order_by('-average_stars')

Related

How to keep assigned attributes to a queryset object after filtering? Alternatives?

Maybe it's a stange answer, so i will explain why i'm doing this.
I have a model of Products. I have to assign each of them some stock.
So i have a function on the Products model that calculates a lot of neccesary things like stock and returns a QuerySet.
Since my db model is a little bit "complicated" i can't use annotations in this case. So i decided to execute this database query manually and then, assign each product on the querySet a stock attribute manually. Something like:
for product in queryset_products:
product.stock = some_stock_calc...
The problem comes when i want to use filters this queryset_product.
after executing something like:
queryset_products = queryset_products.filter(...)
the stock attribute gets lost
Any solution?
Since you can't use annotate(), if you can add a separate column to store stock in your Product table, you can make a the filter queries any time.
Maybe have a celery task that does all the calculations for each Product and save to new column.
Otherwise, without annotate you can't have the stock attribute in the queryset.
It can be solved differently, you can run one loop as
queryset_products = list(queryset_products.filter(...))
for product in queryset_products:
setattr(product, "stock") = some_stock_calc...
Basically, you need to fetch all the records from the database as query being lazy it will be lost since it will be re-evaluated unless results have been cached/stored.
All operations on the queryset like .filter() are symbolic until the queryset is enumerated. Then an SQL query is compiled and executed. It is not effective to calculate the stock on a big unfiltered queryset and then even to run it again filtered. You can split the filter to conditions unrelated to stock appended to the queryset and then a filter related to stock that you evaluate in the same Python loop where you calculate the stock.
result = []
for product in queryset_products.filter(**simple filters):
product.stock = some_stock_calc...
if product.stock > 0 or not required_on_stock:
result append(product)
A cache field of possible active products that could be on stock is very useful for the first simple filter.
Maybe the stock calculation is not more complicated then e.g. a stock at midnight plus a sum of stock operations since midnight. Then the current stock can be calculated by a Subquery in an annotation and filtered together. It will by compiled to one SQL with a main query with joins to your related models and a relative simple subquery for stock. (That would be another question.)

django-filter chained select

I'm using django-filters lib https://django-filter.readthedocs.io/en/master/index.html. I need to make chained select dropdown in my filters.
I knew how to make it with simple django-forms like here https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html.
When user pick region, i need to show cities in this region? Have someone idea or solution how to build filters like this?
Integrate django-smart-selects with how you perform the filtering.
This package allows you to quickly filter or group “chained” models by adding a custom foreign key or many to many field to your models. This will use an AJAX query to load only the applicable chained objects.
In analogy to the original question for Region -> City, the documentation's example is Continent -> Country which fits exactly to what is needed.
Once you select a continent, if you want only the countries on that continent to be available, you can use a ChainedForeignKey on the Location model:
class Location(models.Model):
continent = models.ForeignKey(Continent)
country = ChainedForeignKey(
Country,
chained_field="continent", # Location.continent
chained_model_field="continent", # Country.continent
show_all=False, # only the filtered results should be shown
auto_choose=True,
sort=True)
Related question:
How to use django-smart-select

How to get all objects, excluding ones that have records with same related field id value

Model Model1 has ForeignKey field (my_field) to Model2. I want to retrieve all Model1 without records that already have at least 1 record with same my_field value.
I think you need to get all distinct records from Model1, So this is how you can do it in django.
Model1.objects.all().distinct()
Hope this would answer your question.
Thanks

Odoo 10 - Counting values of linked records

In Odoo 10 I have created my own custom application (using the new studio feature), however I have run into an issue trying to compute data between records that belong to different views.
In the scenario I have two models (model A and model B), where records from model B are connect to records from model A via a many2one relational field. There is a field in Model B that counts a numerical value entered into it.
Ideally what I would like to achieve is have some form of Automated Action / Server Action, that loops through the records in Model A, then loops through related records in Model B adding together the values of the previously mentioned numerical value field and sets the value of a field in model A equal to the equated number, before continuing onto the next record.
For example sake say the field names are:
Model A = x_a
- Model A ID Field = x_id_field
- Target field for computed value = x_compute
Model B = x_b
- many2one field = x_a_id
- numerical field = x_value_field
I have attempted to use the automated actions to execute some basic Python code (because I thought this would be as simple as a nested loop) however all my attempts have been failures due to not being familiar with how to loop through records in odoo and how to access other models and their records (from python).
How would I go about accomplishing this?
Ideally what I would like to achieve is have some form of Automated
Action / Server Action, that loops through the records in Model A,
then loops through related records in Model B adding together the
values of the previously mentioned numerical value field and sets the
value of a field in model A equal to the equated number, before
continuing onto the next record.
Create an Automated Action with Related Document Model = model a
On the Actions tab create a Server Action:
model_b_records = self.env['model_b'].search([('many2one_field', '!=', False)])
for record in model_b_records:
record.many2one_field.target_field_for_computed_value = record.numerical_field
Save the Server Action and execute it.
The code should be self-explanatory, for any questions do not hesitate to ask and comment below.

Get all related many-to-many objects from a Django QuerySet

I have a twisty maze of interrelated Django models, with many-to-many fields describing the relationships.
What's the cleanest way to get a list of unique members of a related model from a QuerySet?
If I have a Item model with a groups ManyToMany pointing to the Groups model.
If I have a queryset of Items, of 'items', how do I get this:
groups = items[0].groups.all().values_list('name', flat=True)
But for the whole set? Do I need to iterate through them all and do set().intersect() ?
One solution is to use 2 queries.
You can use the reverse relationships to query all Groups that an Item in your items points to.
groups = groups.objects.filter(item__in=items).distinct().values_list('name', flat=True)

Categories

Resources