Truly dynamic DateField values in Django - python

Some of my app models define date ranges (e.g. of contracts), where the current instance has no fixed end date (i.e. it should always evaluate to today). Setting the default parameter on the end field –
class Contract(models.Model):
building = models.ForeignKey(Building, on_delete=models.CASCADE)
service = models.ForeignKey(Service, on_delete=models.CASCADE)
begin = models.DateField()
end = models.DateField(default=datetime.date.today)
– will populate the field with a fixed value. A property to work around the problem –
class Contract(models.Model):
building = models.ForeignKey(Building, on_delete=models.CASCADE)
service = models.ForeignKey(Service, on_delete=models.CASCADE)
begin = models.DateField()
end = models.DateField(blank=True)
#property
def get_end(self):
if not self.end:
return datetime.date.today()
return self.end
– does not work with querysets. Is there any way to implement a truly dynamic value on the database level using Django's ORM?

The best options available for a sudo-dynamic DateField are auto_now and auto_now_add as mentioned in the comments. The former updates the DateField on each .save() call and the latter does it at creation (Django doc).
The other option, as mentioned by Adrian, is to set the end as Null and use a conditional query for your claen() function. You can do that either by introducing an if statement or by using Case() and When(). You can check this Django doc or see this answer for more information.
Update: In your case something like this should work:
today = datetime.today()
conflicts = Contract.objects.filter(building=building, begin__lte=Case(
... When(end=Null, then=today), default=end), end__gte=begin)
There are also other options for having a dynamic date in your model but not as a DateField, which I assume is not what you are looking for nor anything near a 'clean code'.

Related

Automate update of models based on conditions

I have a model
class Session(models.Model):
name = models.CharField(max_length=12)
active = models.BooleanField(default=False)
date = models.DateField()
startTime = models.TimeField()
The active field is set based on the date and start time.
For eg -
Suppose during the creation of an object, the date is for tomorrow, and let there be any time, I want to know the process and not the code on how and what to study to make this object active on that particular date and time.
BY default the active field is False, or should I change the way I'm thinking to implement it?
Thanks
I would advise to use a DateTimeField for the start timestamp, and make active a property, so:
from django.utils import timezone
class Session(models.Model):
name = models.CharField(max_length=12)
start = models.DateTimeField()
#property
def active(self):
return timezone.now() >= self.start
This will thus not store the active field in the database, but simply determine the value when needed.

Question about sorting by date inside a database using Django

I have a database, in which it's possible to find products by their name. The DB has ID, category, name, amount and date defined, and I was trying to create a separate search field that would let me search those items by the date they were added.
The models.py looks like this:
class Expense(models.Model):
class Meta:
ordering = ('-date', '-pk')
category = models.ForeignKey(Category, models.PROTECT, null=True, blank=True)
name = models.CharField(max_length=50)
amount = models.DecimalField(max_digits=8, decimal_places=2)
date = models.DateField(default=datetime.date.today, db_index=True)
def __str__(self):
return f'{self.date} {self.name} {self.amount}'
And views.py looks like this:
class ExpenseListView(ListView):
model = Expense
paginate_by = 5
def get_context_data(self, *, object_list=None, **kwargs):
queryset = object_list if object_list is not None else self.object_list
form = ExpenseSearchForm(self.request.GET)
if form.is_valid():
name = form.cleaned_data.get('name', '').strip()
if name:
queryset = queryset.filter(name__icontains=name)
return super().get_context_data(
form=form,
object_list=queryset,
summary_per_category=summary_per_category(queryset),
**kwargs)
I've added the "date" field under the "name", following it's structure, but I kept getting the 'datetime.date' object has no attribute 'strip' error. Is there a different format for defining the date? When I've also added it to the search field in forms.py, it was seen there as a string.
I've also found a similar post from How to make date range filter in Django?, but it didn't explained that much, even after searching in the official library. I'm very new to Django, and I'm not sure if there should be a separate queryset call out for searching by date too.
In form.is_valid() I've tried to add the date field, but I kept getting the 'datetime.date' object has no attribute 'strip' error.
I think the behaviour you're looking for is described I. The django documentation here:
https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.DateField.auto_now
DateField
field_name = DateField(auto_now=False, auto_now_add=False, **options)
A date, represented in Python by a datetime.date instance. Has a few extra, optional arguments:
DateField.auto_now
Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps. Note that the current date is always used; it’s not just a default value that you can override.
The field is only automatically updated when calling Model.save(). The field isn’t updated when making updates to other fields in other ways such as QuerySet.update(), though you can specify a custom value for the field in an update like that.
DateField.auto_now_add
Automatically set the field to now when the object is first created. Useful for creation of timestamps. Note that the current date is always used; it’s not just a default value that you can override. So even if you set a value for this field when creating the object, it will be ignored. If you want to be able to modify this field, set the following instead of auto_now_add=True:
For DateField:
default=date.today - from datetime.date.today()
For DateTimeField:
default=timezone.now - from django.utils.timezone.now()
The default form widget for this field is a DateInput. The admin adds a JavaScript calendar, and a shortcut for “Today”. Includes an additional invalid_date error message key.

