I'm trying to build a blurb of text that I'll ultimately send in an SMS message. The blurb includes text of a datetime object that has UTC as the timezone, but needs the text to be localized for the user's timezone instead. I have the user's timezone stored in the db.
I know that I can use timezone.activate() and timezone.deactivate() to change the current timezone, but I don't know if that's the best thing to do, when all I want is the text of the datetime to print out in the user's local timezone. I don't know if changing the current timezone will have unwanted system consequences, even if just for a short time.
Activating timezone using timezone.activate() will only affect current request, so there is really no "unwanted system consequences" to worry about.
By activating the timezone in middleware:
from django.utils import timezone
class TimezoneMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated():
timezone.activate(request.user.get_profile().timezone)
you'll be able to render user's local time by using {{ datatime }} parameter in your SMS template.
Related
I want to automatically save a user's timezone to their profile after it has been detected by the django-tz-detect package.
Use case: I have a simple app that allows users to sign up and reserve time slots.
Features:
As little overhead as possible for users. Simply sign up with a name and email address, then click a button to reserve a time slot.
Admin user can send email reminders to users when their time slots are approaching.
Problem: The email sent to users is not formatted by their local time zone, as django-tz-detect is only concerned with the current timezone of that session.
How can I format times in the user's local timezone in the reminder email?
This can be accomplished with simple custom middleware.
Make sure you're using django-tz-detect.
INSTALLED_APPS = [
# ...
'tz_detect',
'myapp.apps.MyappConfig',
]
Add your own middleware to the stack, after django-tz-detect.
MIDDLEWARE = [
# ...
'tz_detect.middleware.TimezoneMiddleware',
'myapp.middleware.UserTimezoneMiddleware',
]
myapp/middleware.py
from django.utils import timezone
class UserTimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if request.user.is_authenticated:
if request.session.get('detected_tz'): # (set by django-tz-detect)
tz = timezone.get_current_timezone()
if tz:
tz = str(tz)
# (this assumes your user model has a char field called "timezone")
if tz != request.user.timezone:
request.user.timezone = tz
request.user.save()
return response
Timezone strings (e.g. "America/Chicago") are now automatically saved to the user model, and updated if the user logs in from a different tz.
Before marked as dup (I believe many answers are incorrect becase):
According to my research:
request.session.get_expiry_age returns the TOTAL lifespan of the cookie, not the time from now until it expires. It always returns the same value, irrespecitive of how much time is left.
Similarly get_expiry_date adds the total lifespan to the now time. It always increases when called.
Hence niether of them help.
settings.SESSION_SAVE_EVERY_REQUEST refreshes the cookie on every request, this is NOT what I am trying to achieve.
get_session_cookie_age() ONLY returns the value of settings.SESSION_COOKIE_AGE, as seen in the sourcecode:
def get_session_cookie_age(self):
return settings.SESSION_COOKIE_AGE
I use the cached_db session back end. The cached part is made none in void because it has to save the cookie everytime, hence SESSION_SAVE_EVERY_REQUEST is not applicable. The reason I ask this question is because I wish to refresh a cookie ONLY if the time left has gone below a threshold, and hence have some hysterious (have the benefits of caching and auto refreshing).
Here is my code that does not work because of the behaviour of get_expiry_age/get_expiry_date as explained above:
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
class SessionExpiry(MiddlewareMixin):
def process_request(self, request):
"""Automatically refresh a session expirary at a certain limit.
If request.session age goes below settings.SESSION_REFRESH, then
set the sessions expirary age to settings.SESSION_COOKIE_AGE. The
session is not refreshed every time to have the caching benefits of
the cached_db backend.
"""
try:
empty = request.session.is_empty()
except AttributeError:
return None
if (empty or not getattr(settings, 'SESSION_REFRESH', None)):
return None
if request.session.get_expiry_age() < settings.SESSION_REFRESH:
request.session.set_expiry(settings.SESSION_COOKIE_AGE)
return None
This is an area of Django that's confusing, in both design and documentation. The easiest way to figure out what's going on is to look at the source code.
To get what you want you need to set a custom expiration as a datetime. If you use seconds (including the default SESSION_COOKIE_AGE) that is interpreted as the number of seconds from now; that is, from whenever you ask.
So to set the expiration, use:
# Using timedelta will cause the session to save the expiration time as a datetime.
request.session.set_expiry(timedelta(seconds=settings.SESSION_COOKIE_AGE))
If you do that, calls to get_expiry_age() will return the actual difference between the current datetime and the datetime set as the expiry.
Be sure to note the warning in the set_expiry() documentation: "Note that datetime and timedelta values are only serializable if you are using the PickleSerializer."
Came here from the Django ticket. Here is what I came up with avoiding the need for pickle serializer and SESSION_SAVE_EVERY_REQUEST. So instead of using the datetime we can use plain timestamp, and since set_expire does not support that, we will just add another session key to store that.
class RefreshSessionMiddleware(middleware.SessionMiddleware):
def process_response(self, request, response):
session = request.session
if not (session.is_empty() or session.get_expire_at_browser_close()):
expiry = session.get('_session_expire_at_ts')
now_ts = int(time.time())
cookie_lifetime = session.get_expiry_age()
if expiry is None or now_ts + cookie_lifetime / 2 > expiry:
# This will set modified flag and update the cookie expiration time
session['_session_expire_at_ts'] = now_ts + cookie_lifetime
return super().process_response(request, response)
I have logged into my site 1 days ago but today when I login into my site again, it is showing me today's date in user.last_login whereas its should display yesterday's date.
Here is the Django source code:-
https://github.com/django/django/blob/a3ba2662cdaa36183fdfb8a26dfa157e26fca76a/django/contrib/auth/models.py#L20
I have also reported to Django(https://code.djangoproject.com/ticket/28256)
Check this link out.
It says last time not the time before this time. I think you misunderstand what last time means. When you write a comment that is the last comment you wrote, not the one before that. So when you Log in again to check when you last logged in THIS is the time Django shows you.
Test the function with two different users. Log in with user1, Log user1 out again. Then go with user2 in the Admin and check the time of user1.
Hope that helps.
p.s. please take down the Ticket...
For those coming here to figure out how to save the previous login time to the current session, you can make your own signal handler for the user_logged_in signal.
Add previous_login to your User model:
from django.utils.translation import gettext_lazy as _
...
previous_login = models.DateTimeField(_("previous login"), blank=True, null=True)
And then make your own signal handler and disconnect the one offered by django
from django.contrib.auth import user_login_failed, user_logged_in
from django.contrib.auth.models import update_last_login
from django.utils import timezone
def update_last_and_previous_login(sender, user, **kwargs):
user.previous_login = user.last_login
user.last_login = timezone.now()
user.save(update_fields=["previous_login", "last_login"])
user_logged_in.disconnect(update_last_login, dispatch_uid="update_last_login")
user_logged_in.connect(update_last_and_previous_login, dispatch_uid="update_last_and_previous_login")
I'm working on the app where I need to limit the ability to log in and be authenticated for a specified time of the day. Let's say from 8am to 5pm. To limit the ability to log in I created a custom auth backend where authenticate() method returns user object only if current time is within allowed period of time.
Now I want to terminate user's auth session after specified time.
Setting session expiry_date date and cookie's Expiry seems to be the best way to achieve this, but after reading Django docs and digging in the source code for some time I did not found a good solution to it. How do I do this? Any help is appreciated.
Changing the auth backend is probably not the solution you are looking for (at least I wouldn't recommend it), since you are changing security-critical parts of your application.
I would suggest a custom middleware: If registered users trying to access your site between 8am and 5pm, they'll see a warning that this site cannot be used.
from django.utils import timezone
from django.core.exceptions import PermissionDenied
class AccessRestrictionMiddleware:
def process_request(self, request):
current_hour = timezone.now().hour
is_time_restricted = current_hour >= 8 and current_hour < 17
if request.user.is_authenticated() and is_time_restricted:
raise PermissionDenied
Beginner Django programmer here. I'm working on a Django app and am having timezone issues. My code successfully converts time objects into local time when I run on my local server but this conversion does not work when my application is hosted on Heroku.
I am currently converting timezone using the tzlocal extension. In my views I use the code:
activate(get_localzone())
On my local server, get_localzone() successfully returns the local time. On the Heroku-hosted version of my application, get_localzone() returns UTC.
Here are my time settings in settings.py:
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
Not sure if this matters but I'm using a Postgres database:
DATABASES = {'default': dj_database_url.config(default='postgres://localhost')}
Here is an example of my home view to give you a sense of how I'm using the tzlocal package:
def home(request):
if not request.user.is_authenticated():
return render(request, 'idealist/index.html')
else:
context = RequestContext(request)
activate(get_localzone())
user = UserProfile.objects.filter(user=request.user)[0]
user_projects = user.projects()
events = user.events()
event_dict = SortedDict()
string = get_localzone()
for event in reversed(events):
date = event.datew.astimezone(get_localzone())
date = date.strftime("%B") + " " + str(date.day)
if date in event_dict.keys():
event_dict[date].append(event)
else:
event_dict[date] = [event]
return render_to_response('idealist/account_home.html', {'string': string, 'user': user, 'event_dict': event_dict, 'user_projects': user_projects}, context)
Also, here are the other time-related packages that I'm importing:
from django.utils.timezone import *
import pytz
from tzlocal import *
import datetime
from pytz import timezone
Thank you so much for your help. If there is any other information I can provide you with, please let me know!
Before the answer a piece of advice, never store localized timezones. Always store dates in UTC and convert on the application as needed.
get_localzone() is probably picking up the server timezone, yours is in whatever you call local timezone while heroku server is probably set to UTC. You have a few options:
1) Set your server timezone to whatever you want
I'm not 100% sure this will work but maybe it's worth trying. Maybe setting the TZ variable to your desirable timezone will make that the local timezone. Try using:
heroku config:add TZ="America/Los_Angeles"
Replacing with your desired timezone from this list.
2) Force django to use the timezone that you want
This might be a better option since you won't have to remember to set the timezone again in case you change providers or change the heroku server.
Instead of using get_localzone() pass in the name of the timezone you want directly like this:
timezone.activate(pytz.timezone('America/Los_Angeles'))
I'm assuming you always want to use the same timezone throughout your application, but if you want to allow the user to select it's own timezone you can store that value in the user session or profile. The Django docs has some good resources on this.