Django: how to change the value of a field one changing another? - django

I have 2 fields in a model I need that when I change the value of a field to calculate the other
Example:
Date_mission1_equipe=models.DateField (null=True,blank=True,max_length=200)
Date_mission2_equipe=models.DateField (null=True,blank=True,max_length=200)
for example if i choose 01/01/2019 for Date_mission1_equipe automatically Date_mission2_equipe should be 02/01/2019

There are few ways to handle this. You could override the model save method. Or perhaps the cleanest is to use a pre_save signal, as shown below.
from django.db.models.signals import pre_save
def change_date(sender, instance, **kwargs):
my_object = MyModel.objects.get(id=instance.id)
if instance.Date_mission1_equipe:
a.Date_mission2_equipe = ...
pre_save.connect(change_date, sender=MyModel)

It is often recommended not to store calculated values like this in the database. Instead, just perform the calcaultion when you will need it.
from datetime import timedelta
class Mission(models.Model):
date_mission1_equipe = models.DateField(null=True, blank=True)
#property
def date_mission2_equipe(self):
return self.date_mission1_equipe + timedelta(days=1)
You can now do something like this:
first_mission = Mission.objects.get(id=1)
first_mission.date_mission1_equipe
<01/01/2019>
first_mission.date_mission2_equipe
<02/01/2019>
That's just an example. The date would probably need to be formatted for your needs, and will not automatically output in the format I put above.
Also, you do not need max_length on a DateField. It makes no sense for this field type.

Related

How to run a function in Django after a certain time passed by

Having the model of Station with the below fields:
class Station(models.Model):
is_available = models.BooleanField(default=True)
unavailable_until = models.DateTimeField(null=True,blank=True)
I can define until when my station is unavailable by giving a DateTime value , so when that action happens the is_available value turns to False.
I want to turn the is_available value to True every time the unavailable_until value passed by(comparing with the current time based on the timezone).
How can I achieve an automation like this?
Imagine that I have a lot of Station records which belong to station owners which can update the availability (assign new unavailable_until value if passed by) whenever they want.
I think the logic could be something like:
def turn_availability_to_true(station):
if (station.unavailable_until < current_time):
station.is_available = True
But how can I implement a function like this to be called by its own when the unavailable_until value passed by?
Instead of using a field, you can .annotate(…) [Django-doc] to calculate dynamically if the Station is available:
The model thus then looks like
class Station(models.Model):
# no is_available
unavailable_until = models.DateTimeField(null=True,blank=True)
and we annotate this with:
from django.db.models import BooleanField, ExpressionWrapper, Q
from django.db.models.functions import Now
Station.objects.annotate(
is_available=ExpressionWrapper(
~Q(unavailable_until__gte=Now()),
output_field=BooleanField()
)
)
If you need this often, you can implement a manager [Django-doc] that will automatically add this to the Stations:
from django.db.models import BooleanField, ExpressionWrapper, Q
from django.db.models.functions import Now
class StationManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).annotate(
is_available=ExpressionWrapper(
~Q(unavailable_until__gte=Now()),
output_field=BooleanField()
)
)
class Station(models.Model):
# no is_available
unavailable_until = models.DateTimeField(null=True,blank=True)
objects = StationManager()
The advantage of not running a scheduled task is that it is hard to alter that task if later the unavailable_until field is modified, for example to a later date. In that case, one has to remove the scheduled task, and schedule a new one. By annotating, we avoid synchronization issues.

Django DateTimeRangeField: default=[timezone.now()]-[timezone.now()]+[10YEARS]

