Django order by queryset from a method - python

I would like to order by entry_count but it seems (from the django error) I can only order by name and status. How can I order by entry_count in this example? Thanks
Category.objects.filter(status='Published').order_by('-entry_count')
Model.py
class Category(models.Model):
"""
Entry Categories.
"""
name = models.CharField(max_length=60, help_text="Title of Category")
status = models.CharField(max_length=16, choices=STATUS_CHOICES,
default="Draft", )
#property
def entry_count(self):
return Entry.objects.filter(category=self.id).count()

You can do this by using aggregation:
from django.db.models import Count
Category.objects.annotate(
entry_count=Count('entry_set')
).order_by('-entry_count')
Like this, you'll get all the counts in one query and all the category objects will have entry_count, so you can remove the #property from the model.

Related

Queryset with annotate Subquery with OuterRef and Sum

I'm trying to return the sum of fields from another model inside a Subquery.
My main queryset returns all users of type company. I have to return the total of credits used by taking the data from CreditOrder and Sum the credit_used field.
I'm using ClusterableModel and ParentalKey from django-modelcluster
My CreditOrder model
class CreditOrder(ClusterableModel):
credit = ParentalKey(
Credit, on_delete=models.CASCADE, related_name="credit_order"
)
order = ParentalKey(Order, on_delete=models.CASCADE, related_name="credit_order")
credit_used = models.DecimalField(
max_digits=12, decimal_places=2, null=True, blank=True
)
My User model
class User(AbstractUser, ClusterableModel):
username = models.CharField(max_length=40, null=True, blank=True)
user_type = models.CharField(max_length=20, choices=TIPO_UTENTE, default="dipendente")
My queryset using the class model User
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.filter(user_type='company')
credits_used_subquery = Subquery(CreditOrder.objects.filter(credit__font__company__id=OuterRef('id')).order_by()
.values('credit_used').annotate(credit_used_sum=Sum('credit_used'))
.values('credit_used_sum'), output_field=DecimalField())
qs = qs.annotate(
_credits_used_sum=credits_used_subquery
)
return qs
But this error is returning me:
django.db.utils.ProgrammingError: more than one row returned by a subquery used as an expression
If you just need to sum all the credits used by the company, you can do:
qs.annotate(_credits_used_sum=Sum('font__credit__credit_used'))
Without seeing all the models between CreditOrder and User, it's hard to tell exactly what you've got wrong. It looks like the Credit model is linked to Font and Font might have an attribute called Company which is a foreign key to the User model?
In any case, your first values call has the wrong argument, you need to group by the the essentially the same thing you are linked to in the outer ref. So I'd suggest
.values('credit__font__company__id')
In the first call to values. And keep the annotate and second call to values the same.
Another answer suggests doing the Sum with a join instead of a Subquery, if you like the simplicity of that api, but you still want to use a Subquery, you can use the django-sql-utils package. After you pip install django-sql-utils
from sql_util.utils import SubquerySum
qs.annotate(_credits_used_sum=SubquerySum('font_credit_credit_used')
I solved my problem with this class:
class SubquerySum(Subquery):
template = "(SELECT COALESCE(SUM(%(field)s), %(zero_value)s) FROM (%(subquery)s) _sum)"
def as_sql(self, compiler, connection, template=None, **extra_context):
if 'field' not in extra_context and 'field' not in self.extra:
if len(self.queryset._fields) > 1:
raise FieldError('You must provide the field name, or have a single column')
extra_context['field'] = self.queryset._fields[0]
if 'zero_value' not in extra_context and 'zero_value' not in self.extra:
extra_context['zero_value'] = 0
return super().as_sql(compiler, connection, template=template, **extra_context)
and
def get_queryset(self, request):
credit_query=CreditOrder.objects.filter(credit__font__company__id=OuterRef('id')).order_by()
.values('credit_used')
qs = super().get_queryset(request)
qs = qs.filter(user_type='company')
qs = qs.annotate(
_credits_used_sum=SubquerySum(credit_query, zero_value=0, field='credit_used')
)
return qs

Django-Tables2 add extra columns from dictionary

I apologize if this question has been asked before but I couldn't find my specific use case answered.
I have a table that displays basic product information. Product details such as price, number of sales, and number of sellers are scraped periodically and stored in a separate database table. Now I want to display both the basic product information and scraped details in one table on the frontend using tables2. To do this, I wrote a function in my Product model to fetch the latest details and return them as a dictionary this way I can use a single Accessor call.
# models.py
class Product(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=256)
brand = models.ForeignKey(Brand)
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES)
def __unicode__(self):
return self.name
def currentState(self):
currentDetailState = ProductDetailsState.objects.filter(
product=self
).latest('created_at')
# return current details as a dictionary
return {
price: currentDetailState.price,
num_sellers: currentDetailState.num_sellers,
num_sales: currentDetailState.num_sales
}
class ProductDetailsState(models.Model):
product = models.ForeignKey(Product)
created_at = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=6, decimal_places=2, null=True)
num_sellers = models.IntegerField(null=True)
num_sales = models.IntegerField(null=True)
def __unicode__(self):
return self.created_at
# tables.py
class ProductTable(tables.Table):
productBrand = tables.Column(
accessor=Accessor('brand.name'),
verbose_name='Brand'
)
currentRank = tables.Column(
accessor=Accessor('currentRank')
)
class Meta:
model = Product
...
How do I now use this returned dictionary and split it into columns in my Product table? Is there another way to use an Accessor than how I am doing it?
You can use the Accessor to traverse into the dict, so something like this should work:
class ProductTable(tables.Table):
# brand is the name of the model field, if you use that as the column name,
# and you have the __unicode__ you have now, the __unicode__ will get called,
# so you can get away with jus this:
brand = tables.Column(verbose_name='Brand')
currentRank = tables.Column()
# ordering on the value of a dict key is not possible, so better to disable it.
price = tables.Column(accessor=tables.A('currentState.price'), orderable=False)
num_sellers = tables.Column(accessor=tables.A('currentState.num_sellers'), orderable=False)
num_sales = tables.Column(accessor=tables.A('currentState.num_sales'), orderable=False)
class Meta:
model = Product
While this works, sorting is also nice to have. In order to do that, your 'currentState' method is a bit in the way, you should change the QuerySet you pass to the table. This view shows how that could work:
from django.db.models import F, Max
from django.shortcuts import render
from django_tables2 import RequestConfig
from .models import Product, ProductDetailsState
from .tables import ProductTable
def table(request):
# first, we make a list of the post recent ProductDetailState instances
# for each Product.
# This assumes the id's increase with the values of created_at,
# which probably is a fair assumption in most cases.
# If not, this query should be altered a bit.
current_state_ids = Product.objects.annotate(current_id=Max('productdetailsstate__id')) \
.values_list('current_id', flat=True)
data = Product.objects.filter(productdetailsstate__pk__in=current_state_ids)
# add annotations to make the table definition cleaner.
data = data.annotate(
price=F('productdetailsstate__price'),
num_sellers=F('productdetailsstate__num_sellers'),
num_sales=F('productdetailsstate__num_sales')
)
table = ProductTable(data)
RequestConfig(request).configure(table)
return render(request, 'table.html', {'table': table})
This simplifies the table definition, using the annotations created above:
class ProductTable(tables.Table):
brand = tables.Column(verbose_name='Brand')
currentRank = tables.Column()
price = tables.Column()
num_sellers = tables.Column()
num_sales = tables.Column()
class Meta:
model = Product
You can find the complete working django project at github

