diff --git a/bookwyrm/templatetags/sealed_dates.py b/bookwyrm/templatetags/sealed_dates.py index fb64734fa..f0b0f7d25 100644 --- a/bookwyrm/templatetags/sealed_dates.py +++ b/bookwyrm/templatetags/sealed_dates.py @@ -10,6 +10,7 @@ register = template.Library() @register.filter(expects_localtime=True, is_safe=False) def naturalday_partial(date): + """allow templates to easily format SealedDate objects""" if not isinstance(date, SealedDate): return defaultfilters.date(date) if date.has_day: diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 1f4a18aef..e9afcdef6 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -25,7 +25,6 @@ from bookwyrm.models import fields, User, Status, Edition from bookwyrm.models.base_model import BookWyrmModel from bookwyrm.models.activitypub_mixin import ActivitypubMixin from bookwyrm.settings import DOMAIN -from bookwyrm.utils import sealed_date # pylint: disable=too-many-public-methods @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") diff --git a/bookwyrm/tests/test_sealed_date.py b/bookwyrm/tests/test_sealed_date.py index 4e87a26d0..7e4c06c39 100644 --- a/bookwyrm/tests/test_sealed_date.py +++ b/bookwyrm/tests/test_sealed_date.py @@ -11,26 +11,30 @@ from bookwyrm.utils import sealed_date class SealedDateTest(unittest.TestCase): + """test SealedDate class in isolation""" + + # pylint: disable=missing-function-docstring + def setUp(self): - self.dt = datetime.datetime(2023, 10, 20, 17, 33, 10, tzinfo=timezone.utc) + self._dt = datetime.datetime(2023, 10, 20, 17, 33, 10, tzinfo=timezone.utc) def test_day_seal(self): - sealed = sealed_date.SealedDate.from_datetime(self.dt) - self.assertEqual(self.dt, sealed) + sealed = sealed_date.SealedDate.from_datetime(self._dt) + self.assertEqual(self._dt, sealed) self.assertEqual("2023-10-20", sealed.partial_isoformat()) self.assertTrue(sealed.has_day) self.assertTrue(sealed.has_month) def test_month_seal(self): - sealed = sealed_date.MonthSeal.from_datetime(self.dt) - self.assertEqual(self.dt, sealed) + sealed = sealed_date.MonthSeal.from_datetime(self._dt) + self.assertEqual(self._dt, sealed) self.assertEqual("2023-10", sealed.partial_isoformat()) self.assertFalse(sealed.has_day) self.assertTrue(sealed.has_month) def test_year_seal(self): - sealed = sealed_date.YearSeal.from_datetime(self.dt) - self.assertEqual(self.dt, sealed) + sealed = sealed_date.YearSeal.from_datetime(self._dt) + self.assertEqual(self._dt, sealed) self.assertEqual("2023", sealed.partial_isoformat()) self.assertFalse(sealed.has_day) self.assertFalse(sealed.has_month) @@ -95,20 +99,24 @@ class SealedDateTest(unittest.TestCase): class SealedDateFormFieldTest(unittest.TestCase): + """test form support for SealedDate objects""" + + # pylint: disable=missing-function-docstring + def setUp(self): - self.dt = datetime.datetime(2022, 11, 21, 17, 1, 0, tzinfo=timezone.utc) + self._dt = datetime.datetime(2022, 11, 21, 17, 1, 0, tzinfo=timezone.utc) self.field = sealed_date.SealedDateFormField() def test_prepare_value(self): - sealed = sealed_date.SealedDate.from_datetime(self.dt) + sealed = sealed_date.SealedDate.from_datetime(self._dt) self.assertEqual("2022-11-21", self.field.prepare_value(sealed)) def test_prepare_value_month(self): - sealed = sealed_date.MonthSeal.from_datetime(self.dt) + sealed = sealed_date.MonthSeal.from_datetime(self._dt) self.assertEqual("2022-11-0", self.field.prepare_value(sealed)) def test_prepare_value_year(self): - sealed = sealed_date.YearSeal.from_datetime(self.dt) + sealed = sealed_date.YearSeal.from_datetime(self._dt) self.assertEqual("2022-0-0", self.field.prepare_value(sealed)) def test_to_python(self): diff --git a/bookwyrm/utils/sealed_date.py b/bookwyrm/utils/sealed_date.py index c7ad3b7f3..9181fcdd3 100644 --- a/bookwyrm/utils/sealed_date.py +++ b/bookwyrm/utils/sealed_date.py @@ -12,6 +12,7 @@ from django.forms import DateField from django.forms.widgets import SelectDateWidget from django.utils import timezone +# pylint: disable=no-else-return __all__ = [ "SealedDate", @@ -32,17 +33,25 @@ class SealedDate(datetime): @property def has_day(self) -> bool: + """whether this is a full date""" return self.has_month @property def has_month(self) -> bool: + """whether this date includes month""" return True def partial_isoformat(self) -> str: + """partial ISO-8601 format""" return self.strftime("%Y-%m-%d") @classmethod def from_datetime(cls: Type[Sealed], dt: datetime) -> Sealed: + """construct a SealedDate object from a timezone-aware datetime + + Use subclasses to specify precision. If `dt` is naive, `ValueError` + is raised. + """ # pylint: disable=invalid-name if timezone.is_naive(dt): raise ValueError("naive datetime not accepted") @@ -50,6 +59,9 @@ class SealedDate(datetime): @classmethod def from_date_parts(cls: Type[Sealed], year: int, month: int, day: int) -> Sealed: + """construct a SealedDate from year, month, day. + + Use sublcasses to specify precision.""" # because SealedDate is actually a datetime object, we must create it with a # timezone such that its date remains stable no matter the values of USE_TZ, # current_timezone and default_timezone. @@ -57,6 +69,8 @@ class SealedDate(datetime): class MonthSeal(SealedDate): + """a date sealed into month precision""" + @property def has_day(self) -> bool: return False @@ -66,6 +80,8 @@ class MonthSeal(SealedDate): class YearSeal(SealedDate): + """a date sealed into year precision""" + @property def has_month(self) -> bool: return False @@ -75,6 +91,11 @@ class YearSeal(SealedDate): def from_partial_isoformat(value: str) -> SealedDate: + """construct SealedDate from a partial string. + + Accepted formats: YYYY, YYYY-MM, YYYY-MM-DD; otherwise `ValueError` + is raised. + """ match = _partial_re.match(value) if not match: @@ -127,6 +148,10 @@ class SealedDateFormField(DateField): class SealedDateDescriptor: + """descriptor for SealedDateField. + + Encapsulates the "two columns, one field" for SealedDateField. + """ _SEAL_TYPES = { YearSeal: "YEAR", @@ -185,6 +210,7 @@ class SealedDateDescriptor: class SealedDateField(models.DateTimeField): # FIXME: use DateField. + """a date field for Django models, using SealedDate as values""" descriptor_class = SealedDateDescriptor