Skip to content

Commit

Permalink
Shelter stable dates from USE_TZ
Browse files Browse the repository at this point in the history
Some dates (publication dates, author dates) are meant as _literals_. What
the user inputs through a `SelectDateWidget` should be preserved as-is.
Django's otherwise-excelent support for timezones interferes with it (see

Until a better fate of these columns is determined (do we migrate them to a
DateField?), and as a stop-gap measure, we can start being faithful to the
data by storing them in the Eastern-most timezone.

This is particularly important because 1/1/YYYY is a common pattern in
publication dates, given bookwyrm-social#743.
  • Loading branch information
dato committed Oct 20, 2023
1 parent 28d1d46 commit 85f04f1
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 8 deletions.
4 changes: 2 additions & 2 deletions bookwyrm/models/author.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class Author(BookDataModel):
max_length=255, blank=True, null=True, deduplication_field=True
)
# idk probably other keys would be useful here?
born = fields.DateTimeField(blank=True, null=True)
died = fields.DateTimeField(blank=True, null=True)
born = fields.StableDateField(blank=True, null=True)
died = fields.StableDateField(blank=True, null=True)
name = fields.CharField(max_length=255)
aliases = fields.ArrayField(
models.CharField(max_length=255), blank=True, default=list
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/models/book.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ class Book(BookDataModel):
preview_image = models.ImageField(
upload_to="previews/covers/", blank=True, null=True
)
first_published_date = fields.DateTimeField(blank=True, null=True)
published_date = fields.DateTimeField(blank=True, null=True)
first_published_date = fields.StableDateField(blank=True, null=True)
published_date = fields.StableDateField(blank=True, null=True)

objects = InheritanceManager()
field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"])
Expand Down
35 changes: 32 additions & 3 deletions bookwyrm/models/fields.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
""" activitypub-aware django model fields """
from dataclasses import MISSING
from datetime import datetime
from datetime import date, datetime, timedelta
import re
from uuid import uuid4
from urllib.parse import urljoin
Expand All @@ -11,7 +11,7 @@
from django.contrib.postgres.fields import CICharField as DjangoCICharField
from django.core.exceptions import ValidationError
from django.db import models
from django.forms import ClearableFileInput, ImageField as DjangoImageField
from django.forms import ClearableFileInput, DateField as DjangoDateField, ImageField as DjangoImageField
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import filepath_to_uri
Expand Down Expand Up @@ -537,7 +537,6 @@ def field_to_activity(self, value):
def field_from_activity(self, value, allow_external_connections=True):
missing_fields = datetime(1970, 1, 1) # "2022-10" => "2022-10-01"
try:
# TODO(dato): investigate `ignoretz=True` wrt bookwyrm#3028.
date_value = dateutil.parser.parse(value, default=missing_fields)
try:
return timezone.make_aware(date_value)
Expand All @@ -547,6 +546,36 @@ def field_from_activity(self, value, allow_external_connections=True):
return None


class StableDateFormField(DjangoDateField): # should be in forms/fields.py
"""converts datetime to date, purposedly ignoring USE_TZ"""
def prepare_value(self, value):
if isinstance(value, datetime):
return value.date()
return value


class StableDateField(DateTimeField):
"""a date in a datetime column, forcibly unaffected by USE_TZ"""

# TODO: extend to PartialStableDate (or SealedDate).

def formfield(self, **kwargs):
kwargs.setdefault('form_class', StableDateFormField)
return super().formfield(**kwargs)

def to_python(self, value):
if isinstance(value, date):
tz = timezone.get_fixed_timezone(timedelta(hours=-12))
naive_dt = datetime(value.year, value.month, value.day)
# Convert to midnight in a timezone that has a stable date
# across the globe. (This is a hotfix while we keep on
# storing stable dates as DateTimeField.)
return timezone.make_aware(naive_dt, tz)
return super().to_python(value) # XXX Just return value?

# TODO: override field_from_activity(), if necessary?


class HtmlField(ActivitypubFieldMixin, models.TextField):
"""a text field for storing html"""

Expand Down
1 change: 0 additions & 1 deletion bookwyrm/tests/views/books/test_edit_book.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ def test_create_book(self):
book = models.Edition.objects.get(title="New Title")
self.assertEqual(book.parent_work.title, "New Title")

@expectedFailure # bookwyrm#3028
def test_create_book_published_date(self):
"""create a book and verify its publication date"""
view = views.ConfirmEditBook.as_view()
Expand Down

0 comments on commit 85f04f1

Please sign in to comment.