How to filter objects by price range in Django?

I have a model Item with field price.
class Item(models.Model):
title = models.CharField(max_length=200, blank='true')
price = models.IntegerField(default=0)
My query may contain min_price & max_price values. So, my request may be like this: http://example.com/api/items?min_price=50&max_price=500. Can anybody tell me, how can I query items between min & max values? Can I solve it using Django ORM?
Thanks!
Check api reference for range. Like it states
You can use range anywhere you can use BETWEEN in SQL — for dates,
numbers and even characters.
So, in your case:
Item.objects.filter(price__range=(min_price, max_price))
Specifying a FilterSet
For more advanced filtering requirements you can specify a FilterSet class that should be used by the view. For example:
import django_filters
from myapp.models import Item
from myapp.serializers import ItemSerializer
from rest_framework import generics
class ItemListFilter(django_filters.FilterSet):
min_price = django_filters.NumberFilter(name="price", lookup_type='gte')
max_price = django_filters.NumberFilter(name="price", lookup_type='lte')
class Meta:
model = Item
fields = ['min_price', 'max_price']
class ItemList(generics.ListAPIView):
queryset = Item.objects.all()
serializer_class = GameSerializer
filter_class = ItemListFilter
Which will allow you to make requests such as:
http://example.com/api/games?min_price=1.20&max_price=8.00

