Django - При задании часового пояса, месяца и года получить все сообщения, созданные в этот день в этом часовом поясе
Итак, у меня есть такая Post
модель. Я хочу иметь возможность получить все сообщения, которые были created
в месяц, год в определенном часовом поясе.
model.py
class Post(models.Model):
uuid = models.UUIDField(primary_key=True)
created = models.DateTimeField('Created at', auto_now_add=True)
updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
creator = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="post_creator")
body = models.CharField(max_length=POST_MAX_LEN)
Например, если пользователь создает 10 сообщений в ноябре, 2 в декабре 2021 года в PST. Тогда у меня есть представление, которое принимает month
, year
и time_zone
и, допустим, url выглядит как /post/<int:month>/<int:year>/<str:time_zone>
и пользователь пингует /post/11/2021/PST
, тогда оно должно вернуть 10 сообщений из ноября. Как вернуть все сообщения за месяц и год в заданном часовом поясе, месяце и году?
Примечание: Сложный крайний случай, который следует учитывать, - это если они публикуют информацию в самый последний день месяца с большим опозданием. В зависимости от часового пояса что-то вроде 12/31/2021 в UTC может быть 01/01/2022.
Установка:
- Django 3.2.9
- Postgresql
Я бы попробовал что-то вроде этого:
Предположим, что вы передаете заданный месяц как given_month и данный год как given_year и заданный часовой пояс как given_timezone
Model_Name.objects.filter(created.split('/',4)[2]=given_month,created.split('/',4)[3]=given_year, created.split('/',4)[4]=given_timezone)
Это должно получить значение из раздела месяца, года и timzone вашего сообщения. Возможно, вам придется поиграть с тем, что я вам дал. Также может быть лучше добавить связь от пользователя к посту, чтобы вы могли отфильтровать пользователя для поиска постов с моим ответом. В большинстве случаев это будет намного эффективнее, если предположить, что постов больше, чем пользователей. Это должно вывести вас на правильный путь.
Взять первый момент месяца (полночь 1-го числа) в UTC и первый момент следующего месяца в UTC, скорректировать их с нужным вам часовым поясом, сделать запрос posted__range=(a, b)?
Это может сработать (но математика дат сложна...).
Для этого необходимо python-dateutil
сделать вычисление end
времени надежным.
from dateutil.relativedelta import relativedelta
from django.utils import timezone
year = 2021
month = 6
tz = datetime.tzinfo("PST")
start = datetime.datetime(year, month, 1)
end = start + relativedelta(months=1)
posts = Post.objects.filter(created__range=(
timezone.make_aware(start, tz),
timezone.make_aware(end, tz),
))
Рассмотрите возможность сохранения записи в UTC времени даты, не пытайтесь сохранить смещение часового пояса. Просто запишите время поступления данных на сервер в UTC, и пусть браузер или ваше приложение переведет его в свой настроенный часовой пояс. Не имеет значения, если пользователь в Китае создал сообщение, а на следующий день переехал в Нью-Йорк и прочитал ранее созданное сообщение, время создания_даты будет одинаковым для обоих мест, поскольку оно универсально. Таким образом, если пользователь B захочет восстановить данные, записанные пользователем A с октября, это будет так же просто, как запросить created_at::date>="2021-10-01"
Простите, если это не совсем соответствует вашим ожиданиям, но Django создает поле PostgreSQL timestamp with a time zone
для поля DateTimeField.
# \d posts_post
Table "public.posts_post"
Column | Type | Collation | Nullable | Default
------------+--------------------------+-----------+----------+---------
uuid | uuid | | not null |
created | timestamp with time zone | | not null |
updated_at | timestamp with time zone | | |
body | character varying(500) | | not null |
Indexes:
"posts_post_pkey" PRIMARY KEY, btree (uuid)
Значит, вы можете запрашивать их как временные метки с помощью класса пользовательского поля из Поля временных меток в django.
from django.db import models
from datetime import datetime
from time import strftime
class UnixTimestampField(models.DateTimeField):
"""UnixTimestampField: creates a DateTimeField that is represented on the
database as a TIMESTAMP field rather than the usual DATETIME field.
"""
def __init__(self, null=False, blank=False, **kwargs):
super(UnixTimestampField, self).__init__(**kwargs)
# default for TIMESTAMP is NOT NULL unlike most fields, so we have to
# cheat a little:
self.blank, self.isnull = blank, null
self.null = True # To prevent the framework from shoving in "not null".
def db_type(self, connection):
typ=['TIMESTAMP']
# See above!
if self.isnull:
typ += ['NULL']
if self.auto_created:
typ += ['default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP']
return ' '.join(typ)
def to_python(self, value):
if isinstance(value, int):
return datetime.fromtimestamp(value)
else:
return models.DateTimeField.to_python(self, value)
def get_db_prep_value(self, value, connection, prepared=False):
if value==None:
return None
# Use '%Y%m%d%H%M%S' for MySQL < 4.1
return strftime('%Y-%m-%d %H:%M:%S',value.timetuple())
В запрос вы передадите временную метку, созданную из требуемой временной зоны DateTime. Этот подход также должен быть быстрее, поскольку вам не нужно выполнять 1 дополнительное преобразование DateTime.
class Post(models.Model):
uuid = models.UUIDField(primary_key=True)
created = UnixTimestampField(verbose_name='Created at', auto_now_add=True)
updated_at = UnixTimestampField(verbose_name='Last updated at', auto_now=True, blank=True, null=True)
from datetime import datetime, timezone
Post.objects.filter(created__gte=datetime(year=2021, month=11, day=1, tzinfo=timezone.utc).timestamp(), created__lt=datetime(year=2021, month=12, day=1, tzinfo=timezone.utc).timestamp())