diff --git a/.github/workflows/django-ci.yml b/.github/workflows/django-ci.yml index 27de73d..3706337 100644 --- a/.github/workflows/django-ci.yml +++ b/.github/workflows/django-ci.yml @@ -12,33 +12,33 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12', '3.13'] + python-version: ['3.12', '3.13'] django-version: ['4.2', '5.0', '5.1', '6.0'] - + steps: - uses: actions/checkout@v4 - + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - + - name: Install uv uses: astral-sh/setup-uv@v5 with: enable-cache: true - + - name: Install dependencies run: | uv sync uv pip install "Django==${{ matrix.django-version }}.*" - + - name: Run tests with coverage env: DJANGO_SETTINGS_MODULE: django_nepkit.tests.settings run: | uv run pytest django_nepkit/tests/ --cov=django_nepkit --cov-report=xml --cov-report=term - + - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: @@ -46,7 +46,7 @@ jobs: flags: unittests name: codecov-umbrella fail_ci_if_error: false - + - name: Run linting run: | uv run ruff check django_nepkit/ @@ -55,22 +55,22 @@ jobs: build: runs-on: ubuntu-latest needs: test - + steps: - uses: actions/checkout@v4 - + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.11' - + python-version: '3.12' + - name: Install uv uses: astral-sh/setup-uv@v5 - + - name: Build package run: | uv build - + - name: Check package run: | uv run twine check dist/* diff --git a/django_nepkit/tests/conftest.py b/django_nepkit/tests/conftest.py index b1ce811..a1daa5a 100644 --- a/django_nepkit/tests/conftest.py +++ b/django_nepkit/tests/conftest.py @@ -1,6 +1,7 @@ """ Pytest configuration and shared fixtures for django-nepkit tests. """ + import pytest import django from django.conf import settings @@ -14,6 +15,7 @@ if not settings.configured: def nepali_date_sample(): """Sample nepalidate object for testing.""" from nepali.datetime import nepalidate + return nepalidate(2081, 1, 15) # 2081-01-15 @@ -21,4 +23,5 @@ def nepali_date_sample(): def nepali_datetime_sample(): """Sample nepalidatetime object for testing.""" from nepali.datetime import nepalidatetime + return nepalidatetime(2081, 1, 15, 14, 30, 0) # 2081-01-15 14:30:00 diff --git a/django_nepkit/tests/test_filters.py b/django_nepkit/tests/test_filters.py index 45f185b..89a3acc 100644 --- a/django_nepkit/tests/test_filters.py +++ b/django_nepkit/tests/test_filters.py @@ -1,17 +1,18 @@ """ Tests for django-nepkit filters (DRF and Admin). """ + import pytest -try: - from django_filters import FilterSet +import importlib.util + +FILTERS_AVAILABLE = importlib.util.find_spec("django_filters") is not None + +if FILTERS_AVAILABLE: from django_nepkit.filters import ( NepaliDateYearFilter, NepaliDateMonthFilter, ) - FILTERS_AVAILABLE = True -except ImportError: - FILTERS_AVAILABLE = False @pytest.mark.skipif(not FILTERS_AVAILABLE, reason="django-filter not installed") @@ -21,13 +22,13 @@ class TestNepaliDateYearFilter: def test_filter_initialization(self): """Test that NepaliDateYearFilter can be initialized.""" filter_instance = NepaliDateYearFilter(field_name="birth_date") - + assert filter_instance.field_name == "birth_date" def test_filter_has_correct_type(self): """Test that filter is a NumberFilter.""" from django_filters import NumberFilter - + filter_instance = NepaliDateYearFilter(field_name="birth_date") assert isinstance(filter_instance, NumberFilter) @@ -39,12 +40,12 @@ class TestNepaliDateMonthFilter: def test_filter_initialization(self): """Test that NepaliDateMonthFilter can be initialized.""" filter_instance = NepaliDateMonthFilter(field_name="birth_date") - + assert filter_instance.field_name == "birth_date" def test_filter_has_correct_type(self): """Test that filter is a NumberFilter.""" from django_filters import NumberFilter - + filter_instance = NepaliDateMonthFilter(field_name="birth_date") assert isinstance(filter_instance, NumberFilter) diff --git a/django_nepkit/tests/test_forms.py b/django_nepkit/tests/test_forms.py index 5be0429..f1b1a99 100644 --- a/django_nepkit/tests/test_forms.py +++ b/django_nepkit/tests/test_forms.py @@ -1,8 +1,8 @@ """ Tests for django-nepkit form fields. """ + import pytest -from django import forms from django.core.exceptions import ValidationError from nepali.datetime import nepalidate @@ -15,7 +15,7 @@ class TestNepaliDateFormField: def test_valid_date_input(self): """Test that valid date strings are accepted.""" field = NepaliDateFormField() - + result = field.clean("2081-01-15") assert isinstance(result, nepalidate) assert result.year == 2081 @@ -25,13 +25,13 @@ class TestNepaliDateFormField: def test_multiple_input_formats(self): """Test that multiple date formats are accepted.""" field = NepaliDateFormField() - + formats = [ "2081-01-15", "15/01/2081", "15-01-2081", ] - + for date_str in formats: result = field.clean(date_str) assert isinstance(result, nepalidate) @@ -39,14 +39,14 @@ class TestNepaliDateFormField: def test_empty_value_optional(self): """Test that empty value is accepted for optional fields.""" field = NepaliDateFormField(required=False) - + result = field.clean("") assert result is None def test_empty_value_required(self): """Test that empty value raises error for required fields.""" field = NepaliDateFormField(required=True) - + with pytest.raises(ValidationError): field.clean("") @@ -54,7 +54,7 @@ class TestNepaliDateFormField: def test_invalid_date_format(self): """Test that invalid date format raises ValidationError.""" field = NepaliDateFormField() - + with pytest.raises(ValidationError): field.clean("invalid-date") @@ -62,6 +62,6 @@ class TestNepaliDateFormField: """Test that nepalidate objects are accepted.""" field = NepaliDateFormField() date_obj = nepalidate(2081, 1, 15) - + result = field.clean(date_obj) assert result is date_obj diff --git a/django_nepkit/tests/test_models.py b/django_nepkit/tests/test_models.py index 6b74e34..3e48428 100644 --- a/django_nepkit/tests/test_models.py +++ b/django_nepkit/tests/test_models.py @@ -1,8 +1,8 @@ """ Tests for django-nepkit model fields. """ + import pytest -from django.db import models from django.core.exceptions import ValidationError from nepali.datetime import nepalidate, nepalidatetime @@ -23,7 +23,7 @@ class TestNepaliDateField: """Test that dates are stored as YYYY-MM-DD strings.""" field = NepaliDateField() date_obj = nepalidate(2081, 1, 15) - + # get_prep_value should return string format result = field.get_prep_value(date_obj) assert result == "2081-01-15" @@ -32,7 +32,7 @@ class TestNepaliDateField: def test_field_retrieval_as_object(self, nepali_date_sample): """Test that stored strings are converted to nepalidate objects.""" field = NepaliDateField() - + # from_db_value should return nepalidate object result = field.from_db_value("2081-01-15", None, None) assert isinstance(result, nepalidate) @@ -43,7 +43,7 @@ class TestNepaliDateField: def test_null_handling(self): """Test that null values are handled correctly.""" field = NepaliDateField(null=True, blank=True) - + assert field.get_prep_value(None) is None assert field.from_db_value(None, None, None) is None @@ -67,7 +67,7 @@ class TestNepaliDateTimeField: """Test that datetimes are stored as YYYY-MM-DD HH:MM:SS strings.""" field = NepaliDateTimeField() dt_obj = nepalidatetime(2081, 1, 15, 14, 30, 0) - + result = field.get_prep_value(dt_obj) assert result == "2081-01-15 14:30:00" assert isinstance(result, str) @@ -75,7 +75,7 @@ class TestNepaliDateTimeField: def test_datetime_retrieval(self): """Test that stored strings are converted to nepalidatetime objects.""" field = NepaliDateTimeField() - + result = field.from_db_value("2081-01-15 14:30:00", None, None) assert isinstance(result, nepalidatetime) assert result.year == 2081 @@ -89,13 +89,13 @@ class TestNepaliPhoneNumberField: def test_valid_phone_numbers(self): """Test that valid phone numbers pass validation.""" field = NepaliPhoneNumberField() - + valid_numbers = [ "9841234567", # Mobile "9801234567", # Mobile - "014123456", # Landline + "014123456", # Landline ] - + for number in valid_numbers: # Should not raise ValidationError field.run_validators(number) @@ -104,13 +104,13 @@ class TestNepaliPhoneNumberField: def test_invalid_phone_numbers(self): """Test that invalid phone numbers fail validation.""" field = NepaliPhoneNumberField() - + invalid_numbers = [ - "123456789", # Too short - "12345678901", # Too long + "123456789", # Too short + "12345678901", # Too long "abcdefghij", # Non-numeric ] - + for number in invalid_numbers: with pytest.raises(ValidationError): field.run_validators(number) @@ -122,11 +122,11 @@ class TestLocationFields: def test_province_field_choices(self): """Test that ProvinceField has correct choices.""" field = ProvinceField() - + # Should have choices from nepali.locations.provinces assert field.choices is not None assert len(field.choices) > 0 - + # Check that Koshi Province mapping works choice_names = [choice[0] for choice in field.choices] assert "Koshi Province" in choice_names @@ -134,21 +134,21 @@ class TestLocationFields: def test_district_field_choices(self): """Test that DistrictField has correct choices.""" field = DistrictField() - + assert field.choices is not None assert len(field.choices) > 0 def test_municipality_field_choices(self): """Test that MunicipalityField has correct choices.""" field = MunicipalityField() - + assert field.choices is not None assert len(field.choices) > 0 def test_location_field_language_ne(self): """Test that location fields respect ne=True for Devanagari.""" field = ProvinceField(ne=True) - + assert field.ne is True # Choices should include Devanagari names choice_names = [choice[0] for choice in field.choices] @@ -158,5 +158,5 @@ class TestLocationFields: def test_htmx_configuration(self): """Test that HTMX mode can be enabled.""" field = DistrictField(htmx=True) - + assert field.htmx is True diff --git a/django_nepkit/tests/test_serializers.py b/django_nepkit/tests/test_serializers.py index acbc16d..0fc9f6a 100644 --- a/django_nepkit/tests/test_serializers.py +++ b/django_nepkit/tests/test_serializers.py @@ -1,6 +1,7 @@ """ Tests for django-nepkit DRF serializers. """ + import pytest from nepali.datetime import nepalidate, nepalidatetime @@ -10,6 +11,7 @@ try: NepaliDateSerializerField, NepaliDateTimeSerializerField, ) + DRF_AVAILABLE = True except ImportError: DRF_AVAILABLE = False @@ -23,7 +25,7 @@ class TestNepaliDateSerializerField: """Test serializing a nepalidate object to string.""" field = NepaliDateSerializerField() date_obj = nepalidate(2081, 1, 15) - + result = field.to_representation(date_obj) assert result == "2081-01-15" assert isinstance(result, str) @@ -31,21 +33,21 @@ class TestNepaliDateSerializerField: def test_serialize_string_date(self): """Test serializing a string date.""" field = NepaliDateSerializerField() - + result = field.to_representation("2081-01-15") assert result == "2081-01-15" def test_serialize_none(self): """Test that None is serialized as None.""" field = NepaliDateSerializerField() - + result = field.to_representation(None) assert result is None def test_deserialize_valid_date(self): """Test deserializing a valid date string.""" field = NepaliDateSerializerField() - + result = field.to_internal_value("2081-01-15") assert isinstance(result, nepalidate) assert result.year == 2081 @@ -55,14 +57,14 @@ class TestNepaliDateSerializerField: def test_deserialize_none(self): """Test that None/empty string is deserialized as None.""" field = NepaliDateSerializerField() - + assert field.to_internal_value(None) is None assert field.to_internal_value("") is None def test_deserialize_invalid_raises_error(self): """Test that invalid date raises ValidationError.""" field = NepaliDateSerializerField() - + with pytest.raises(serializers.ValidationError): field.to_internal_value("invalid-date") @@ -70,9 +72,10 @@ class TestNepaliDateSerializerField: """Test Devanagari output when ne=True.""" field = NepaliDateSerializerField(ne=True) date_obj = nepalidate(2081, 1, 15) - + result = field.to_representation(date_obj) # Should use strftime_ne for Devanagari digits + assert result == "२०८१-०१-१५" assert field.ne is True @@ -84,7 +87,7 @@ class TestNepaliDateTimeSerializerField: """Test serializing a nepalidatetime object to string.""" field = NepaliDateTimeSerializerField() dt_obj = nepalidatetime(2081, 1, 15, 14, 30, 0) - + result = field.to_representation(dt_obj) assert result == "2081-01-15 14:30:00" assert isinstance(result, str) @@ -92,7 +95,7 @@ class TestNepaliDateTimeSerializerField: def test_deserialize_valid_datetime(self): """Test deserializing a valid datetime string.""" field = NepaliDateTimeSerializerField() - + result = field.to_internal_value("2081-01-15 14:30:00") assert isinstance(result, nepalidatetime) assert result.year == 2081 @@ -103,6 +106,6 @@ class TestNepaliDateTimeSerializerField: """Test using a custom datetime format.""" field = NepaliDateTimeSerializerField(format="%Y-%m-%d") dt_obj = nepalidatetime(2081, 1, 15, 14, 30, 0) - + result = field.to_representation(dt_obj) assert result == "2081-01-15" diff --git a/django_nepkit/tests/test_utils.py b/django_nepkit/tests/test_utils.py index 7e33c0a..8502e82 100644 --- a/django_nepkit/tests/test_utils.py +++ b/django_nepkit/tests/test_utils.py @@ -1,7 +1,7 @@ """ Tests for django-nepkit utility functions. """ -import pytest + from nepali.datetime import nepalidate, nepalidatetime from django_nepkit.utils import ( @@ -16,7 +16,7 @@ class TestDateParsing: def test_parse_nepali_date_from_string(self): """Test parsing BS date from string.""" result = try_parse_nepali_date("2081-01-15") - + assert result is not None assert isinstance(result, nepalidate) assert result.year == 2081 @@ -30,7 +30,7 @@ class TestDateParsing: "15/01/2081", "15-01-2081", ] - + for date_str in formats: result = try_parse_nepali_date(date_str) assert result is not None @@ -40,7 +40,7 @@ class TestDateParsing: """Test that passing a nepalidate object returns it unchanged.""" date_obj = nepalidate(2081, 1, 15) result = try_parse_nepali_date(date_obj) - + assert result is date_obj def test_parse_nepali_date_none(self): @@ -65,7 +65,7 @@ class TestDateTimeParsing: def test_parse_nepali_datetime_from_string(self): """Test parsing BS datetime from string.""" result = try_parse_nepali_datetime("2081-01-15 14:30:00") - + assert result is not None assert isinstance(result, nepalidatetime) assert result.year == 2081 @@ -78,7 +78,7 @@ class TestDateTimeParsing: """Test that passing a nepalidatetime object returns it unchanged.""" dt_obj = nepalidatetime(2081, 1, 15, 14, 30, 0) result = try_parse_nepali_datetime(dt_obj) - + assert result is dt_obj def test_parse_nepali_datetime_none(self): diff --git a/django_nepkit/tests/test_validators.py b/django_nepkit/tests/test_validators.py index af26e4f..8d7a933 100644 --- a/django_nepkit/tests/test_validators.py +++ b/django_nepkit/tests/test_validators.py @@ -1,6 +1,7 @@ """ Tests for django-nepkit validators. """ + import pytest from django.core.exceptions import ValidationError @@ -19,7 +20,7 @@ class TestPhoneNumberValidator: "9861234567", "9741234567", ] - + for number in valid_mobiles: # Should not raise ValidationError validate_nepali_phone_number(number) @@ -31,7 +32,7 @@ class TestPhoneNumberValidator: "015123456", "016123456", ] - + for number in valid_landlines: # Should not raise ValidationError validate_nepali_phone_number(number) diff --git a/django_nepkit/tests/test_widgets.py b/django_nepkit/tests/test_widgets.py index 91a1ab1..e98afc2 100644 --- a/django_nepkit/tests/test_widgets.py +++ b/django_nepkit/tests/test_widgets.py @@ -1,8 +1,8 @@ """ Tests for django-nepkit widgets. """ + import pytest -from django.forms import Form from django_nepkit.widgets import ( NepaliDatePickerWidget, @@ -20,16 +20,16 @@ class TestNepaliDatePickerWidget: """Test that widget renders with nepkit-datepicker class.""" widget = NepaliDatePickerWidget() html = widget.render("birth_date", None) - - assert 'class=' in html - assert 'nepkit-datepicker' in html + + assert "class=" in html + assert "nepkit-datepicker" in html @pytest.mark.xfail(reason="Django app registry timing issue in test environment") def test_widget_language_attribute_en(self): """Test that widget renders data-en attribute when en=True.""" widget = NepaliDatePickerWidget(en=True) html = widget.render("birth_date", None) - + assert 'data-en="true"' in html @pytest.mark.xfail(reason="Django app registry timing issue in test environment") @@ -37,7 +37,7 @@ class TestNepaliDatePickerWidget: """Test that widget renders data-ne attribute when ne=True.""" widget = NepaliDatePickerWidget(ne=True) html = widget.render("birth_date", None) - + assert 'data-ne="true"' in html @pytest.mark.xfail(reason="Django app registry timing issue in test environment") @@ -45,15 +45,15 @@ class TestNepaliDatePickerWidget: """Test that widget renders data-format attribute.""" widget = NepaliDatePickerWidget() html = widget.render("birth_date", None) - - assert 'data-format=' in html + + assert "data-format=" in html @pytest.mark.xfail(reason="Django app registry timing issue in test environment") def test_widget_autocomplete_off(self): """Test that widget has autocomplete=off.""" widget = NepaliDatePickerWidget() html = widget.render("birth_date", None) - + assert 'autocomplete="off"' in html @pytest.mark.xfail(reason="Django app registry timing issue in test environment") @@ -61,8 +61,8 @@ class TestNepaliDatePickerWidget: """Test that widget has placeholder.""" widget = NepaliDatePickerWidget() html = widget.render("birth_date", None) - - assert 'placeholder=' in html + + assert "placeholder=" in html class TestLocationWidgets: @@ -73,8 +73,8 @@ class TestLocationWidgets: """Test that ProvinceSelectWidget renders correctly.""" widget = ProvinceSelectWidget() html = widget.render("province", None) - - assert '=4.2", "django-filter>=25.2", @@ -40,10 +40,9 @@ classifiers = [ "Framework :: Django", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", "Framework :: Django :: 6.0", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License",