DRF changing field name values of django models with foreign keys

I followed suggestion from this question
But i need to name one field of query_set to date filed of another object
My models are
class Choice(models.Model):
question = models.ForeignKey(Question, related_name='choice', on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
My view
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.values('choiceTime__choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)
I need to count number of submission in particular dates
I don't know how to name choiceTime__choice_date that serializer recognizes field in query set
class ChoiceDateSerializer(serializers.ModelSerializer):
choiceTime__choice_date = serializers.DateTimeField()
total_votes = serializers.IntegerField()
class Meta:
model = Choice
fields = ('id', 'choice_text','total_votes','choiceTime__choice_date')
i receive
{
"choice_text": "ant tower",
"total_votes": 3,
"choiceTime__choice_date": "2017-04-20"
}
But i want to recieve
{
"choice_text": "ant tower",
"total_votes": 3,
"choice_date": "2017-04-20"
}
Tried different options with no success. Definitely i am missing the point.
For my purposes it is working, but i want to have well written API.
2 option change time submission model?
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
coutner = models.IntegerField(default=0)
Is 2 option considers to be better approach to my particular problem? Thanks!
You are receiving a json object, which you add its key value.
for vote_detail in data:
if vote_detail.choiceTime__choice_date:
vote_detail.choice_date=vote_detail.choiceTime__choice_date
then serialize and save, a quick solution.
You could also add to your model the name that you want to call it. That's closer to backend and maybe worth delving into.
from django.db.models import Count,F
If anybody finds this problem and this is easiest answer i came up to.
As it was suggested before passing to serializer change value using model package functions
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.all().annotate(choice_date=F('choiceTime__choice_date')).values('choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)

Django one-to-many relations in a template

I am searching of a method to obtain html forms from some one-to-many relations, like order-lineorder, invoice-lineinvoice, etc.
Let me an example:
# models.py
class Order(models.Model):
date = models.DateTimeField()
number = models.IntegerField(default=0)
class LineOrder(models.Model):
description = models.TextField()
price = models.FloatField()
order = models.ForeignKey(Order)
# views.py
def order_form(request):
form = OrderForm()
table_lineorder = LineOrderTable([])
RequestConfig(request).configure(table)
return render(request, "order_form.html", {"form": form, "table": table_lineorder})
Then, I want to obtain the order template with "generic attributes" (date, number), and a table list (originally empty) of lines order. Add some action like add, edit and remove must be possible.
I think that a solution like django-tables2 is possible, but I can't add rows dinamically, I think.
Thanks in advice.
[EDIT]
I have found the solution. It is django-dynamic-formset
I'm not quite clear about your question, but I guess this might be what you want:
class Order(models.Model):
date = models.DateTimeField()
number = models.IntegerField(default=0)
items = model.ManyToManyField(Item)
class Item(models.Model):
description = models.TextField()
price = models.FloatField()
It should be equivalent to one-to-many if you don't assign one Item to multiple Orders.

Categories

Resources