I want an "active_in" attribute as a timeframe. I assume that the DBMS is optimized for the postgresql tsrange field, and as such it is preferable to utilize the DateTimeRangeField rather than 2 separate fields for start_date and end_date.
Doing this I desire a default value for the field.
active_in = models.DateTimeRangeField(default=timezone.now+'-'+timezone.now+10YEARS)
Is my assumption about the DateTimeRangeField performance true?
Is there a smart solution be it creating a new; function,class or
simply manipulating the 2nd last digit?
My possible solutions:
Code using string manipulation:
active_in = models.DateTimeRangeField(default=timezone.now+'-'+timezone.now[:-2]+'30')
Code using custom function object: (adjusted from here: https://stackoverflow.com/a/27491426/7458018)
def today_years_ahead():
return timezone.now + '-' timezone.now() + timezone.timedelta(years=10)
class MyModel(models.Model):
...
active_in = models.DateTimeRangeField(default=today_years_ahead)
There's no need for string manipulation, as the documented Python type for this field is DateTimeTZRange.
I can't say I've ever used this field before, but something like this should work:
from psycopg2.extras import DateTimeTZRange
from django.utils import timezone
from datetime import timedelta
def next_ten_years():
now = timezone.now()
# use a more accurate version of "10 years" if you need it
return DateTimeTZRange(now, now + timedelta(days=3652))
class MyModel(models.Model):
...
active_in = models.DateTimeRangeField(default=next_ten_years)

django DateField model -- unable to find the date differences

I'm unable to find the difference between two dates in my form.
models.py:
class Testing(models.Model):
Planned_Start_Date = models.DateField()
Planned_End_Date = models.DateField()
Planned_Duration = models.IntegerField(default=Planned_Start_Date - Planned_End_Date)
difference between the date has to calculated and it should stored in the database but It doesn't works
default is a callable function that is just used on the class level, so you can't use it to do what you want. You should override the model's save() method (or better, implement a pre_save signal handler to populate the field just before the object is saved:
def save(self, **kwargs):
self.Planned_Duration = self.Planned_End_Date - self.Planned_Start_Date
super().save(**kwargs)
But why do you save a computed property to the database? This column is unnecessary. Both for querying (you can easily use computed queries on the start and end date) as for retrieving, you're wasting db space.
# if you need the duration just define a property
#property
def planned_duration(self):
return self.Planned_End_Date - self.Planned_Start_Date
# if you need to query tasks which last more than 2 days
Testing.objects.filter(Planned_End_Date__gt=F('Planned_Start_Date') + datetime.timedelta(days=2))
Note: Python conventions would recommend you name your fields using snake_case (planned_duration, planned_end_date, planned_start_date). Use CamelCase for classes (TestingTask). Don't mix the two.

How to set a Django model field's default value to a function call / callable (e.g., a date relative to the time of model object creation)

EDITED:
How can I set a Django field's default to a function that gets evaluated each time a new model object gets created?
I want to do something like the following, except that in this code, the code gets evaluated once and sets the default to the same date for each model object created, rather than evaluating the code each time a model object gets created:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
ORIGINAL:
I want to create a default value for a function parameter such that it is dynamic and gets called and set each time the function is called. How can I do that? e.g.,
from datetime import datetime
def mydate(date=datetime.now()):
print date
mydate()
mydate() # prints the same thing as the previous call; but I want it to be a newer value
Specifically, I want to do it in Django, e.g.,
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
The question is misguided. When creating a model field in Django, you are not defining a function, so function default values are irrelevant:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
This last line is not defining a function; it is invoking a function to create a field in the class.
In this case datetime.now() + timedelta(days=1) will be evaluated once, and stored as the default value.
PRE Django 1.7
Django [lets you pass a callable as the default][1], and it will invoke it each time, just as you want:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=lambda: datetime.now() + timedelta(days=1))
Django 1.7+
Please note that since Django 1.7, usage of lambda as default value is not recommended (c.f. #stvnw comment). The proper way to do this is to declare a function before the field and use it as a callable in default_value named arg:
from datetime import datetime, timedelta
# default to 1 day from now
def get_default_my_date():
return datetime.now() + timedelta(days=1)
class MyModel(models.Model):
my_date = models.DateTimeField(default=get_default_my_date)
More information in the #simanas answer below
[1]: https://docs.djangoproject.com/en/dev/ref/models/fields/#default
Doing this default=datetime.now()+timedelta(days=1) is absolutely wrong!
It gets evaluated when you start your instance of django. If you are under apache it will probably work, because on some configurations apache revokes your django application on every request, but still you can find you self some day looking through out your code and trying to figure out why this get calculated not as you expect.
The right way of doing this is to pass a callable object to default argument. It can be a datetime.today function or your custom function. Then it gets evaluated every time you request a new default value.
def get_deadline():
return datetime.today() + timedelta(days=20)
class Bill(models.Model):
name = models.CharField(max_length=50)
customer = models.ForeignKey(User, related_name='bills')
date = models.DateField(default=datetime.today)
deadline = models.DateField(default=get_deadline)
There's an important distinction between the following two DateTimeField constructors:
my_date = models.DateTimeField(auto_now=True)
my_date = models.DateTimeField(auto_now_add=True)
If you use auto_now_add=True in the constructor, the datetime referenced by my_date is "immutable" (only set once when the row is inserted to the table).
With auto_now=True, however, the datetime value will be updated every time the object is saved.
This was definitely a gotcha for me at one point. For reference, the docs are here:
https://docs.djangoproject.com/en/dev/ref/models/fields/#datetimefield
Sometimes you may need to access model data after creating a new user model.
Here is how I generate a token for each new user profile using the first 4 characters of their username:
from django.dispatch import receiver
class Profile(models.Model):
auth_token = models.CharField(max_length=13, default=None, null=True, blank=True)
#receiver(post_save, sender=User) # this is called after a User model is saved.
def create_user_profile(sender, instance, created, **kwargs):
if created: # only run the following if the profile is new
new_profile = Profile.objects.create(user=instance)
new_profile.create_auth_token()
new_profile.save()
def create_auth_token(self):
import random, string
auth = self.user.username[:4] # get first 4 characters in user name
self.auth_token = auth + ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(random.randint(3, 5)))
You can't do that directly; the default value is evaluated when the function definition is evaluated. But there are two ways around it.
First, you can create (and then call) a new function each time.
Or, more simply, just use a special value to mark the default. For example:
from datetime import datetime
def mydate(date=None):
if date is None:
date = datetime.now()
print date
If None is a perfectly reasonable parameter value, and there's no other reasonable value you could use in its place, you can just create a new value that's definitely outside the domain of your function:
from datetime import datetime
class _MyDateDummyDefault(object):
pass
def mydate(date=_MyDateDummyDefault):
if date is _MyDateDummyDefault:
date = datetime.now()
print date
del _MyDateDummyDefault
In some rare cases, you're writing meta-code that really does need to be able to take absolutely anything, even, say, mydate.func_defaults[0]. In that case, you have to do something like this:
def mydate(*args, **kw):
if 'date' in kw:
date = kw['date']
elif len(args):
date = args[0]
else:
date = datetime.now()
print date
Pass the function in as a parameter instead of passing in the result of the function call.
That is, instead of this:
def myfunc(date=datetime.now()):
print date
Try this:
def myfunc(date=datetime.now):
print date()

How to I change the rendering of a specific field type in Django admin?

For example I have an IntegerField and I want to change how it is displayed all across Django admin.
I considered subclassing it and overriding __str__ and __unicode__ methods but it doesn't seam to work.
class Duration(models.IntegerField):
def __unicode__(self):
return "x" + str(datetime.timedelta(0, self))
def __str__(self):
return "y" + str(datetime.timedelta(0, self))
Update: I just want to chage the way the field is displayed, not the edit control (widget).
I'm not sure what you want to do with the field, but if you want to change the HTML that is displayed, you need to either change the widget that the form field is using, or create your own custom widget:
https://docs.djangoproject.com/en/dev/ref/forms/widgets/
models.py
class LovelyModel(models.Model):
my_int = models.IntegerField()
forms.py
from widgets import WhateverWidgetIWant
class LovelyModelForm(forms.ModelForm):
my_int = models.IntegerField(widget=WhateverWidgetIWant())
class Meta:
model = LovelyModel
admin.py
from forms import LovelyModelForm
class LovelyModelAdmin(admin.ModelAdmin):
form = LovelyModelForm
What is it you are trying to do?
I think you need something like this (untested code)::
import datetime
from django.db import models
class Duration(models.IntegerField):
description = "Stores the number of seconds as integer, displays as time"
def to_python(self, value):
# this method can receive the value right out of the db, or an instance
if isinstance(value, models.IntegerField):
# if an instance, return the instance
return value
else:
# otherwise, return our fancy time representation
# assuming we have a number of seconds in the db
return "x" + str(datetime.timedelta(0, value))
def get_db_prep_value(self, value):
# this method catches value right before sending to db
# split the string, skipping first character
hours, minutes, seconds = map(int, value[1:].split(':'))
delta = datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
return delta.seconds
This, however, changes how the field's value represented in Python at all, not only in admin, which may not be a desired behaviour. I.e., you have object.duration == 'x00:1:12', which would be saved to the database as 72.
See also documentation on custom fields.