Skip to content

Commit f43b688

Browse files
authored
Resolve strftime type compatibility issue with jdatetime version 5 (#80)
* Resolve strftime type compatibility issue with jdatetime version 5 * Add tests app * Add test runner file * Add tests for template tags * Add "Review" workflow * Remove pip caching from "Review" workflow to resolve workflow run issue * Add quotations to matrix versions in "Review" workflow * Remove Python 3.12 version from "Review" workflow matrix due to "distutils" package issue * Refactor "to_jalali" template tag tests
1 parent cb16b3a commit f43b688

10 files changed

+288
-5
lines changed

.github/workflows/Review.yml

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Review
2+
on:
3+
pull_request:
4+
branches:
5+
- master
6+
- main
7+
push:
8+
branches:
9+
- master
10+
- main
11+
jobs:
12+
unit-test:
13+
strategy:
14+
matrix:
15+
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
16+
django-version: [ "2.2.*", "3.2.*", "4.2.*", "5.0.*" ]
17+
jdatetime-version: [ "3.8.*", "4.1.*", "5.0.*" ]
18+
exclude:
19+
# Django 5 does not support Python 3.8 and 3.9.
20+
- django-version: "5.0.*"
21+
python-version: "3.8"
22+
- django-version: "5.0.*"
23+
python-version: "3.9"
24+
# Django 3 does not support Python 3.11.
25+
- django-version: "3.2.*"
26+
python-version: "3.11"
27+
# Django 2 does not support Python 3.10 and 3.11.
28+
- django-version: "2.2.*"
29+
python-version: "3.10"
30+
- django-version: "2.2.*"
31+
python-version: "3.11"
32+
runs-on: ubuntu-latest
33+
steps:
34+
- name: Checkout code
35+
uses: actions/checkout@v4
36+
- name: Set up Python ${{ matrix.python-version }}
37+
uses: actions/setup-python@v5
38+
with:
39+
python-version: ${{ matrix.python-version }}
40+
- name: Install Django ${{ matrix.django-version }}
41+
run: pip install django==${{ matrix.django-version }}
42+
- name: Install Jdatetime ${{ matrix.jdatetime-version }}
43+
run: pip install jdatetime==${{ matrix.jdatetime-version }}
44+
- name: Run unit tests
45+
run: python runtests.py

jalali_date/templatetags/jalali_tags.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,25 @@ def contents(self):
2727
return self.value
2828

2929

30+
def normalize_strftime(strftime):
31+
"""
32+
Normalize strftime values to make sure their usable for datetime libraries.
33+
"""
34+
if not isinstance(strftime, str):
35+
# Convert non-str values to str to support stuff like "lazy_translations".
36+
strftime = str(strftime)
37+
return strftime
38+
39+
3040
@register.filter
3141
def to_jalali(g_date, strftime=None):
3242
if g_date is None:
3343
return '-'
3444
elif isinstance(g_date, datetime):
35-
strftime = strftime if strftime else DEFAULTS['Strftime']['datetime']
45+
strftime = normalize_strftime(strftime if strftime else DEFAULTS['Strftime']['datetime'])
3646
return datetime2jalali(g_date).strftime(strftime)
3747
elif isinstance(g_date, date):
38-
strftime = strftime if strftime else DEFAULTS['Strftime']['date']
48+
strftime = normalize_strftime(strftime if strftime else DEFAULTS['Strftime']['date'])
3949
return date2jalali(g_date).strftime(strftime)
4050
return '-'
4151

@@ -55,10 +65,10 @@ def jalali_admin_safe_readonly(readonly_field, strftime=None):
5565

5666
field = getattr(instance, field_name)
5767
if isinstance(field, datetime):
58-
strftime = strftime if strftime else DEFAULTS['Strftime']['datetime']
68+
strftime = normalize_strftime(strftime if strftime else DEFAULTS['Strftime']['datetime'])
5969
return ObjectContents(datetime2jalali(field).strftime(strftime))
6070
elif isinstance(field, date):
61-
strftime = strftime if strftime else DEFAULTS['Strftime']['date']
71+
strftime = normalize_strftime(strftime if strftime else DEFAULTS['Strftime']['date'])
6272
return ObjectContents(date2jalali(field).strftime(strftime))
6373
elif field is None:
6474
return ObjectContents('-')
@@ -68,5 +78,5 @@ def jalali_admin_safe_readonly(readonly_field, strftime=None):
6878

6979
@register.simple_tag
7080
def jalali_now(strftime=None):
71-
strftime = strftime if strftime else DEFAULTS['Strftime']['datetime']
81+
strftime = normalize_strftime(strftime if strftime else DEFAULTS['Strftime']['datetime'])
7282
return datetime2jalali(datetime.now()).strftime(strftime)

runtests.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python
2+
import os
3+
import sys
4+
5+
import django
6+
from django.conf import settings
7+
from django.test.utils import get_runner
8+
9+
if __name__ == "__main__":
10+
os.environ["DJANGO_SETTINGS_MODULE"] = "tests.test_settings"
11+
django.setup()
12+
TestRunner = get_runner(settings)
13+
test_runner = TestRunner()
14+
failures = test_runner.run_tests(["tests"])
15+
sys.exit(bool(failures))

tests/__init__.py

Whitespace-only changes.

tests/test_settings.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
SECRET_KEY = "fake-secret-key"
2+
3+
INSTALLED_APPS = [
4+
"tests",
5+
]

tests/tests/__init__.py

Whitespace-only changes.

tests/tests/templatetags/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from datetime import datetime, date
2+
from unittest.mock import Mock
3+
4+
from django.conf import settings
5+
from django.test import SimpleTestCase
6+
from django.utils.translation import gettext_lazy
7+
8+
from jalali_date.templatetags.jalali_tags import jalali_admin_safe_readonly
9+
10+
11+
class TestJalaliAdminSafeReadonlyTemplateTag(SimpleTestCase):
12+
"""
13+
Tests for `jalali_admin_safe_readonly` template tag
14+
"""
15+
16+
FAKE_DATETIME = datetime(year=2012, month=4, day=17, hour=10, minute=51, second=36)
17+
FAKE_DATETIME_JALALI = datetime(year=1391, month=1, day=29, hour=10, minute=51, second=36)
18+
19+
FAKE_DATE = date(year=2012, month=4, day=17)
20+
FAKE_DATE_JALALI = date(year=1391, month=1, day=29)
21+
22+
@property
23+
def default_date_format(self):
24+
return settings.JALALI_DATE_DEFAULTS["Strftime"]["date"]
25+
26+
@property
27+
def default_datetime_format(self):
28+
return settings.JALALI_DATE_DEFAULTS["Strftime"]["datetime"]
29+
30+
@staticmethod
31+
def create_readonly_field(field_value=None):
32+
"""
33+
A helper method which creates a mock readonly field.
34+
"""
35+
instance = Mock()
36+
instance.some_field = field_value
37+
form = Mock()
38+
form.instance = instance
39+
readonly_field = Mock()
40+
readonly_field.form = form
41+
readonly_field.field = {"field": "some_field"}
42+
return readonly_field
43+
44+
def test_instance_missing_field(self):
45+
readonly_field = self.create_readonly_field()
46+
readonly_field.field = {"field": "missing_field"}
47+
self.assertEqual(jalali_admin_safe_readonly(readonly_field), readonly_field)
48+
49+
def test_with_datetime(self):
50+
readonly_field = self.create_readonly_field(self.FAKE_DATETIME)
51+
self.assertEqual(
52+
jalali_admin_safe_readonly(readonly_field).value,
53+
self.FAKE_DATETIME_JALALI.strftime(self.default_datetime_format),
54+
)
55+
56+
def test_with_custom_strftime_datetime(self):
57+
readonly_field = self.create_readonly_field(self.FAKE_DATETIME)
58+
custom_strftime = "%H.%M.%S | %Y-%m-%d"
59+
self.assertEqual(
60+
jalali_admin_safe_readonly(readonly_field=readonly_field, strftime=custom_strftime).value,
61+
self.FAKE_DATETIME_JALALI.strftime(custom_strftime),
62+
)
63+
64+
def test_with_date(self):
65+
readonly_field = self.create_readonly_field(self.FAKE_DATE)
66+
self.assertEqual(
67+
jalali_admin_safe_readonly(readonly_field).value,
68+
self.FAKE_DATE_JALALI.strftime(self.default_date_format),
69+
)
70+
71+
def test_with_custom_strftime_date(self):
72+
readonly_field = self.create_readonly_field(self.FAKE_DATE)
73+
custom_strftime = "%Y-%m-%d"
74+
self.assertEqual(
75+
jalali_admin_safe_readonly(readonly_field=readonly_field, strftime=custom_strftime).value,
76+
self.FAKE_DATE_JALALI.strftime(custom_strftime),
77+
)
78+
79+
def test_error_prevention_with_lazy_translatable_strftime(self):
80+
"""
81+
Ensure that using lazy translatable strings as strftime will not raise an error. (#78)
82+
"""
83+
readonly_field = self.create_readonly_field(self.FAKE_DATETIME)
84+
custom_strftime = gettext_lazy("%H.%M.%S | %Y-%m-%d")
85+
self.assertEqual(
86+
jalali_admin_safe_readonly(readonly_field=readonly_field, strftime=custom_strftime).value,
87+
self.FAKE_DATETIME_JALALI.strftime(str(custom_strftime)),
88+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from datetime import datetime
2+
from unittest.mock import patch, Mock
3+
4+
from django.conf import settings
5+
from django.test import SimpleTestCase
6+
from django.utils.translation import gettext_lazy
7+
8+
from jalali_date.templatetags.jalali_tags import jalali_now
9+
10+
11+
class TestJalaliNowTemplateTag(SimpleTestCase):
12+
"""
13+
Tests for `jalali_now` template tag
14+
"""
15+
16+
FAKE_NOW_DATETIME = datetime(year=2012, month=4, day=17, hour=10, minute=51, second=36)
17+
FAKE_NOW_DATETIME_JALALI = datetime(year=1391, month=1, day=29, hour=10, minute=51, second=36)
18+
19+
@property
20+
def default_datetime_format(self):
21+
return settings.JALALI_DATE_DEFAULTS["Strftime"]["datetime"]
22+
23+
@patch("jalali_date.templatetags.jalali_tags.datetime")
24+
def test_default_strftime(self, patched_datetime: Mock):
25+
patched_datetime.now.return_value = self.FAKE_NOW_DATETIME
26+
self.assertEqual(
27+
jalali_now(),
28+
self.FAKE_NOW_DATETIME_JALALI.strftime(self.default_datetime_format),
29+
)
30+
31+
@patch("jalali_date.templatetags.jalali_tags.datetime")
32+
def test_custom_strftime(self, patched_datetime: Mock):
33+
patched_datetime.now.return_value = self.FAKE_NOW_DATETIME
34+
custom_strftime = "%H.%M.%S | %Y-%m-%d"
35+
self.assertEqual(
36+
jalali_now(strftime=custom_strftime),
37+
self.FAKE_NOW_DATETIME_JALALI.strftime(custom_strftime),
38+
)
39+
40+
@patch("jalali_date.templatetags.jalali_tags.datetime")
41+
def test_error_prevention_with_lazy_translatable_strftime(self, patched_datetime: Mock):
42+
"""
43+
Ensure that using lazy translatable strings as strftime will not raise an error. (#78)
44+
"""
45+
patched_datetime.now.return_value = self.FAKE_NOW_DATETIME
46+
custom_strftime = gettext_lazy("%Y-%m-%d")
47+
self.assertEqual(
48+
jalali_now(strftime=custom_strftime),
49+
self.FAKE_NOW_DATETIME_JALALI.strftime(str(custom_strftime)),
50+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from datetime import datetime, date
2+
3+
from django.conf import settings
4+
from django.test import SimpleTestCase
5+
from django.utils.translation import gettext_lazy
6+
7+
from jalali_date.templatetags.jalali_tags import to_jalali
8+
9+
10+
class TestToJalaliTemplateTag(SimpleTestCase):
11+
"""
12+
Tests for `to_jalali` template tag
13+
"""
14+
15+
FAKE_DATE = date(year=2012, month=4, day=17)
16+
FAKE_DATE_JALALI = date(year=1391, month=1, day=29)
17+
18+
FAKE_DATETIME = datetime(year=2012, month=4, day=17, hour=10, minute=51, second=36)
19+
FAKE_DATETIME_JALALI = datetime(year=1391, month=1, day=29, hour=10, minute=51, second=36)
20+
21+
@property
22+
def default_date_format(self):
23+
return settings.JALALI_DATE_DEFAULTS["Strftime"]["date"]
24+
25+
@property
26+
def default_datetime_format(self):
27+
return settings.JALALI_DATE_DEFAULTS["Strftime"]["datetime"]
28+
29+
def test_none_g_date(self):
30+
self.assertEqual(to_jalali(g_date=None), "-")
31+
32+
def test_invalid_type_g_date(self):
33+
self.assertEqual(to_jalali(g_date=123), "-")
34+
self.assertEqual(to_jalali(g_date="test-str"), "-")
35+
36+
def test_date_conversion(self):
37+
self.assertEqual(
38+
to_jalali(self.FAKE_DATE),
39+
self.FAKE_DATE_JALALI.strftime(self.default_date_format),
40+
)
41+
42+
def test_date_conversion_with_custom_strftime(self):
43+
custom_strftime = "%Y-%m-%d"
44+
self.assertEqual(
45+
to_jalali(g_date=self.FAKE_DATE, strftime=custom_strftime),
46+
self.FAKE_DATE_JALALI.strftime(custom_strftime),
47+
)
48+
49+
def test_datetime_conversion(self):
50+
self.assertEqual(
51+
to_jalali(self.FAKE_DATETIME),
52+
self.FAKE_DATETIME_JALALI.strftime(self.default_datetime_format),
53+
)
54+
55+
def test_datetime_conversion_with_custom_strftime(self):
56+
custom_strftime = "%H.%M.%S | %Y-%m-%d"
57+
self.assertEqual(
58+
to_jalali(g_date=self.FAKE_DATETIME, strftime=custom_strftime),
59+
self.FAKE_DATETIME_JALALI.strftime(custom_strftime),
60+
)
61+
62+
def test_error_prevention_with_lazy_translatable_strftime(self):
63+
"""
64+
Ensure that using lazy translatable strings as strftime will not raise an error. (#78)
65+
"""
66+
custom_strftime = gettext_lazy("%Y-%m-%d")
67+
self.assertEqual(
68+
to_jalali(g_date=self.FAKE_DATE, strftime=custom_strftime),
69+
self.FAKE_DATE_JALALI.strftime(str(custom_strftime)),
70+
)

0 commit comments

Comments
 (0)