Suppose I want to have a model like this:
from django.db import models
from django.contrib.postgres.fields import DateRangeField
class QueryRange(models.Model):
name = models.CharField('Name', max_length=50)
rsv = DateRangeField()
And in shell I create some objects like this:
obj1 = QueryRange.objects.create(name='John', rsv=('2016-06-01', '2016-06-10'))
obj2 = QueryRange.objects.create(name='Peter', rsv=('2016-07-05', '2016-07-10'))
obj3 = QueryRange.objects.create(name='Chris', rsv=('2016-07-12', '2016-07-15'))
How can I query the db by asking this question: Please check and see if the date i.e '2016-07-08' is occupied or not.
Something like this won't work:
from psycopg2.extras import DateRange
rng = DateRange('2016-07-08')
QueryRange.objects.filter(rsv__contains=rng)
I have implement the same scenario with two separate date fields (from_date and until_date) and works great (of course). I am just curious how can I benefit myself with the DateRangefield.
Cheers to all Djangonauts out there!
You're very close with your answer. When you're looking for the presence of a single date, use a date object directly:
from datetime import date
QueryRange.objects.filter(rsv__contains=date.today())
If you're using a date range to query, you're probably looking to see if there's any overlap:
rng = DateRange('2016-07-08', '2016-07-20')
QueryRange.objects.filter(rsv__overlap=rng)
Tested both examples to make sure they work.
Related
I have a puzzle on my hands. As an exercise, I am trying to write a queryset that helps me visualize which of my professional contacts I should prioritize corresponding with.
To this end I have a couple of models:
class Person(models.Model):
name = models.CharField(max_length=256)
email = models.EmailField(blank=True, null=True)
target_contact_interval = models.IntegerField(default=45)
class ContactInstance(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='contacts')
date = models.DateField()
notes = models.TextField(blank=True, null=True)
The column target_contact_interval on the Person model generally specifies the maximum amount of days that should pass before I reach out to this person again.
A ContactInstance reflects a single point of contact with a Person. A Person can have a reverse relationship with many ContactInstance objects.
So, the first Person in the queryset should own the greatest difference between the date of the most recent ContactInstance related to it and its own target_contact_interval
So my dream function would look something like:
Person.objects.order_by(contact__latest__date__day - timedelta(days=F(target_contact_interval))
but of course that won't work for a variety of reasons.
I'm sure someone could write up some raw PostgreSQL for this, but I am really curious to know if there is a way to accomplish it using only the Django ORM.
Here are the pieces I've found so far, but I'm having trouble putting them together.
I might be able to use a Subquery to annotate the date of the most recent datapoint:
from django.db.models import OuterRef, Subquery
latest = ContactInstance.objects.filter(person=OuterRef('pk')).order_by('-date')
Person.objects.annotate(latest_contact_date=Subquery(latest.values('date')[:1]))
And I like the idea of sorting the null values at the end:
from django.db.models import F
Person.objects.order_by(F('last_contacted').desc(nulls_last=True))
But I don't know where to go from here. I've been trying to put everything into order_by(), but I can't discern if it is possible to use F() with annotated values or with timedelta in my case.
UPDATE:
I have changed the target_contact_interval model to a DurationField as suggested. Here is the query I am attempting to use:
ci = ContactInstance.objects.filter(
person=OuterRef('pk')
).order_by('-date')
Person.objects.annotate(
latest_contact_date=Subquery(ci.values('date'[:1])
).order_by((
(datetime.today().date() - F('latest_contact_date')) -
F('target_contact_interval')
).desc(nulls_last=True))
It seems to me that this should work, however, the queryset is still not ordering correctly.
If I have a model of an Agent that looks like this:
class Agent(models.Model):
name = models.CharField(max_length=100)
and a related model that looks like this:
class Deal(models.Model):
agent = models.ForeignKey(Agent, on_delete=models.CASCADE)
price = models.IntegerField()
and a view that looked like this:
from django.views.generic import ListView
class AgentListView(ListView):
model = Agent
I know that I can adjust the sort order of the agents in the queryset and I even know how to sort the agents by the number of deals they have like so:
queryset = Agent.objects.all().annotate(uc_count=Count('deal')).order_by('-uc_count')
However, I cannot figure out how to sort the deals by the sum of the price of the deals for each agent.
Given you already know how to annotate and sort by those annotations, you're 90% of the way there. You just need to use the Sum aggregate and follow the relationship backwards.
The Django docs give this example:
Author.objects.annotate(total_pages=Sum('book__pages'))
You should be able to do something similar:
queryset = Agent.objects.all().annotate(deal_total=Sum('deal__price')).order_by('-deal_total')
My spidy sense is telling me you may need to add a distinct=True to the Sum aggregation, but I'm not sure without testing.
Building off of the answer that Greg Kaleka and the question you asked under his response, this is likely the solution you are looking for:
from django.db.models import Case, IntegerField, When
queryset = Agent.objects.all().annotate(
deal_total=Sum('deal__price'),
o=Case(
When(deal_total__isnull=True, then=0),
default=1,
output_field=IntegerField()
)
).order_by('-o', '-deal_total')
Explanation:
What's happening is that the deal_total field is adding up the price of the deals object but if the Agent has no deals to begin with, the sum of the prices is None. The When object is able to assign a value of 0 to the deal_totals that would have otherwise been given the value of None
I'm currently having a hard time wrapping my head around Django's Array Field. What i'm hoping to do is have an array that looks something like this:
Price(close=[
[1/1/2018, 3.00],
[1/2/2018, 1.00],
])
It's basically an array that stores a date followed by a corresponding value tied to that date. However, thus far my model looks like this:
class Price(models.Model):
close = ArrayField(
models.DecimalField(max_digits=10, decimal_places=4),
size=365,
)
I am not certain how to create an array with two different types of fields, one DateTime, the other decimal. Any help would be much appreciated.
You can't mix types stored in the ArrayField. [1] I recommend you to change model schema (aka Database normalization [2]).
This is my suggestion:
from django.db import models
class Price(models.Model):
pass
class PriceItem(models.Model):
datetime = models.DateTimeField()
ammount = models.DecimalField(max_digits=10, decimal_places=4)
price = models.ForeignKey(Price, on_delete=models.CASCADE)
[1] https://stackoverflow.com/a/8168017/752142
[2] https://en.wikipedia.org/wiki/Database_normalization
It depends on how important it is to the model.
postgresql provides composite types
The generous contributor of psycopg2 (django's posgresql driver) is supporting it,
define this type in postgresql:
CREATE TYPE date_price AS (
start date,
float8 price
);
and using the methods described here to implement CompositeField
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.db import connection
from psycopg2.extras import register_composite
# register the composite
register_composite('date_price', connection.cursor().cursor)
# CompositeField implementation here . . . . . .
class DatePriceField(CompositeField):
'''
DatePriceField specifics
'''
pass
class Price(models.Model):
close = ArrayField(base_field=DatePriceField(), size=365,)
I am going to follow this route and update soon.
Lets say i have two model
class Testmodel1():
amount = models.IntegerField(null=True)
contact = models.CharField()
entry_time = models.DateTimeField()
class Testmodel2():
name = models.CharField()
mobile_no = models.ForeignKey(Testmodel1)
and I am creating the object for this model(Testmodel2). Now I want to find out the count of object(Testmodel2) created in last 24 hours by mobile_no field.
what could be the best way of making query.
Any help would be appreciated.
It'd be better if you made the contact field into a models.DateTime field rather than a models.CharField. If it were a DateTime field, you could do lte, gte, and other operations on it easily to compare it to other datetimes.
For example, if Testmodel.contact were a DateTime field, the answer to your question would be:
Testmodel.objects.filter(contact__gte=past).count()
If the contact field contains a string representing a DateTime, I'd recommend switching it over, since there's really no reason to store it as a string.
If you're unable to change these fields, unfortunately I don't think there's a way to do this on the database level. You'll have to filter them individually on the python side:
from dateutil.parser import parse
results = []
past = arrow.utcnow().shift(hours=-24)
model_query = TestModel.objects.all()
for obj in model_query.iterator():
contact_date = parse(obj.contact) # Parse string into datetime
if contact_date > past:
results.append(obj)
print(len(results))
This will give you a list (note: NOT a queryset) containing all matching model instances. It'll be a lot slower than the other option would be, you can't edit the results afterwards with something like results.filter(amount__gte=1).count(), and it's not quite as clean.
That said, it'll get the job done.
EDIT
It occurs to me that this might be able to be done with annotation, but I'm not sure how that would be accomplished, or if it would even work. I defer to other answers if they can think of a way to use annotation to accomplish this in a better way, but stick to my original assessment that this should probably be a DateTime field.
EDIT 2
With a DateTime field now added on the other model, you can look it up across models like so:
past = arrow.utcnow().shift(hours=-24)
Testmodel2.objects.filter(mobile_no__entry_time__gte=past)
I got two models :
models.py
class Representation(models.Model):
datetime = models.DateTimeField()
show = models.ForeignKey(Show)
class Show(models.Model):
name = models.CharField(max_length=512)
and then I'd like to have the next 5 incoming shows, and the best i can do is a clever hack like that, but this just uses a ton of queries, and I'm sure there is a more pythonic way to do it
i=5
list_shows = set()
while (len(list_shows) < 5:
for rep in Representation.objects.filter(datetime__gt=datetime.now()).order_by('-datetime')[:i]:
list_shows.add(rep.show)
i+=1
if i>100:
break
I also tryed something like this :
from datetime import datetime
from django.db.models import Min
Show.objects.annotate(date=Min('representation__datetime')).filter(date__gte=datetime.now()).order_by('date')[:5]
But this doesn't take the case where a show already played yesterday but play against tonight.
Can you try this:
Show.objects.filter(representation_set__datetime__gt=datetime.now()).order_by('representation_set__datetime')[:5]