How to mimic Python set with django ORM? - python

I am working on a membership application. I would like to make a membership reminder. (member during a period of time which is not member for another period of time).
Currently, I am using set for making this calculation. See the code below.
class Member(models.Model):
...
class Membership(models.Model):
member = models.ForeignKey(Member, verbose_name=_("Member"))
start_date = models.DateField(_("Start date"))
end_date = models.DateField(_("End date"))
x = Member.objects.filter(Q(membership__start_date__lte=dt1) & Q(membership__end_date__gte=dt1))
y = Member.objects.filter(Q(membership__start_date__lte=dt2) & Q(membership__end_date__gte=dt2))
result = set(x) - set(y)
I would like to know of I can do it only by using the django ORM (filter, exclude, annotate, distinct ...)?
Thanks in advance for your help
UPDATE
In fact, my model is a bit more complex. I also have newspaper foreign key.
class Member(models.Model):
...
class Newspaper(models.Model):
...
class Membership(models.Model):
member = models.ForeignKey(Member, verbose_name=_("Member"))
start_date = models.DateField(_("Start date"))
end_date = models.DateField(_("End date"))
newspaper = models.ForeignKey(Newspaper)
I want to have the reminder for a given newspaper. In this case, the working query is
sin = models.Membership.objects.filter(start_date__lte=dt1,
end_date__gte=dt1,
newspaper__id=2)
sout = models.Membership.objects.filter(start_date__lte=dt2,
end_date__gte=dt2,
newspaper__id=2)
result = models.Member.objects.filter(membership__in=sin).exclude(membership__in=sout)
I think that this a more verbose version of the answer given Ghislain Leveque which is also working well for me.
Thanks to S.Lott and KillianDS for very valuable answers and sorry for not so clear question :)

Isn't it simply negating the second expression and putting it in the same filter? So you have something like !(a&b), which equals to (!a)|(!b), in this case:
result = Member.objects.filter(membership__start_date__lte=dt1, membership__end_date__gte=dt1, ~Q(membership__start_date__lte=dt2) | ~Q(membership__end_date__gte=dt2))
note by the way that for simple anding and basic lookups you need no Q objects, like I showed with the first two lookup parameters. Anding happens just by passing multiple arguments, Q objects are needed for negating and OR'ing lookups.

A relational database table is a set -- by definition. Set - is where not exists in SQL, which is exclude in Django's ORM.
It seems (without testing) that you're doing this.
result = Member.objects.filter(
Q(membership__start_date__lte=dt1) & Q(membership__end_date__gte=dt1)
).exclude(
Q(membership__start_date__lte=dt2) & Q(membership__end_date__gte=dt2)
)

