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?
Related
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'.
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.
Consider the following model. I'd like to filter objects based on the latest timestamp, which might be either created_at or updated_at. For some reason, the filter function does not recognise the annotated field and I'm having hard time finding the appropriate code examples to do this simple thing.
The error message is "Cannot resolve keyword 'timestamp' into field.".
How would you retrieve the Example objects within 7 days, based on newer date.
from datetime import timedelta
from django.db import models
from django.db.models.functions import Greatest
from django.utils import timezone
class Example(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(null=True)
#property
def recently_updated(self):
return Event.objects
.annotate(timestamp=Greatest('created_at', 'updated_at'))
.filter(timestamp__gte=timezone.now() - timedelta(days=7))
.order_by('-timestamp')
Django 1.11
Just set updated_at = models.DateTimeField(auto_now=True, null=True), this way when record is inserted for the first time updated_at and created_at will be same, so, you can just run the query on updated_at:
Event.objects.filter(updated_at__gte=timezone.now() - timedelta(days=7)).order_by('-timestamp')
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.
I've been working on a Django app to replace a spreadsheet-based sports picking game I play with some friends. I defined my Game model a while ago, but recently added a datetime field to signify the starting time of the game/match:
class Game(models.Model):
home_team = models.CharField(max_length=100, default='---')
away_team = models.CharField(max_length=100, default='---')
spread = models.FloatField(default=0)
week = models.IntegerField(default=0)
home_team_score = models.IntegerField(default=0)
away_team_score = models.IntegerField(default=0)
start_time = models.DateTimeField(default='2015-09-01 13:00:00')
def has_started(self):
if datetime.now() >= self.start_time:
return True
return False
def __str__(self):
return '%s vs. %s, week: %i' % (self.home_team, self.away_team, self.week)
I just noticed that when I use the Django admin functionality to add a new Game, the creation page errors out. However, if I click on an existing Game, the page displays fine, and I can edit the starting time as expected.
The error thrown is:
AttributeError at /admin/app/game/add/
'str' object has no attribute 'date'
Some searching returned similar issues, however they were related to definining the datetime field with auto_now or auto_now_add as True, so the fix is to make sure it handles those fields as read-only.
This situation is different, as if I define a custom fieldset in admin.py, that doesn't include the time field, the creation page does not error out when I click to add a new one. For example:
class GameAdmin(admin.ModelAdmin):
fieldsets = [
('League week', {'fields': ['week'], }),
('Teams', {'fields': ['home_team', 'away_team'], }),
('Spread', {'fields': ['spread'], }),
]
list_filter = ['week']
search_fields = ['home_team','away_team']
This works just fine to add a new Game, and I can go back to the default admin layout to change the default time.
I thought I might need to import date from datetime, but adding that didn't help. Is there a different way I need to define my fieldset in order to handle dates properly?
Set default attribute of start_time to an object of datetime.datetime i.e. datetime.datetime.now() or in your case
datetime.datetime(year=2015, month=9, day=1, hours=13)
Although it is considered good practise to use current/dynamic time as default by setting
default = datetime.datetime.now
or
default = django.utils.timezone.now
for timezone aware datetime (see brackets () are absent after .now), but it depends on your use case.