We are working on a multi tenant django app that needs to display datetimes in tenant's timezone.
We achieve multitenancy using django-tenants, it separates tenants via Posgresql different schemas.
We are using graphql through graphene to expose our APIs, and we would like to make this implementation transparent to it, thus avoiding to manipulate every datetime we receive in input with timezone.localtime(received_datetime).
We've written a middleware that should do the job but graphene queries keep displaying the naive datetimes, to our understanding this is because django, while timezone aware, manage datetimes timezone only at forms and template levels, as per docs: https://docs.djangoproject.com/en/4.1/topics/i18n/timezones/
When support for time zones is enabled, Django stores datetime information in UTC in the database, uses time-zone-aware datetime objects internally, and translates them to the end user’s time zone in templates and forms.
By looking at timezone.now and timezone.localtime(timezone.now) values we get the correct results, but every date we get from graphql is both aware and with django "from settings" timezone (UTC), instead of tenant's timezone:
timezone.now(). # prints 2022-09-02 13:38:46.658864+00:00
timezone.localtime(timezone.now()) # prints 2022-09-02 22:56:16.620355+09:00
Querying graphql api instead outputs:
"edges”: [
{
“node”: {
“id”: “UG9saWN5Tm9kZToxNQ==“,
“createdAt”: “2022-09-01T13:55:04.542763+00:00" # in UTC timezone, instead of tenant's timezone
}
}
]
middleware.py
class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
tenant = request.tenant
if not tenant.schema_name == "public":
if tenant.timezone:
timezone.activate(pytz.timezone(tenant.timezone))
else:
timezone.deactivate()
return self.get_response(request)
settings.py
...
TIME_ZONE = "UTC"
USE_TZ = True
...
tenant_model.py
class Client(TenantMixin):
TIERS = [
('1', '1'),
('2', '2'),
('3','3')
]
name = models.CharField(max_length=100)
paid_until = models.DateField()
on_trial = models.BooleanField()
tier = models.CharField(max_length=20, choices=TIERS, default = '1')
created_on = models.DateField(auto_now_add=True)
storage_name = models.CharField(max_length=254, default='local')
dashboard_base_url = models.CharField(max_length=254, default='https://foo.bar')
auto_create_schema = True
timezone = models.CharField(max_length=254, default="Asia/Tokyo")
Every datetime we receive in input is a naive datetime (YYYY-MM-DDTHH:mm:ss format), is there any way to automagically (through a middleware) make it aware using tenant's timezone?
Related
settings.py
TIME_ZONE = 'Asia/Kolkata'
models.py
order_booking_time = models.DateTimeField()
while creating:
"order_booking_time":"2021-10-09 06:00"
What it stores in database:
"2021-10-08T18:53:17.097257+05:30"
So i did this in serializer.py while viewing in data
def get_order_booking_time(self,obj):
date = obj.order_booking_time.strftime("%Y-%m-%d %H:%M:%S")
return str(date)
Output:
"2021-10-09 00:30:00"
which is not equal to what i stored in data i.e."2021-10-09 06:00"
What in the way we retrieve OR store data in this "%Y-%m-%d %H:%M:%S" format with my local time
Having the same issue here. Django stores the datetime in UTC format.
You are accessing the data as stored in the database>
As far as I know Django uses serializers to transform datetime fields. In this case you use the (db) model directly.
views
import datetime
from .models import AccountTransaction
date = datetime.datetime.today()
def account_transactions_week_view(request):
account_transactions = AccountTransaction.objects.filter(user_id = request.user.id).filter(datetime__range=[date - datetime.timedelta(days=7), date])
models
class AccountTransaction(models.Model):
user = models.ForeignKey(User, verbose_name=_('user'))
datetime = models.DateTimeField(_('created at'), auto_now_add=True)
I bring to the page a list of recent entries over the past week. For this I use a filter.
I wondered why this variant does not work correctly (new entries appear after a while):
filter(datetime__range=[date - datetime.timedelta(days=7), date])
But this variant works correctly:
filter(datetime__gt=date - datetime.timedelta(days=7))
I wonder what's wrong with the first one?
There are middleware
class TimezoneMiddleware(object):
def process_request(self, request):
tzname = request.session.get('django_timezone')
if not tzname:
request.session['django_timezone'] = 'Europe/Kiev'
tzname = 'Europe/Kiev'
timezone.activate(pytz.timezone(tzname))
settings
TIME_ZONE = 'UTC'
You've defined date outside the method. That means the definition is executed when the module is first imported, when the Django process starts up. It will keep the same value for all uses of that process, until the server decides to recycle it and create a new one. So your range query will use the original value as the end point for all queries during the lifetime of the process.
The solution is to simply move the definition to inside the view function.
(Your other query works because it is simply doing "everything greater than 7 days since the original date", which automatically includes things greater than the original date.)
When I save dates in my database Django shows message about succesfull adding with the right time but in fact in the databese time is different
models.py:
from datetime import datetime
from django.db import models
class Teg1(models.Model):
created_at = models.DateTimeField(default=datetime.now, null=True, blank=True, editable=False)
num = models.FloatField(default=0.0, null=True, blank=True)
def __str__(self):
return str(self.num) + " || " + str(self.created_at)
settings.py
TIME_ZONE = 'Asia/Novosibirsk'
USE_TZ = True
The first sentence of Django's time zone documentation explains what you're seeing:
When support for time zones is enabled, Django stores datetime information in UTC in the database, uses time-zone-aware datetime objects internally, and translates them to the end user’s time zone in templates and forms.
So the database value is in UTC. The str() value is also in UTC, since you've manually converted the UTC datetime to a string without changing the timezone. The value interpreted by the form and displayed by the template is in your local time, since templates convert DateTimeFields to the current timezone.
If you want the str() value to use the local timezone you can use Django's localtime() function:
from django.utils.timezone import localtime
class Teg1(models.Model):
...
def __str__(self):
return str(self.num) + " || " + str(localtime(self.created_at))
If i'm not mistaken, you must be in Russia which is 7 hours ahead of UTC. So, the server that you use must be using the UTC time which in my opinion is a good thing.
I personally prefer to save times in UTC time in the data base and then convert them to the local time in the front end.
from django.utils import timezone
from datetime import datetime
teg1 = Teg1(created_at=datetime.now(tz=timezone.utc)
teg1.save()
However, if you want to save the datetime in your local time, you can use:
from datetime import datetime
import pytz
novosibirsk = pytz.timezone("Asia/Novosibirsk")
now = datetime.now(novosibirsk)
teg1 = Teg1(created_at=now)
teg1.save()
Have in mind that in your admin interface, you might see the time and date based on the timezone you select in your settings.py. However, the data saved in the database is still in UTC time.
instead of using
from datetime import datetime
class Teg1(models.Model):
created_at = models.DateTimeField(default=datetime.now)
use (this will use timezone that you have set in settings.py)
from django.utils import timezone
class Teg1(models.Model):
created_at = models.DateTimeField(default=timezone.localtime())
I have a model
class Unit(models.Model):
date = models.DateTimeField()
name = models.CharField(max_length = 128)
Then I have a view with js jquery ui datepicker. Then I have an ajax post function with
data_send['date'] = $('#date').value;
data_send['name'] = $('#name').value;
$.post("/url_1/",data_send, function(data_recieve){
alert(data_recieve);
});
Then in a view I'm trying to save unit like
def url_1(request):
unit = Unit.objects.get(pk = 1)
unit.date = request.POST['date']
unit.name = request.POST['name']
unit.save()
return HttpResponse('ok')
Unit.name changes, but not unit.date. I use django 1.3. I have no csrf protection. I recieve 'ok' from server.
Why does the unit.date not save?
Thanks
Since django datetime field is a representation of a datetime.datetime python instance, I like to make sure that I have a valid instance before inserting into the database.
you can use the datetime module to achieve this, instead of saving unit.date as a string
from datetime import datetime
try:
valid_datetime = datetime.strptime(request.POST['date'], '%d-%m-%Y')
except ValueError:
# handle this
then you can save the valid_datetime as your unit.date
the second param of strptime is formatted using the below values, it should match your datepicker format
http://docs.python.org/library/datetime.html#strftime-strptime-behavior
i have i little problem, and that is how can serialize a django query with defer ?
I have this model :
class Evento(models.Model):
nome=models.CharField(max_length=100)
descricao=models.CharField(max_length=200,null=True)
data_inicio= models.DateTimeField()
data_fim= models.DateTimeField()
preco=models.DecimalField(max_digits=6,decimal_places=2)
consumiveis= models.CharField(max_length=5)
dress_code= models.CharField(max_length=6)
guest_list=models.CharField(max_length=15)
local = models.ForeignKey(Local)
user= models.ManyToManyField(User,null=True,blank=True)
def __unicode__(self):
return unicode('%s %s'%(self.nome,self.descricao))
my query is this :
eventos_totais = Evento.objects.defer("user").filter(data_inicio__gte=default_inicio,
data_fim__lte=default_fim)
it works fine i think (how can i check if the query has realy defer the field user ? ) but when i do:
json_serializer = serializers.get_serializer("json")()
eventos_totais = json_serializer.serialize(eventos_totais,
ensure_ascii=False,
use_natural_keys=True)
it always folow the natural keys for user and local, i need natural keys for this query because of the fields local. But i do not need the field user.
To serialize a subset of your models fields, you need to specify the fields argument to the serializers.serialize()
from django.core import serializers
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))
Ref: Django Docs