Python 正在寻找在Django 3中执行基于时区的通知的更好方法
我想向今天没有输入日记账的所有用户发送一封电子邮件。我想让他们在当地时间晚上8点之前输入日志条目,并使其正常工作,但代码非常复杂,我想知道是否有更有效的方法来实现我的目标 我当前保存用户注册时的时区偏移量Python 正在寻找在Django 3中执行基于时区的通知的更好方法,python,django,timezone,Python,Django,Timezone,我想向今天没有输入日记账的所有用户发送一封电子邮件。我想让他们在当地时间晚上8点之前输入日志条目,并使其正常工作,但代码非常复杂,我想知道是否有更有效的方法来实现我的目标 我当前保存用户注册时的时区偏移量 <style> #clientTimezone { opacity: 0; width: 0; float: left; } </style> .... <form class="signup form-default row"
<style>
#clientTimezone {
opacity: 0;
width: 0;
float: left;
}
</style>
....
<form class="signup form-default row" id="signup_form" method="post" action="{% url 'account_signup' %}">
{% csrf_token %}
<input type="text" name="client_timezone" id="clientTimezone" required />
....
<script>
$(document).ready(function(){
var d = new Date();
$('#clientTimezone').val(moment().format('ZZ'))
})
</script>
#客户时区{
不透明度:0;
宽度:0;
浮动:左;
}
....
{%csrf_令牌%}
....
$(文档).ready(函数(){
var d=新日期();
$('#clientTimezone').val(矩().format('ZZ'))
})
然后,电子邮件代码在我的dailryreminder.py管理命令中,该命令每10分钟触发一次。如果可能的话,我想不用芹菜继续这样做
import arrow
from datetime import datetime, timedelta
from django.utils import timezone
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Q
from django.core.mail import send_mail, send_mass_mail, EmailMultiAlternatives
from django.conf import settings
from dashboard.models import Entry
from dashboard.emails import email_daily_reminder
from redwoodlabs.models import UserProfile
from identity.strings import GLOBAL_STRINGS
LOCAL_TIME_TO_EMAIL = 20 # 8:00 PM
MAX_LOCAL_TIME_TO_EMAIL = 24 # 12:00 AM
USER_BATCH_SIZE = 10
class Command(BaseCommand):
help = 'Email users daily reminder to reflect'
def handle(self, *args, **options):
# UserProfile model has last_notified (datetimefield) and timezone (char field)
# get current time in utc
time_now = arrow.utcnow()
# Get timezone where it's 8PM
start_tz = LOCAL_TIME_TO_EMAIL - time_now.hour
end_tz = MAX_LOCAL_TIME_TO_EMAIL - time_now.hour
# Get a list of timezones where it is between 8pm to 12am
timezones = list()
for tz in range(start_tz, end_tz):
if tz > 14:
tz = 24 - tz
timezones.append('-' + str(tz).zfill(2) + '00')
else:
timezones.append('+' + str(tz).zfill(2) + '00')
time_now_local = time_now.replace(minute=0, second=0)
time_yesterday = time_now.shift(days=-1)
# Get all users with the ff cases:
# - who have not been notified today
# - who have not been notified at all
# - who have not been notified in the last 24hrs
users_to_email = UserProfile.objects.filter(Q(last_notified__isnull=True) |
(Q(timezone__in=timezones) & Q(last_notified__lt=time_now_local.datetime)) |
Q(last_notified__lt=time_yesterday.datetime)
).exclude(timezone__isnull=True)[:USER_BATCH_SIZE]
for profile in users_to_email:
user_time_now = time_now.to(profile.timezone)
user_today = user_time_now.floor('day')
last_notified_day = arrow.get(profile.last_notified).to(profile.timezone)
# Send email if last_notified was before today
if not profile.last_notified or last_notified_day.datetime < user_today.datetime:
self.stdout.write('Notifying %s' % profile)
email_sent = email_daily_reminder(profile.user, user_time_now.date())
# save somewhere they've been notified today?
self.stdout.write(self.style.SUCCESS('Notified %s' % profile.user.email))
profile.last_notified = user_time_now
profile.save()
导入箭头
从datetime导入datetime,timedelta
从django.utils导入时区
从django.core.management.base导入BaseCommand,CommandError
从django.db.models导入Q
从django.core.mail导入send_mail、send_mass_mail、EmailMultiAlternations
从django.conf导入设置
从dashboard.models导入条目
从dashboard.emails导入电子邮件\u每日提醒
从redwoodlabs.models导入用户配置文件
从identity.strings导入全局字符串
当地时间到电子邮件=20#晚上8:00
最长本地时间至电子邮件=24#上午12:00
用户\批量\大小=10
类命令(BaseCommand):
帮助='电子邮件用户每日提醒以反映'
def句柄(自身、*参数、**选项):
#UserProfile模型已最后通知(datetimefield)和时区(char field)
#以utc为单位获取当前时间
time\u now=arrow.utcnow()
#到达晚上8点的时区
start_tz=本地时间发送电子邮件-TIME_now.hour
end_tz=最大本地时间到电子邮件-时间现在.hour
#获取从晚上8点到凌晨12点的时区列表
时区=列表()
对于范围内的tz(开始tz,结束tz):
如果tz>14:
tz=24-tz
时区.append('-'+str(tz).zfill(2)+'00')
其他:
时区.append('+'+str(tz).zfill(2)+'00')
time\u now\u local=time\u now.replace(分钟=0,秒=0)
昨天的时间=现在的时间。班次(天数=-1)
#获取具有ff案例的所有用户:
#-今天没有通知谁
#-根本没有通知谁
#-在过去24小时内未收到通知的人员
users\u to\u email=UserProfile.objects.filter(Q(上次通知的\u isnull=True)|
(Q(时区=时区)和Q(上次通知时间现在本地日期时间))|
Q(上次通知时间=昨天时间。日期时间)
).exclude(时区\uuu isnull=True)[:用户\u批量大小]
对于用户\u至\u电子邮件中的配置文件:
user\u time\u now=time\u now.to(profile.timezone)
user\U today=user\U time\U now.floor('天')
last_notified_day=arrow.get(profile.last_notified).to(profile.timezone)
#如果上次通知是在今天之前,请发送电子邮件
如果没有profile.last_notified或last_notified_day.datetime
这是有效的(或者至少到目前为止是有效的),但我想问一下是否有更好的方法(尤其是时区逻辑),因为我很可能会在另一个项目中再次使用这段代码,并希望学习它的最佳实践。Django似乎有更好的方法来实现这一点。Django有很多方法可以简化计算。基本上,您可以尝试的是,用每个用户配置文件的本地时间/小时注释查询集,然后在db级别上执行所有必要的计算,而无需构建时区列表。类似的方法可能会奏效:
from django.db.models import F
from django.db.models.functions import Now
import datetime
users_to_email = UserProfile.objects.exclude(
timezone__isnull=True
).annotate(
local_time=ConvertToTimezone(Now(), 'timezone') # Annotate with users' local time
).filter(
local_time__hour__gte=LOCAL_TIME_TO_EMAIL # Filter to only include users whose local time is past 20 hr
).filter(
Q(last_notified__isnull=True) | # Not notified at all
Q(last_notified__date__lt=F('local_time__date')) # Not notified today
).distinct()[:USER_BATCH_SIZE]
for profile in users_to_email:
self.stdout.write('Notifying %s' % profile)
email_sent = email_daily_reminder(profile.user, user_time_now.date())
self.stdout.write(self.style.SUCCESS('Notified %s' % profile.user.email))
profile.last_notified = user_time_now
profile.save()
在这里,ConvertToTimezone不是Django中可用的db函数,但以下是我以前用于此任务的自定义db函数:
from django.db.models import Func, DateTimeField
class ConvertToTimezone(Func):
"""
Custom SQL expression to convert time to timezone stored in database column
"""
output_field = DateTimeField()
def __init__(self, datetime_field, timezone_field, **extra):
expressions = datetime_field, timezone_field
super(ConvertToTimezone, self).__init__(*expressions, **extra)
def as_sql(self, compiler, connection, fn=None, template=None, arg_joiner=None, **extra_context):
params = []
sql_parts = []
for arg in self.source_expressions:
arg_sql, arg_params = compiler.compile(arg)
sql_parts.append(arg_sql)
params.extend(arg_params)
return "%s AT TIME ZONE %s" % tuple(sql_parts), params
我没有测试这段代码,因此它可能无法立即工作,但应该给您一些提示,说明如何使用Django的数据库函数来简化这里的代码。Django有许多方法可以简化您的计算。基本上,您可以尝试的是,用每个用户配置文件的本地时间/小时注释查询集,然后在db级别上执行所有必要的计算,而无需构建时区列表。类似的方法可能会奏效:
from django.db.models import F
from django.db.models.functions import Now
import datetime
users_to_email = UserProfile.objects.exclude(
timezone__isnull=True
).annotate(
local_time=ConvertToTimezone(Now(), 'timezone') # Annotate with users' local time
).filter(
local_time__hour__gte=LOCAL_TIME_TO_EMAIL # Filter to only include users whose local time is past 20 hr
).filter(
Q(last_notified__isnull=True) | # Not notified at all
Q(last_notified__date__lt=F('local_time__date')) # Not notified today
).distinct()[:USER_BATCH_SIZE]
for profile in users_to_email:
self.stdout.write('Notifying %s' % profile)
email_sent = email_daily_reminder(profile.user, user_time_now.date())
self.stdout.write(self.style.SUCCESS('Notified %s' % profile.user.email))
profile.last_notified = user_time_now
profile.save()
在这里,ConvertToTimezone不是Django中可用的db函数,但以下是我以前用于此任务的自定义db函数:
from django.db.models import Func, DateTimeField
class ConvertToTimezone(Func):
"""
Custom SQL expression to convert time to timezone stored in database column
"""
output_field = DateTimeField()
def __init__(self, datetime_field, timezone_field, **extra):
expressions = datetime_field, timezone_field
super(ConvertToTimezone, self).__init__(*expressions, **extra)
def as_sql(self, compiler, connection, fn=None, template=None, arg_joiner=None, **extra_context):
params = []
sql_parts = []
for arg in self.source_expressions:
arg_sql, arg_params = compiler.compile(arg)
sql_parts.append(arg_sql)
params.extend(arg_params)
return "%s AT TIME ZONE %s" % tuple(sql_parts), params
我没有测试这段代码,所以它可能无法立即工作,但应该给您一些关于使用Django的数据库函数简化代码的提示