Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django4 timezones: Enable & migrate ikhaya article model to DateTime field #1379

Merged
merged 87 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
54c221e
Remove `.replace(tzinfo=None)`
chris34 Jan 26, 2024
6f5ade7
Use django.utils.timezone.now
chris34 Jan 28, 2024
d0cc5fe
Remove not needed lines
chris34 Jun 15, 2024
a6df90c
Use TZ from Django
chris34 Jun 15, 2024
4e2916f
Fix deprecation warning for utcfromtimestamp
chris34 Jun 15, 2024
3d048cd
Fix DeprecationWarning `datetime.utcnow()`
chris34 Jun 15, 2024
ff9a2d1
ikhaya, models: Fixup Managers for timezone aware datetimes
chris34 Jul 14, 2024
177a0ff
Add changed migrations
chris34 Jul 14, 2024
d0cfb8b
Sort imports
chris34 Sep 7, 2024
e34e838
portal/user_edit_groups.html: Remove warning
chris34 Nov 30, 2024
042648c
wiki-fixture: Add timezone info
chris34 Nov 30, 2024
08f96fd
Old portal Migrations: Use timezone-aware objects
chris34 Nov 30, 2024
89927ca
markup/macros: Fix import for `UTC`
chris34 Nov 30, 2024
60cbc3b
wiki/forms: Fix strptime-string for timezone aware datetimes
chris34 Nov 30, 2024
afa5db4
portal/test_user.py: Use timezone aware datetimes
chris34 Nov 30, 2024
950ff50
ikhaya/test_views.py: Use timezone aware datetimes
chris34 Nov 30, 2024
a4c3507
forum/test_views.py: Use timezone aware datetimes
chris34 Nov 30, 2024
a3baebc
planet/test_views.py: Use timezone aware datetimes
chris34 Nov 30, 2024
c1dc44d
Forum, views: Add basic test for topiclist content
chris34 Dec 1, 2024
5604227
Typo
chris34 Dec 21, 2024
fa9c572
action_mv_baustelle.html: Remove DateTime script
chris34 Dec 21, 2024
fa65e68
ikhaya/event_suggest.html: Remove DateTime script
chris34 Dec 21, 2024
415ce65
ikhaya/event_suggest.html: Do not manually render form
chris34 Dec 21, 2024
a069e29
Add migrations for changed verbose_names in event model
chris34 Dec 26, 2024
e8394e6
ikhaya/event_suggest.html: Display WikiEditor
chris34 Dec 21, 2024
9bb0339
ikhaya/article_edit.html: Remove DateTime script
chris34 Dec 21, 2024
0da38aa
ikhaya/event_edit.html: Remove DateTime script
chris34 Dec 21, 2024
e82534d
ikhaya/event_edit.html: Dont render form manually
chris34 Dec 21, 2024
d03f408
forum/forum_edit.html: Remove unused scripts
chris34 Dec 21, 2024
861a0bf
portal/configuration.html: Remove DateTime script
chris34 Dec 21, 2024
5b711ab
ChangeLog: Typo
chris34 Dec 22, 2024
fd34f4a
portal/user_edit_status.html: Fix Heading in docstring
chris34 Dec 22, 2024
f780064
Typo
chris34 Dec 22, 2024
ea8aad0
portal/memberlist.html: Remove DateTime script
chris34 Dec 22, 2024
29acd04
portal/user_admin.html: Remove DateTime script
chris34 Dec 22, 2024
187f230
Remove script & styles for DateTime
chris34 Dec 22, 2024
819953c
UserField: Fix typos in docstring
chris34 Dec 22, 2024
c512261
Introduce NativeDateInput, NativeTimeInput and NativeSplitDateTimeWidget
chris34 Dec 22, 2024
a052b76
EditCommentForm: Remove HTML attributes for size
chris34 Dec 22, 2024
20b9b67
style/portal.less: Remove unused `#forum_rights` section
chris34 Dec 22, 2024
72c52ea
style/main.less: Style time input following a date input
chris34 Dec 22, 2024
3b6bd85
Make label for div-style-rendered forms bold
chris34 Dec 22, 2024
e252487
WikiEditor: Insert toolbar directly before textarea
chris34 Dec 22, 2024
6bd944f
wiki.less: Generalize style of labels with checkbox in add attachment…
chris34 Dec 22, 2024
5b386f5
EditUserStatusForm: Remove not need lines
chris34 Dec 22, 2024
64cf2db
Ikhaya, Article: Replace `unique_together` with `UniqueConstraint`
chris34 Dec 26, 2024
f9c8a2b
Ikhaya, EditArticleForm: Remove logic for the updated field on save
chris34 Dec 26, 2024
819dddf
Ikhaya, EditArticleForm: Set initial value for updated, only if artic…
chris34 Dec 26, 2024
73062b9
ikhaya/test_views.py: Fix tests after form is not manually rendered a…
chris34 Dec 26, 2024
20d2872
Add migration for article updated unique constraint
chris34 Dec 26, 2024
16ea7f2
Ikhaya, Article: Add field `publication_datetime`
chris34 Dec 27, 2024
eb5af4b
portal/test_migrations.py: Add license header
chris34 Dec 27, 2024
a7d2361
Ikhaya, Article: Remove fields `pub_date` and `pub_time`
chris34 Dec 29, 2024
9d074fe
Introduce `Article.is_updated`
chris34 Dec 29, 2024
69c9138
remove cache for ikhaya articles
chris34 Dec 29, 2024
b1468bc
Ikhaya, views: Introduce `_get_article_by_date_and_slug` to unify logic
chris34 Dec 29, 2024
c78d961
Ikhaya, article_edit: use `form_class` to instantiate with same param…
chris34 Dec 29, 2024
d5870ff
Ikhaya, Article: Remove cache clearance on `save`
chris34 Dec 29, 2024
63b83cc
portal/test_views.py: Remove unused variables
chris34 Dec 29, 2024
dc0ba1f
Sort imports
chris34 Dec 29, 2024
3046c96
Older python compatibility: UTC → timezone.utc
chris34 Dec 29, 2024
e90dc6d
Adjust ikhaya tests for new model-attributes
chris34 Jan 2, 2025
2abb8a5
Ikhaya, Article.save: Remove check for None valued fields
chris34 Jan 2, 2025
0459b7a
Ikhaya, articles: Use UTC in URLs and DB-query
chris34 Jan 3, 2025
ccf0238
Non-German `verbose_name` for `Report.pub_date`
chris34 Jan 3, 2025
e6198bf
ikhaya/article_edit.html: Fix link in breadcrump
chris34 Jan 3, 2025
cfff77d
ikhaya/article_edit.html: Use django's form rendering
chris34 Jan 3, 2025
5d5a23b
Add migration to unify `Article.updated` semantic
chris34 Jan 3, 2025
faf79fd
Enforce UTC for Article.stamp
chris34 Jan 3, 2025
575cc3f
Article UniqueConstraint: Let it use UTC, as URLs are also based on UTC
chris34 Jan 3, 2025
a6d5ccd
Use `annotate_publication_date_utc` to check uniqueness in `EditArtic…
chris34 Jan 4, 2025
7b950b1
Use `annotate_publication_date_utc` in more places
chris34 Jan 4, 2025
edaa621
Remove `Article.pub_datetime`
chris34 Jan 4, 2025
138b953
utils.dates.datetime_to_timezone: Remove unused parameter `enforce_utc`
chris34 Jan 4, 2025
47f148a
Ikhaya, index: Reduce number of queries with select_related
chris34 Jan 4, 2025
0d746e9
Typo
chris34 Jan 4, 2025
08dfd4b
Fix TestArticleDetail.test_anonymous_user_http_post_not_allowed
chris34 Jan 4, 2025
1268cdf
Ikhaya, index: Annotate subscription status to each article
chris34 Jan 5, 2025
6c17a31
Remove default ordering of Ikhaya.Article
chris34 Jan 5, 2025
0340df0
Fieldset: Remove border, style legend in bold
chris34 Jan 5, 2025
4899579
Add margin between form fields
chris34 Jan 7, 2025
547cef5
Allow to not style labels bold with a special class
chris34 Jan 7, 2025
6fae51b
Squash migrations for ikhaya article
chris34 Jan 8, 2025
bc4e6fb
Add _msg_unique_constraint to ease testing unique error with differen…
chris34 Jan 11, 2025
9c9d777
Add _msg_not_null_constraint to ease testing null error with differen…
chris34 Jan 11, 2025
a0f601c
Update Changelog
chris34 Jan 11, 2025
269283d
TestTopicFeedPostRevision: Save edit datetime earlier
chris34 Jan 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ChangeLog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,20 @@ Deployment notes

✨ New features
---------------
* Wiki: Update metadata and content of related pages after a edit
* Wiki: Update metadata and content of related pages after a edit

🏗 Changes
----------
* ``*.pot`` files are no longer in git
* Enable timezon-aware datetimes from Django
* Fix deprecation warnings related to UTC methods

🗑 Deprecations
--------------

🔥 Removals
-----------
* Replace javascript based datetime picker with native HTML one

🐛 Fixes
--------
Expand Down
3 changes: 1 addition & 2 deletions inyoka/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

from celery.schedules import crontab

import inyoka

#: Base path of this application
BASE_PATH = dirname(__file__)
Expand All @@ -41,7 +40,7 @@
TIME_ZONE = 'Europe/Berlin'

# https://docs.djangoproject.com/en/3.2/topics/i18n/timezones/
USE_TZ = False
USE_TZ = True

# Language code for this installation. All choices can be found here:
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
Expand Down
2 changes: 0 additions & 2 deletions inyoka/forum/jinja2/forum/forum_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
#}
{%- extends 'forum/page.html' %}
{% from 'macros.html' import render_form %}
{% set styles = ['editor', 'datetimefield'] %}
{% set scripts = ['WikiEditor', 'DateTime'] %}

{% if not forum %}
{% set BREADCRUMBS = [(_('Create forum'), href('forum', 'forum', 'new'))] + BREADCRUMBS|d([]) %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.2.13 on 2024-06-15 22:17

import django.utils.timezone
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("forum", "0016_auto_20230312_1704"),
]

operations = [
migrations.AlterField(
model_name="poll",
name="start_time",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AlterField(
model_name="post",
name="pub_date",
field=models.DateTimeField(
db_index=True, default=django.utils.timezone.now
),
),
migrations.AlterField(
model_name="postrevision",
name="store_date",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]
17 changes: 9 additions & 8 deletions inyoka/forum/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import os
import pickle
import re
from datetime import datetime
from datetime import datetime, timezone
from functools import reduce
from hashlib import md5
from itertools import groupby
Expand All @@ -26,6 +26,7 @@
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
from django.db.models import Count, F, Max, QuerySet, Sum
from django.utils import timezone as dj_timezone
from django.utils.encoding import DjangoUnicodeDecodeError, force_str
from django.utils.html import escape, format_html
from django.utils.translation import gettext as _
Expand Down Expand Up @@ -779,7 +780,7 @@ class PostRevision(models.Model):
"""

text = InyokaMarkupField(application='forum')
store_date = models.DateTimeField(default=datetime.utcnow)
store_date = models.DateTimeField(default=dj_timezone.now)
post = models.ForeignKey('forum.Post', related_name='revisions', on_delete=models.CASCADE)

def get_absolute_url(self, action='restore'):
Expand Down Expand Up @@ -815,7 +816,7 @@ class Post(models.Model, LockableObject):
lock_key_base = 'forum/post_lock'

position = models.IntegerField(default=None, db_index=True)
pub_date = models.DateTimeField(default=datetime.utcnow, db_index=True)
pub_date = models.DateTimeField(default=dj_timezone.now, db_index=True)
hidden = models.BooleanField(default=False)
text = InyokaMarkupField(application='forum')
has_revision = models.BooleanField(default=False)
Expand Down Expand Up @@ -1095,7 +1096,8 @@ def check_ownpost_limit(self, type='edit'):
return False
if t == -1:
return True
delta = datetime.utcnow() - self.pub_date.replace(tzinfo=None)

delta = datetime.now(timezone.utc) - self.pub_date
return delta.total_seconds() < t

def mark_ham(self):
Expand Down Expand Up @@ -1245,8 +1247,7 @@ def update_post_ids(att_ids, post):
return False

attachments = Attachment.objects.filter(id__in=att_ids, post=None).all()

base_path = datetime.utcnow().strftime('forum/attachments/%S/%W')
base_path = dj_timezone.now().strftime('forum/attachments/%S/%W')

for attachment in attachments:
new_name = secure_filename('%d-%s' % (post.pk, attachment.name))
Expand Down Expand Up @@ -1386,7 +1387,7 @@ class Meta:

class Poll(models.Model):
question = models.CharField(max_length=250)
start_time = models.DateTimeField(default=datetime.utcnow)
start_time = models.DateTimeField(default=dj_timezone.now)
end_time = models.DateTimeField(null=True)
multiple_votes = models.BooleanField(default=False)

Expand All @@ -1405,7 +1406,7 @@ def participated(self):
@property
def ended(self):
"""Returns a boolean whether the poll ended already"""
return self.end_time and datetime.utcnow() > self.end_time
return self.end_time and dj_timezone.now() > self.end_time

@deferred
def can_vote(self):
Expand Down
16 changes: 10 additions & 6 deletions inyoka/forum/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
:copyright: (c) 2007-2024 by the Inyoka Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from datetime import datetime, timedelta
from datetime import timedelta
from functools import partial
from itertools import groupby
from operator import attrgetter
Expand All @@ -18,8 +18,10 @@
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import F, Q
from django.db.models.functions import Now
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.utils import timezone as dj_timezone
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
from django.views.generic import CreateView, DetailView, UpdateView
Expand Down Expand Up @@ -327,9 +329,11 @@ def handle_polls(request, topic, poll_ids):

if 'add_poll' in request.POST and poll_form.is_valid():
d = poll_form.cleaned_data
now = datetime.utcnow()
end_time = (d['duration'] and now + timedelta(days=d['duration'])
or None)
now = dj_timezone.now()
if d['duration']:
end_time = now + timedelta(days=d['duration'])
else:
end_time = None
poll = Poll(topic=topic, question=d['question'],
multiple_votes=d['multiple'],
start_time=now, end_time=end_time)
Expand Down Expand Up @@ -624,7 +628,7 @@ def edit(request, forum_slug=None, topic_slug=None, post_id=None,
if not post: # not when editing an existing post
doublepost = Post.objects \
.filter(author=request.user, text=d['text'],
pub_date__gt=(datetime.utcnow() - timedelta(0, 300)))
pub_date__gt=(Now() - timedelta(0, 300)))
if not newtopic:
doublepost = doublepost.filter(topic=topic)
try:
Expand Down Expand Up @@ -1626,7 +1630,7 @@ def topiclist(request, page=1, action='newposts', hours=24, user=None, forum=Non
hours = int(hours)
if hours > 24:
raise Http404()
topics = topics.filter(posts__pub_date__gt=datetime.utcnow() - timedelta(hours=hours))
topics = topics.filter(posts__pub_date__gt=Now() - timedelta(hours=hours))
topics = topics.distinct()
title = _('Posts of the last %(n)d hours') % {'n': hours}
url = href('forum', 'last%d' % hours, forum)
Expand Down
79 changes: 36 additions & 43 deletions inyoka/ikhaya/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
from datetime import time as dt_time

from django import forms
from django.forms import SplitDateTimeField
from django.utils import timezone as dj_timezone
from django.utils.timezone import get_current_timezone
from django.utils.translation import gettext_lazy

from inyoka.ikhaya.models import Article, Category, Event, Suggestion
from inyoka.portal.models import StaticFile
from inyoka.utils.dates import datetime_to_timezone
from inyoka.utils.forms import (
DateTimeField,
DateWidget,
NativeDateInput,
NativeSplitDateTimeWidget,
NativeTimeInput,
StrippedCharField,
TimeWidget,
UserField,
)
from inyoka.utils.text import slugify
Expand Down Expand Up @@ -55,72 +57,63 @@ class EditArticleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
readonly = kwargs.pop('readonly', False)

if instance:
initial = kwargs.setdefault('initial', {})
if instance.pub_datetime != instance.updated:
initial['updated'] = instance.updated
if instance.public and not instance.updated:
initial['updated'] = dj_timezone.now()
initial['author'] = instance.author.username

super().__init__(*args, **kwargs)
# Following stuff is in __init__ to keep helptext etc intact.

self.fields['icon'].queryset = StaticFile.objects.filter(is_ikhaya_icon=True)
if readonly:
for field in ('subject', 'intro', 'text'):
self.fields[field].widget.attrs['readonly'] = True

if not instance:
del self.fields['updated']

author = UserField(label=gettext_lazy('Author'), required=True)
updated = DateTimeField(label=gettext_lazy('Last update'),
help_text=gettext_lazy('If you keep this field empty, the '
'publication date will be used.'),
localize=True, required=False)

def save(self):
instance = super().save(commit=False)
if 'pub_date' in self.cleaned_data and (not instance.pk or
not instance.public or self.cleaned_data.get('public', None)):
instance.pub_date = self.cleaned_data['pub_date']
instance.pub_time = self.cleaned_data['pub_time']
if self.cleaned_data.get('updated', None):
instance.updated = self.cleaned_data['updated']
elif {'pub_date', 'pub_time'} in set(self.cleaned_data.keys()):
instance.updated = datetime.combine(
self.cleaned_data['pub_date'],
self.cleaned_data['pub_time'])
instance.save()
return instance

def clean_slug(self):
slug = self.cleaned_data['slug']
pub_date = self.cleaned_data.get('pub_date', None)
if slug and pub_date:
pub_datetime = self.cleaned_data.get('publication_datetime', None)

if slug and pub_datetime:
slug = slugify(slug)
q = Article.objects.filter(slug=slug, pub_date=pub_date)

q = Article.objects.annotate_publication_date_utc()
q = q.filter(slug=slug, publication_date_utc=pub_datetime)

if self.instance.pk:
q = q.exclude(id=self.instance.pk)

if q.exists():
raise forms.ValidationError(gettext_lazy('There already '
'exists an article with this slug!'))
return slug

class Meta:
model = Article
exclude = ['updated', 'comment_count']
fields = ('subject', 'intro', 'text', 'author', 'category', 'icon', 'public', 'comments_enabled', 'updated', 'publication_datetime', 'slug')
field_classes = {
'updated': SplitDateTimeField,
'publication_datetime': SplitDateTimeField,
}
widgets = {
'subject': forms.TextInput(attrs={'size': 50}),
'intro': forms.Textarea(attrs={'rows': 3}),
'text': forms.Textarea(attrs={'rows': 15}),
'pub_date': DateWidget(),
'pub_time': TimeWidget(),
'subject': forms.TextInput(),
'intro': forms.Textarea(),
'text': forms.Textarea(),
'publication_datetime': NativeSplitDateTimeWidget(),
'updated': NativeSplitDateTimeWidget(),
}


class EditPublicArticleForm(EditArticleForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
del self.fields['pub_date']
del self.fields['pub_time']

class Meta(EditArticleForm.Meta):
exclude = EditArticleForm.Meta.exclude + ['slug']
exclude = ['slug', 'publication_datetime']


class EditCategoryForm(forms.ModelForm):
Expand Down Expand Up @@ -195,10 +188,10 @@ def clean(self):
class Meta:
model = Event
widgets = {
'date': DateWidget,
'time': TimeWidget,
'enddate': DateWidget,
'endtime': TimeWidget,
'date': NativeDateInput,
'time': NativeTimeInput,
'enddate': NativeDateInput,
'endtime': NativeTimeInput,
}
exclude = ['author', 'slug', 'visible']

Expand Down
16 changes: 6 additions & 10 deletions inyoka/ikhaya/jinja2/ikhaya/article_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
#}
{%- extends 'ikhaya/page.html' %}
{% from 'macros.html' import render_form %}
{% set styles = ['editor', 'datetimefield'] %}
{% set scripts = ['WikiEditor', 'DateTime'] %}
{% set styles = ['editor'] %}
{% set scripts = ['WikiEditor'] %}

{% if not article %}
{% set BREADCRUMBS = [(_('New Article'), href('ikhaya', 'new'))] + BREADCRUMBS|d([]) %}
{% set BREADCRUMBS = [(_('New Article'), href('ikhaya', 'article', 'new'))] + BREADCRUMBS|d([]) %}
{% else %}
{% set BREADCRUMBS = [(article.subject, article|url),
(_('Edit'), article|url('edit'))] + BREADCRUMBS|d([]) %}
Expand All @@ -36,13 +36,9 @@ <h3>{% trans %}New Article{% endtrans %}</h3>
{%- if article.article_icon %}
<div class="icon"><img src="{{ article.article_icon|url }}" alt="{{ article.article_icon.identifier|e }}"></div>
{%- endif %}
<dl>
{{ render_form(form, ['subject', 'intro', 'text', 'author', 'category', 'icon', 'public', 'comments_enabled'], inline=true) }}
{%- if article != none %}
{{ render_form(form, ['updated'], inline=true) }}
{%- endif %}
{{ render_form(form, ['pub_date', 'pub_time', 'slug'], inline=true) }}
</dl>

{{ form.as_div() }}

<p>
<input type="submit" value="{% trans %}Submit{% endtrans %}" name="send">
<input type="submit" value="{% trans %}Preview{% endtrans %}" name="preview">
Expand Down
4 changes: 2 additions & 2 deletions inyoka/ikhaya/jinja2/ikhaya/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ <h3 class="title"><a href="{{ article|url }}">{{ article.subject|e }}</a></h3>
{% trans link=article.author|url, author=article.author.username|e -%}
Published by <a href="{{ link }}">{{ author }}</a>
{%- endtrans %} |
{{ article.pub_datetime|datetime }} |
{{ article.publication_datetime|datetime }} |
{% trans %}Category:{% endtrans %} <a href="{{ article.category|url }}">{{ article.category.name|e }}</a>
{%- if article.updated > article.pub_datetime %}
{%- if article.is_updated %}
| {% trans %}Last update:{% endtrans %} {{ article.updated|datetime }}
{%- endif %}
| <a href="{{ article|url('id') }}">#</a>
Expand Down
Loading
Loading