counting self referencing many to many field - python

I have the following self referencing model that gives who this profile supports.
supports = models.ManyToManyField('self', blank=True, symmetrical=False,
related_name='supporters')
And I want the count not supports, supporters that support this user.
Any ideas ?
def get_queryset(self):
qs = super(ProfileListView, self).get_queryset()
if self.request.GET.get('s'):
sorting = self.request.GET.get('s')
if sorting == 'pop':
qs = ......
return qs

I will assume I have an instance of your model with the name myUser:
supporters = myUser.supports_set.all()

Annotate number of supporters like below, solved my problem
qs = qs.annotate(number_of_supporters=Count('supporters'))
qs = qs.order_by('-number_of_supporters')

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

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 filter reversed foreignkey

I have these tables in a django app:
class Order(models.Model):
...
class SubOrder1(models.Model):
order = models.ForeignKey(Order, blank=True, null=True)
class SubOrder2(models.Model):
order = models.ForeignKey(Order, blank=True, null=True)
...
How can I write a query on Order which results only the orders which has at least one related SubOrder1 or SubOrder2? I need something like
Order.objects.filter(suborder__count__ge = 1, ...)
I am using Django = 1.9.2 and Python = 3.4.1
Use isnull field lookup:
orders_with_suborders = Order.objects.filter(
Q(suborder1__isnull=False) | Q(suborder2__isnull=False)
)
Annotate your queryset with the counts of the related models:
from django.db.models import Count
queryset = Order.objects.annotate(
num_suborder1=Count('suborder1', distinct=True),
num_suborder2=Count('suborder2', distinct=True),
)
See the docs about combining multiple aggregations to explain why we need distinct=True
Then you can use Q to filter objects where either of the counts is at least 1.
orders_with_suborders = queryset.filter(
Q(num_suborder1__gte=1) | Q(num_suborder1=1__gte=1),
)
A solution i just found would be something like:
Order.objects.filter(suborder1__id__gt = -1)
and i could use the same for SubOrder2. This is a solution, but not really djangonic. Is there any better solution?
You can get to the SubOrder1 from Order.
order = Order.objects.get(pk=pk)
suborders1 = order.suborder1_set.all()
Then you can iterate through the suborders:
for suborder in suborders1:
print (suborder)
Hope it help you

Django Admin: How to sort by data from another model?

I have the following two models in a Django app: Event and EventDate. I like to show some calculated data from EventDate in the admin list view of Event and I want to be able to sort by it.
I calculate the next upcoming date for each Event from the data in EventDate. Now I want it to not only show up in the Django Admin list view for Event but I also want the events sorted by it.
The Django models look like this:
class EventDate(models.Model):
date = models.DateField(db_index=True)
start = models.TimeField(blank=True, null=True)
end = models.TimeField(blank=True, null=True)
event = models.ForeignKey('Event')
class Event(TimeStampedModel):
title = models.CharField(max_length=200)
text = models.TextField()
published = models.BooleanField(default=False, db_index=True)
What I tried so far
First I gave my Event model a property that returns the next date, which kinda works: I can show the next date in the list_display and even optimize with prefetch_related. But I can't sort on that field in the list display :(
Next I tried to add to the model with extra, to work around this limitation... Not that nice and forward but I came up with this:
class EventAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(EventAdmin, self).get_queryset(request)
qs.extra(
select={
'n2': """SELECT MIN(events_eventdate.date) FROM events_eventdate
WHERE events_event.id == events_eventdate.event_id AND
events_eventdate.date >= '{}'
""".format(now().date().isoformat())
}
)
return qs
It works in the debug shell but I can't get it to work in the admin. I have no clue on how to include it in list_display or what else to do.
Seems like I found a way to use the data from the extra call...
I added a method to the EventAdmin and set admin_order_field for that methode to the field generated by the extra call:
class EventAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(EventAdmin, self).get_queryset(request)
qs = qs.prefetch_related('eventdate_set')
qs = qs.extra(
select={
'next_date': """SELECT MIN(events_eventdate.date)
FROM events_eventdate
WHERE events_event.id == events_eventdate.event_id AND
events_eventdate.date >= '{}'
""".format(now().date().isoformat())
}
)
return qs
def next_date(self, obj):
try:
return obj.next_date
except IndexError:
return 'abgelaufen'
next_date.short_description = 'Nächster Termin'
next_date.admin_order_field = 'next_date'
Not sure if this is clean or sane... it seems to work and I go for it, since it's pretty hard to get answers to Django Questions here. Will report if it bails on me.

django : order queryset by model method without converting to list

i have a model Complaint with get_latest_response method
class Complaint(models.Model):
message = models.TextField(blank=True)
email = models.CharField(max_length=255)
def get_latest_response(self):
return ResponseLetter.objects.filter(complaint=self)[:1][0]
method get_latest_response return the latest respone to this complaint
and ResponseLetter model
class ResponseLetter(models.Model):
date_response = models.DateField(db_index=True)
response_content = models.TextField()
response_from = models.CharField(max_length=255)
complaint = models.ForeignKey(Complaint)
class Meta:
ordering = ["-date_response"]
then in view i get complaints (queryset)
complaints = Complaint.objects.all()
and i have to order these complaints by get_latest_response().date_response
but! i cant convert this queryset to list like
complaints = list(complaints)
complaints.sort(key=lambda x: x.get_latest_response().date_response)
because after that i render this queryset in object_list :
return object_list(request, template_name = 'complaint/complaints.html',
queryset = complaints, paginate_by = COMPLAINTS_PAGE_SIZE,
extra_context=extra_context
)
also method :
sorted(complaints, key=lambda a: a.get_latest_response().date_response)
doesnt work too
can you give me any advice please ?
You want to list Complaints ordered by ResponseLetter.date_response. But order_by and distinct don't play well together. Here's a workaround:
from django.db.models import Max
Complaint.objects.annotate(max=Max('responseletter__date_response')).order_by('-max')
Tested/working.

Categories

Resources