You should try :
result = Member.objects.\
filter(
membership__start_date__lte = dt1,
membership__end_date__gte=dt1).\
exclude(
pk__in = \
Member.objects.filter(
membership__start_date__lte = dt2,
membership__end_date__gte = dt2).\
values_list('pk')

Related

Django Model Struct within a Struct?

I'm trying to create a structure within a structure under Django's models.py. I know how to do this in C but am new to python.
For example, I have a class:
class OneDay(models.Model)
hour1 = ?
hour2 = ?
...
hour23 = ?
hour24 = ?
Each hour will have a list of identical properties. I want to apply these properties to every single hour within the OneDay class. I don't believe they are ForeignKeys.
class PeriodProperties(models.Model)
isX = boolean
isY = boolean
isZ = boolean
R = number
P = number
So with this I would be able to look up:
hour5.isX == false?
hour10.R > 50%?
Something like that. Can anyone point me in the right direction? Thank you.
You're going to want to check out https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_one/
Basically, you make:
class OneDay(models.Model)
hour1 = models.ForeignKey("PeriodProperties", on_delete=models.SET_NULL, related_name="hour1s")
...
Then you could do something like:
oneDay.hour1.isX

Build a list of values from queryset list (pk__in) in special order

i have a model with ratings, the results of a filter query must be in a special order for chartit (comparing ratings for trainee's) but I can't find the right way to do it. (ok I'm new to Django and python ;)
class Bewertung(models.Model):
auffassung = models.PositiveSmallIntegerField()
interesse = models.PositiveSmallIntegerField()
arbeitsabw = models.PositiveSmallIntegerField()
aufmerksamkeit = models.PositiveSmallIntegerField()
arbeitsgenauigkeit = models.PositiveSmallIntegerField()
verhalten = models.PositiveSmallIntegerField()
ausb_sach = models.PositiveSmallIntegerField(null=True, blank=True)
ausb_fuehr = models.PositiveSmallIntegerField(null=True, blank=True)
the query:
qs = Bewertung.objects.filter(pk__in=pk_list)
I want to compare the integer values in a multi bar chart e.g.
auffassung_from_pk(1,2,3) interesse_from_pk(1,2,3) .. n
but every try ends in a list with a lot of unordered values
(Auffassung_from_pk(1), interesse_from_pk(1), Auffassung_from_pk(2) ..)
I can't find a way to solve it nice and efficient in an python way.
so I need a little help, can you help?
#Sachin Kukreja correct, separate it and order it. so every field (e.g. auffassung must be one list with every result from the queryset.)
if I have 3 resulting query sets (pk_list=(1,2,3)) I need something like ((1,2,1),(2,3,3)...) ((auffassung),(interesse))
#Rajez there are no multiple filters
i set all my approach's to zero to start new. I have only this (in the Django shell)
for q in qs:
print(q.auffassung)
print(q.interesse)
i am really struggling at this at the moment
try it:
import itertools
qs = Bewertung.objects.filter(pk__in=pk_list)
values = qs.values_list('auffassung', 'interesse')
result = list(itertools.chain(*values))

Joining two querysets in Django

Suppose I have the following models
class Award(models.Model):
user = models.ForeignKey(User)
class AwardReceived(models.Model):
award = models.ForeignKey(award)
date = models.DateField()
units = models.IntegerField()
class AwardUsed(models.Model):
award = models.ForeignKey(award)
date = models.DateField()
units = models.IntegerField()
Now, suppose I want to get the number of awards for all users and the number of awards used for all users (ie, a queryset containing both). I prefer to do it one query for each calculation - when I combined it in my code I had some unexpected results. Also for some of my queries it won't be possible to do it one query, since the query will get too complex - I'm calculating 8 fields. This is how I solved it so far:
def get_summary(query_date)
summary = (Award.objects.filter(awardreceived__date__lte=query_date))
.annotate(awarded=Sum('awardissuedactivity__units_awarded')))
awards_used = (Award.objects.filter(awardused__date__lte=query_date)
.annotate(used=Sum('awardused__date__lte__units')))
award_used_dict = {}
for award in awards_used:
award_used_dict[award] = award.used
for award in summary:
award.used = award_used_dict.get(award, 0)
return summary
I'm sure there must be a way to solve this without the dictionary approach? For instance, something like this: awards_used.get(award=award), but this causes a db lookup every loop.
Or some other fancy way to join the querysets?
Note this is a simplified example and I know for this example the DB structure can be improved, I'm just trying to illustrate my question.
SOLUTION 1
Just try to concatenate your queryset using |
final_q = q1 | q2
In your example
final_q = summary | awards_used
UPDATED:
| does not works using calculated attributes, so, we can select our queryset first and then mapping our extra attributes
summary = Award.objects.filter(awardreceived__date__lte=query_date)
awards_used = Award.objects.filter(awardused__date__lte=query_date)
final_q = summary | awards_used
final_q = final_q.annotate(used=Sum('awardused__date__lte__units')).annotate(awarded=Sum('awardissuedactivity__units_awarded'))
SOLUTION 2
Using chain built-in function
from itertools import chain
final_list = list(chain(summary, awards_used))
There is an issue with this approach, you won't get a queryset, you will get a list containing instances.

A puzzle concerning Q objects and Foreign Keys

I've got a model like this:
class Thing(models.Model):
property1 = models.IntegerField()
property2 = models.IntegerField()
property3 = models.IntegerField()
class Subthing(models.Model):
subproperty = models.IntegerField()
thing = modelsForeignkey(Thing)
main = models.BooleanField()
I've got a function that is passed a list of filters where each filter is of the form {'type':something, 'value':x}. This function needs to return a set of results ANDing all the filters together:
final_q = Q()
for filter in filters:
q = None
if filter['type'] =='thing-property1':
q = Q(property1=filter['value'])
elif filter['type'] =='thing-property2':
q = Q(property2=filter['value'])
elif filter['type'] =='thing-property2':
q = Q(property3=filter['value'])
if q:
final_q = final_q & q
return Thing.objects.filter(final_q).distinct()
Each Subthing has a Boolean property 'main'. Every Thing has 1 and only 1 Subthing where main==True.
I now need to add filter that returns all the Things which have a Subthing where main==True and subproperty==filter['value']
Can I do this as part of the Q object I'm constructing? If not how else? The queryset I get before my new filter can be quite large so I would like a method that doesn't involve looping over the results.
It's a bit easier to understand if you explicitly give your Subthings a "related_name" in their relationship to the Thing
class Subthing(models.Model):
...
thing = models.ForeignKey(Thing, related_name='subthings')
...
Now, you use Django join syntax to build your Q object:
Q(subthings__main=True) & Q(subthings__subproperty=filter['value'])
The reverse relationship has the default name 'subthing_set', but I find that it's easier to follow if you give it a better name like 'subthings'.
Using (instead of final_q=Q() in the beginning)
final_q=Q(subthing_set__main=True)
sub_vals = map(lambda v: v['value'], filters)
if sub_vals:
final_q = final_q & Q(subthing_set__subproperty__in=sub_vals)
should get you what you want, you can also adjust your loop to build the sub_vals list and apply it after the loop.
subthing_set is and automatically added related field added to the Thing to access related Subthings.
you can assign another related name, e.g.
thing=models.ForeignKey(Thing,related_name='subthings')

How do I filter by time in a date time field?

In the model 'entered' is a datetime field. I want to query the data to find all entry's that where made between noon(start_time) and 5:00pm (end_time).
selected = Entry.objects.filter(entered__gte=start_time, entered__lte=end_time)
(as I expected)I get an error of:
"ValidationError: Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format."
So I know I can use __year so I tried.
selected = Entry.objects.filter(entered__time__gte=start_time, entered__time__lte=end_time)
I get an error of:
"FieldError: Join on field 'start' not permitted. Did you misspell 'time' for the lookup type?"
I don't believe there's built-in support for this, but you can pass extra where-clause parameters (warning: some possibility of introducing DB-dependent behaviour here).
For example, on Postgres, something like:
Entry.objects.extra(where=['EXTRACT(hour from entered) >= 12 and '\
'EXTRACT(hour from entered) < 17'])
If you're using potentially unsafe input to determine the values 12 and 17, note that you can also specify a params option to extra that will ensure proper quoting and escaping, and then use the standard sql %s placeholders in your where statement.
Using SQLite as an example, a relatively clean and generic solution would be:
Entry.objects.extra(where=["time(entered) between '%s' and '%s'"],
params=[start_time.strftime("%H:%M"), end_time.strftime("%H:%M")])
You could filter in python instead using the db's mechanisms:
for e in Entry.objects.all():
if i.entered.hour>= 9 and i.entered.hour < 17 :# or break down to minutes/seconds
list.append(e)
but both solutions are ugly, i think.
Steve, you have to decide, what is less ugly for you:
processsing a lot of data in a for-loop,
or use .extra(..) and by-passing the orm-system
Did you provide datetime objects for start_time and end_time?
A quick try-out:
class Entry(models.Model):
entered = models.DateTimeField()
>>> from datetime import datetime
>>> Entry(entered = datetime.now()).save()
>>> Entry.objects.filter(entered__lte = datetime.now())
[<Entry: Entry object>]
>>> Entry.objects.filter(entered__gte = datetime.now())
[]
>>> Entry.objects.filter(entered__gte = datetime.now(), entered__lte=datetime(2009,11,1,0,0))
[<Entry: Entry object>]
I suggest filter x<=arg and exclude x>=arg2.
I just figured out a solution for a similar use case – perhaps someone finds this useful:
In my case, I have Availability and Constraint models like so (simplified):
class Availability(...):
start = models.DateTimeField()
end = models.DateTimeField()
class Constraint(...):
from = models.TimeField()
to = models.TimeField()
I wanted to find availabilities that don't violate any constraints.
With Postgres, this worked for me:
Availability.objects.extra(
where=['NOT (start::time, "end"::time) OVERLAPS (\'{0}\'::time, \'{1}\'::time)'.format(
from.strftime("%H:%M"), to.strftime("%H:%M")
)]
)
Note how you can cast DateTime to Time (or rather timestamp to time without timezone in Postgres terms) with ::time and then use OVERLAPS to compare the time ranges.
And in regards to the original question, you can also do:
Availability.objects.extra(
where=['start::time BETWEEN \'{0}\'::time AND \'{1}\'::time AND
"end"::time BETWEEN \'{0}\'::time AND \'{1}\'::time'.format(
from.strftime("%H:%M"), to.strftime("%H:%M")
)]
)
You can use range to filter in between. I found this the best way to filter since SQL does filtering better than Django.
fromRange=datetime.datetime.strptime(request.GET.get('from'),'%Y-%m-%dT%H:%M:%S.%fZ')
toRange=datetime.datetime.strptime(request.GET.get('to'),'%Y-%m-%dT%H:%M:%S.%fZ')
entry = Entry.objects.filter(entryTime__range=(fromRange,toRange))

Categories