How to apply a CheckConstraint on creation only

I have a model with a models.DateTimeField field and a CheckConstraint that prevents it from being in the past:
from django.db.models.functions import Now
class MyModel(models.Model)
title = models.CharField()
mydate = models.DateTimeField()
class Meta:
models.CheckConstraint(
check=Q(mydate__gte=Now()),
name='mydate_no_past'
),
I want the constraint to apply only when the record is first created, ideally using Django and not raw SQL (per this answer).
In the above example, if a valid entry is created but then later the system datetime moves past the mydate value, when the record is updated the CheckConstraint fails.
How do I avoid this?
What also confuses me is if I update only other fields on the record and not mydate the CheckConstraint still fails. E.g the following will also fail if the system date is after the mydate value:
mymodel = MyModel.objects.get(pk=1)
mymodel.title = 'Title'
mymodel.save(update_fields=['title'])
Why is this?

FieldError: Unknown field(s) (created_at, modified_at) specified for Account

I am trying to create a timestamp for my model Account, but I don't want my two time stamps (created_at and modified_at) to be editable or even viewable by the user. Everything works fine and as expected until I add editable=False to the created_at and modified_at fields. Here is my model:
class Account(models.Model):
account_name = models.CharField(max_length=25)
active = models.BooleanField(default=True)
created_at = models.DateTimeField(null=True, blank=True, editable=False)
modified_at = models.DateTimeField(null=True, blank=True, editable=False)
def save(self):
if self.id:
self.modified_at = datetime.datetime.now()
else:
self.created_at = datetime.datetime.now()
super(Account, self).save()
class Meta:
ordering = ('id',)
Here is the obscure error I get when I try to do anything (migrate, runserver, etc):
django.core.exception.FieldError: Unknown field(s) (created_at, modified_at) specified for Account
As soon as I remove editable=False from both fields, everything works fine. Is this a Django bug? Is there a better way to make the field non-viewable and non-editable by the user?
I am using Django 1.9 and Python 3.6.1. Thanks for the help, let me know if you need me to post anything else (views, serializers, etc).
EDIT
Full traceback: https://pastebin.com/YEQACX5z
Accounts Form:
class AccountForm(ModelForm):
class Meta:
model = Account
fields = ['account_name', 'active', 'created_at', 'modified_at']
You could just do,
created_at = models.DateTimeField(auto_now_add=True)
and
modified_at = models.DateTimeField(auto_now=True)
From the docs,
DateField.auto_now¶
Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps. Note that the current date is always used; it’s not just a default value that you can override.
The field is only automatically updated when calling Model.save(). The field isn’t updated when making updates to other fields in other ways such as QuerySet.update(), though you can specify a custom value for the field in an update like that.
DateField.auto_now_add¶
Automatically set the field to now when the object is first created. Useful for creation of timestamps. Note that the current date is always used; it’s not just a default value that you can override. So even if you set a value for this field when creating the object, it will be ignored.
So, no need to add editable=False, its already non-editable.
Also, remember to remove your save() method override since it's trying to modify those fields.
If you want to be able to modify this field, set the following instead of auto_now_add=True:
For DateField: default=date.today - from datetime.date.today()
For DateTimeField: default=timezone.now - from django.utils.timezone.now()
The default form widget for this field is a TextInput. The admin adds a JavaScript calendar, and a shortcut for “Today”. Includes an additional invalid_date error message key.

