I'm actually using python 3.7 and Django 3.0.4.
I using in my app models.py like this for a simple system of messages.
from torii.models import User
class Room(models.Model):
users = models.ManyToManyField(User,
related_name='rooms',
blank=True)
name = models.CharField(max_length=100)
class Message(models.Model):
room = models.ForeignKey(Room,
on_delete=models.CASCADE,
related_name='messages')
user = models.ForeignKey(User,
on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True)
So when my user send a message I create a new Message object attached to my Room.
I want to query all Room for a given User and annotate the date of the most recent message and the last message in my query.
No problem to get the most recent date in my related messages using Max like this:
for room in Room.objects.filter(users=user).annotate(last_message_date=Max('messages__date')).order_by('-last_message_date'):
print(room.__dict__)
{'id': 7, 'name': 'room-7', 'last_message_date': datetime.datetime(2020, 3, 20, 14, 0, 2, 118190, tzinfo=<UTC>)}
{'id': 9, 'name': 'room-9', 'last_message_date': datetime.datetime(2020, 3, 8, 15, 19, 52, 343780, tzinfo=<UTC>)}
{'id': 8, 'name': 'room-8', 'last_message_date': datetime.datetime(2020, 3, 7, 17, 18, 32, 648093, tzinfo=<UTC>)}
But I don't find any way to simply annotate the content of the last message. I have tried Max('messages__content') but in fact, it's order message by alphabetic order and it's always the same who is returned... I tried several subqueries with F and Q but that it didn’t work very well.
How can I annotate the result of room.messages.last().content directly with my query?
Finally, I solve this problem using OuterRef and Subquery documented there.
from django.db.models import Max, OuterRef, Subquery
newest = Message.objects.filter(room_id=OuterRef('pk')).order_by('-date')
for room in Room.objects.filter(users=user)
.annotate(last_message_date=Max('messages__date'),
last_message=Subquery(newest.values('content')[:1]))
.order_by('-last_message_date'):
print(r.__dict__)
As I understand the behavior, we prepare beforehand Subquery by saying to use id of the element who call the subquery to annotate the filtred elements and order then by -date. So it returns the most recent element and we take the value of field content to annotate it.
This will create a complex but unique request using all the performance of the database.
Related
I need to make an operation only available in 30 seconds, and then it expires. I Have this model:
(operation)
status = CharField(max_length=10, default="INI")
created_at = DateTimeField(auto_now_add=True)
account = ForeignKey(Account, null=False, on_delete=CASCADE)
metadata = models.JSONField(default=dict)
external_ref = CharField(max_length=128)
expires_at = DateTimeField(default=timezone.now()+timezone.timedelta(seconds=30))
I wanna be able to create in the expires_at field, a timestamp with exactly 30 seconds from the created_at Date, it is like a timeout function, but when I run the test:
def test_timeout_is_30_seconds(self):
print(self.operation.created_at)
timer = self.operation.created_at + timezone.timedelta(seconds=30)
print(timer)
self.assertEqual(self.operation.expires_at, timer)
it fails with this message:
AssertionError: datetime.datetime(2021, 6, 22, 19, 0, 42, 537490, tzinfo=<UTC>) != datetime.datetime(2021, 6, 22, 19, 0, 45, 844588, tzinfo=<UTC>)
I dont know if I need to make an external function or method inside the class or directly in the View, but I would prefer this default behavior in the models so I dont need to worry about setting expiry dates
I would be very grateful if you could help me solve it! :D any tips and information is appreciated
This is a common error. Your expression timezone.now()+timezone.timedelta(seconds=30) is being evaluated once, when the class is defined, and that value is being used as the default for every instance.
What you actually want is for the expiration time to be freshly calculated each time a new instance is created. Which means that you want to set default to a function:
def half_minute_hence():
return timezone.now() + timezone.timedelta(seconds=30)
expires_at = DateTimeField(default=half_minute_hence)
Hi I'm trying to get the newest/latest number in a query set:
I use this codeline for that:
CartQuantity.objects.filter(customer=customer).values_list('cquantity', flat=True).get(pk=-1)
This is how the queryset looks like:
<bound method QuerySet.last of <QuerySet [4, 4, 4, 2, 4, 4, 5, 6, 5, 14, 10, 12]>> # need last number(12)
I tried the code above but I get an Error message:
store.models.CartQuantity.DoesNotExist: CartQuantity matching query does not exist.
This is my models:
class CartQuantity(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, blank=True, null=True)
cquantity = models.IntegerField(default=0, null=True, blank=True)
Does anyone know how to fix the error or another way of getting the newest number(in this case number 12 in the query set)?
CartQuantity.objects.filter(customer=customer).values_list('cquantity', flat=True)[-1]
or
CartQuantity.objects.filter(customer=customer).values_list('cquantity', flat=True).last()
or
CartQuantity.objects.filter(customer=customer).values_list('cquantity', flat=True).reverse()[0]
Is it possible to filter a models.DateTimeField but only get the month in the filter object?
The field is:
time_stamp = models.DateTimeField(
default=timezone.now)
When I filter it, this is what I get:
[datetime.datetime(2016, 9, 22, 15, 2, 48, 867473, tzinfo=),
datetime.datetime(2016, 9, 22, 15, 4, 22, 618675, tzinfo=),
datetime.datetime(2016, 9, 22, 15, 5, 20, 939593, tzinfo=)]
The filter returns 3 rows, but clearly there is too much information. I only require the months, and maybe the year.
How can I achieve this?
Any help or direction would be appreciated,
Thanks
If you are using django 1.10.x there is Extract db function
from django.db.models.functions import Extract
months = MyModel.objects.annotate(month_stamp=Extract('time_stamp', 'month')).values_list('month_stamp', flat=True)
For django 1.9.x
from django.db.models import Func
def Extract(field, date_field='DOW'):
template = "EXTRACT({} FROM %(expressions)s::timestamp)".format(date_field)
return Func(field, template=template)
months = MyModel.objects.annotate(month_stamp=Extract('time_stamp', 'month')).values_list('month_stamp', flat=True)
You can use propety:
Class your_model(models.Model):
time_stamp = models.DateTimeField(
default=timezone.now)
#property
def _get_year(self):
return self.time_stamp.strftime("%Y-%m")
year = property(_get_year) #EDIT
On my Django site, I am saving a user's reactions so when a user clicks a button, I store it as a created time and when the user clicks it second time, I stored the time as a finish time and so forth. Here it is my model;
class UserStatus(models.Model):
STATUS_TYPES = (
('online', 'online'),
('offline', 'offline')
)
user = models.ForeignKey(User)
status_type = models.CharField(max_length=30, choices=STATUS_TYPES, default='online')
created_time = models.DateTimeField(auto_now_add=True)
finish_time = models.DateTimeField(blank=True, null=True)
time_diff = models.DateTimeField(blank=True, null=True)
I added time_diff to show the time difference between created_time and finish time. When I try an example in the shell, I use;
user_status.created_time
datetime.datetime(2016, 3, 31, 12, 50, 21, tzinfo=<UTC>)
user_status.finish_time
datetime.datetime(2016, 3, 31, 12, 51, 37, 998593, tzinfo=<UTC>)
user_status.finish_time - user_status.created_time
datetime.timedelta(0, 76, 998593)
Everything seems to be fine until now, however when I wrote user_status.save() it gave an error;
line 93, in parse_datetime
match = datetime_re.match(value)
TypeError: expected string or buffer
I did not understand why it gave such an error.
Thank you in advance.
Now you try to use DateTimeField, but this field can only be used for storing date and time (but not time difference). You should use DurationField for storing timedelta.
Using django model with Foreignkey as follows:
class Types_of_Expenditures(models.Model):
Expenditure_owner = models.CharField(default="None",max_length=50)
Expenditure_Type = models.CharField(default="Miscellaneous",max_length=50)
Expenditure_Type_Description = models.CharField(max_length=200)
def __unicode__(self):
return self.Expenditure_Type
class Expenditure(models.Model):
exp_owner= models.CharField(default='None',max_length=50)
exp_date = models.DateTimeField("Expenditure_Date")
description = models.CharField(max_length=1000)
amount = models.FloatField(default=0)
currency = models.CharField(max_length=15,default="MYR",editable=True)
exp_pocket = models.ForeignKey(Pockets, null=True, blank=True)
exp_type = models.ForeignKey(Types_of_Expenditures)
class Meta:
unique_together = ('exp_date', 'description',)
def __unicode__(self):
return self.description
Now when i save data in Types of expenditure i am doing :
Types_of_Expenditures.objects.get_or_create(Expenditure_owner='a',Expenditure_Type_Description="Default one")
and in database data is as :
id Expenditure_owner Expenditure_Type Expenditure_type_description
1 a Miscellaneous default one
but when i add anything to Expenditure data is saved with data in exp_type as 1 but it should be Miscellaneous because in def unicode i am returning Expenditure_type .
Here s how i am using formset to save data for Expenditure model:
ExpFormSet = modelformset_factory(Expenditure,extra=10,max_num=10,
fields=('exp_date', 'description','exp_type','amount','currency','exp_pocket'),
can_delete=False,form=ExpenditureForm)
if request.method == 'POST':
formset = ExpFormSet(request.POST,queryset=Expenditure.objects.filter(exp_owner=request.user))
if formset.is_valid():
print "printing formset",formset
formset.save(commit=True)
cd = form.cleaned_data
expenditureType = cd.get('exp_type')
print "Expenditure Type entered is ",expenditureType
return index(request)
What i am doing in index view:
Expenditure_list = Expenditure.objects.order_by('-exp_date').filter(exp_owner=request.user)
print "Expenditure List is : ", Expenditure_list.values_list()
Now output of this expenditure view as value of Expenditure_list.values_list() is (please see last value which is 1 i.e. value of exp_type ) :
Expenditure List is : [(3, u'a', datetime.datetime(2014, 9, 12, 15, 32, 10), u'test', 1.0, u'MYR', u'pocket', 1)]
What do i expect :
Expenditure List is : [(3, u'a', datetime.datetime(2014, 9, 12, 15, 32, 10), u'test', 1.0, u'MYR', u'pocket', u'Miscellaneous')]
Can anyone help what could be the problem here ?
I feel This question might be wrong may be it will save only foreign key there i.e. id and in order to get expenditure type i need to retrieve it using Types_of_Expenditure.objects.get(pk="Key as in list")
but not sure if there s an alternate way ?
I think I understand your question. You seem to be expecting values_list to return the __unicode__ of a related model, but there's no reason to think it would: as the name implies, it returns the values only, and the value of a ForeignKey field is just the ID.
You can supply a list of fieldnames to values_list and traverse the relationship there, if you want:
Expenditure.objects.values_list('description', 'amount', 'exp_type__ Expenditure_Type')