I have a model class with a status field, which might have several alternatives, say:
class MyModel(models.Model):
status_choices = (
('status1', 'status1'),
('status2', 'status2'),
('status3', 'status3'),
('status4', 'status4'),
)
status = models.CharField(choices=status_choices)
Then I want to annotate the instances with an active field which might be either True or False. The instance is active when status is IN [status1, status2].
Django version is 1.11.
We can do this with a Case expression [Django-doc]:
from django.db.models import BooleanField, Case, Value, When
MyModel.objects.annotate(
active=Case(
When(status__in=['status1', 'status2'], then=Value(True))
default=Value(False),
output_field=BooleanField()
)
)
Note that the list of the status__in condition should contain the keys of the choices (so the left item of the tuples, not the right one).
Related
suppose
class Msg(models.Model):
...
likes = models.ManyToManyField(User,...)
channelname = models.CharField(...)
Now my queryset is
queryset = Msg.objects.filter(channelname='home')
What should i do after this to get somelike
[{id:xyz,liked=true},{id:tuv,liked=true},{id:abc,liked:false}]
You can annotate an Exists() subquery using the through model of your many to many field:
from django.db.models import Exists, OuterRef
liked_subquery = Msg.likes.through.objects.filter(
msg=OuterRef('pk'), # Filter for outer queries Msg instance
user=some_user_instance # Filter for user whose like we are checking for
)
queryset = Msg.objects.filter(
channelname='home'
).annotate(
liked=Exists(liked_subquery)
).values('id', 'liked')
print(list(queryset))
I want to have my queryset of Card model annotated with new field called available_on, which should be calculated as closest date in future of relative Booking model's field removal_date. It should consider only dates in future, how can I filter out removal_date dates that are in the past? What I have now is this.
def with_available_on(self):
qs = self.annotate(available_on=Case(
When(
bookings_count__gt=0,
slots_available__lt=1,
then=Min('bookings__removal_date')),
default=None
)
)
return qs
Also I want it to be calculated on database side if possible for performance purposes. Thanks
You can use the filter=… parameter [Django-doc] to filter the objects over which you span the aggregate in the Min aggregate [Django-doc]:
from django.db.models import Q
from django.db.models.functions import Now
def with_available_on(self):
qs = self.annotate(available_on=Case(
When(
bookings_count__gt=0, slots_available__lt=1,
then=Min(
'bookings__removal_date',
filter=Q(bookings__remval_date__gte=Now())
)
),
default=None)
)
return qs
Models
class User(AbstractUser):
pass
class Report(Model):
user = ForeignKey (
"User",
related_name="reports"
)
shared_doctors = ManyToManyField (
"User",
symmetrical = False,
related_name="shared_reports"
)
I have more fields on the models, but I have omitted them in the interest of shortening the problem.
Query
User.objects.annotate(
shared_reports_user = FilteredRelation(
'shared_reports',
condition = Q(shared_reports__user=user)
)
).annotate(
shared_reports_user_count = Count('shared_reports_user')
)
I have brought down the query to the base level which is giving unexpected results. user in the first annotate is an instance of User.
The Query runs successfully, but the resulting QuerySet has no shared_reports_user key, though it has a shared_reports_user_count key, which has a value of 0.
As far as I understood from the documentation, FilteredRelation takes a field present on the Model that it will filter on, and a named argument, condition, that takes a Q object as a condition to be applied to the existing field value. This leads me to believe that the first annotation should return a QuerySet annotated with a subset of shared_reports satisfying the condition specified in the Q object.
I'm sure my database contains values satisfying the Q object (I have manually inspected the data), and even in case there is no output, I would have expected an empty QuerySet against the key shared_reports_user.
Am I missing something?
You can use Conditional aggregation in Django 2.0 like this:
User.objects.annotate(
shared_reports_user_count=Count('shared_reports', filter=Q(shared_reports__user_id=user.id))
class Forecast(Model):
id = UUID()
type = StringField()
approved = BooleanField()
I want to group on the field type by applying 'logical and' on approved field. Suppose the annotated field is all_approved. all_approved should be True if all the items with that type is True, and false if atleast one is False.
So finally in my queryset i want to have two fields type, all_approved.
How can i achieve this?
I tried something based on this answer, but couldn't get anything.
EDIT:
when i tried whats given in that answer, its not doing 'logical and'. Instead for each type it just give two items, one with all_approved as True, another with all_approved as False. I want a single item for each type.
Also i don't understand why that answer should work. Where is it specified if while grouping it should do 'logical and' or 'logical or'.
other solution: you can try compare all approved with approved=True
from django.db.models import Count, Case, When, BooleanField
Forecast.objects.values(
'type'
).annotate(
cnt_app=Count(Case(When(approved=True, then=1)))
).annotate(
all_approved=Case(
When(cnt_app=Count('approved'), then=True),
default=False,
output_field=BooleanField()
)
).values('type', 'all_approved')
where
Count(Case(When(approved=True, then=1))) gives us count of the approved with status True for the type,
Count('approved') gives us total count of the all for the type,
and if the values is equal then all_approved is True other way False
You can use a subquery to flip all_approved to False for types that have at least one False value:
from django.db.models import BooleanField
from django.db.models.expressions import Case, When
(Forecast.objects
.annotate(all_approved=Case(
When(type__in=Forecast.objects.filter(approved=False).values('type'),
then=False),
default=True,
output_field=BooleanField()
))
.values('type', 'all_approved')
.distinct()
)
The question you've linked is a bit different because it relates to a one-to-many relationship between two models that are joined automatically by Django.
Here you have just one model, which you'd have to join with itself to use the same solution. Since Django only supports joins defined by relationships, you need the subquery as a workaround.
Imagine I have the following 2 models in a contrived example:
class User(models.Model):
name = models.CharField()
class Login(models.Model):
user = models.ForeignKey(User, related_name='logins')
success = models.BooleanField()
datetime = models.DateTimeField()
class Meta:
get_latest_by = 'datetime'
How can I get a queryset of Users, which only contains users whose last login was not successful.
I know the following does not work, but it illustrates what I want to get:
User.objects.filter(login__latest__success=False)
I'm guessing I can do it with Q objects, and/or Case When, and/or some other form of annotation and filtering, but I can't suss it out.
We can use a Subquery here:
from django.db.models import OuterRef, Subquery
latest_login = Subquery(Login.objects.filter(
user=OuterRef('pk')
).order_by('-datetime').values('success')[:1])
User.objects.annotate(
latest_login=latest_login
).filter(latest_login=False)
This will generate a query that looks like:
SELECT auth_user.*, (
SELECT U0.success
FROM login U0
WHERE U0.user_id = auth_user.id
ORDER BY U0.datetime DESC
LIMIT 1
) AS latest_login
FROM auth_user
WHERE (
SELECT U0.success
FROM login U0
WHERE U0.user_id = auth_user.id
ORDER BY U0.datetime
DESC LIMIT 1
) = False
So the outcome of the Subquery is the success of the latest Login object, and if that is False, we add the related User to the QuerySet.
You can first annotate the max dates, and then filter based on success and the max date using F expressions:
User.objects.annotate(max_date=Max('logins__datetime'))\
.filter(logins__datetime=F('max_date'), logins__success=False)
for check bool use success=False and for get latest use latest()
your filter has been look this:
User.objects.filter(success=False).latest()