django queryset filter datetimefield

I am strungling to use the django queryset API reference with filters based on the DateTimeField.
I have prepared the following model, in my models.py:
class KleedkamerIndeling(models.Model):
gametimedate = models.DateTimeField(auto_now=False, auto_now_add=False) # date and time of game
hometeam = models.CharField(max_length=25, blank=True) # name of home playing team team
homedressroom = models.CharField(max_length=25, blank=True) # dressing room of home playing team
awayteam = models.CharField(max_length=25, blank=True) # name of visiting team team
awaydressroom = models.CharField(max_length=25, blank=True) # dressing room of visiting team team
pitch = models.CharField(max_length=25, blank=True) # name / number of playing pitch
referee = models.CharField(max_length=25, blank=True) # name of referee
refdressroom = models.CharField(max_length=25, blank=True) # name/number of referee dressroom
timestamp = models.DateField(auto_now_add=True, auto_now=False)
indelings_datum = models.DateField() # need to change to datum added later
def __unicode__(self):
return smart_unicode(self.hometeam)
I want to access the database based on the present date. Should be something like:
queryset = KleedkamerIndeling.objects.all().filter(gametimedate=date.today())
This worked before when I used a 'datefield' but I decided to go to a datetimefield because I also need the starting time of the game played.
I have seached the web and stackoverflow and found the following older topic, How can I filter a date of a DateTimeField in Django? but I stungle on the implemenation.
I want to create a queryset that obtains all the 'KleedkamerIndeling' that are available for today's date. Subsequently I want to check if there are more then 10 objects in the list. If that's the case than I want to further narrow it down based on the current time, also via a filter on the datetimefield. Lastly I want to sort the object list such that it is sorted on time.
I know that my problem has to do which the caracteristics of the datetimefield van I appreciate a couple lines of code to help me move foreward. I have been trying to find the right query in the python manage.py shell all afternoon....
My model most work because the queryset = KleedkamerIndeling.objects.all() seems to work and I am able to set up seperate list for each object called 'game0 through game10 from the queryset list.
I have a further issue on the datetimefield format:
I have defined a 'KleedkamerIndeling' that describes a game that starts at 13:00. I have used the objects in the queryset list to define 10 different 'game'objects. i.e. game0 = [queryset[0].hometeam etc... though game10 if applicable. The gametime field is displayed in the template via a filter like: {{game0.0|date:"H"}}:{{game0.0|date:"i"}}. I still end up with the ':' in the case of no game0 object. I plan to solve this via a if else script in the template or in the view, or is there a better way to fix this?
Since you are using a datetime field you want to make your query’s using datetime objects. so first order the queryset from earliest to latest then filter out the events tat arn't occurring today.
from datetime import datetime, timedelta, time
today = datetime.now().date()
tomorrow = today + timedelta(1)
today_start = datetime.combine(today, time())
today_end = datetime.combine(tomorrow, time())
queryset = KleedkamerIndeling.objects.order_by('-gametimedate').filter(gametimedate__gte=today_start).filter(gametimedate__lt=today_end)
After that we can check if there are more then 10 objects, filter by the current time, and limit the queryset to 10 objects.
if queryset.count() > 10:
queryset = KleedkamerIndeling.objects.filter(gametimedate__gte=datetime.now())[:10]

Categories