diff --git a/.coveragerc b/.coveragerc index e9267ea..19109ab 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,4 @@ [run] include = edc_locator/* omit = edc_locator/tests/*,edc_locator/migrations/* -branch = 1 \ No newline at end of file +branch = 1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59f4d55..950e9aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,20 +5,17 @@ on: [push, pull_request] jobs: build: - name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) + name: | + build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}, ${{ matrix.database-engine }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ['3.11', '3.12'] - django-version: ['4.2', '5.0', 'dev'] + python-version: ['3.12', '3.13'] + django-version: ['5.1', 'dev'] + database-engine: ["mysql", "postgres"] - exclude: - - python-version: '3.12' - django-version: '4.2' - - python-version: '3.11' - django-version: 'dev' services: mysql: image: mysql:latest @@ -28,6 +25,17 @@ jobs: ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + postgres: + image: postgres:latest + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: - name: Install pycups and words dependency @@ -61,7 +69,6 @@ jobs: python -m pip install --upgrade pip python -m pip install -r https://raw.githubusercontent.com/clinicedc/edc/develop/requirements.tests/tox.txt - - name: Tox tests run: | tox -v diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3df1e6b..5825210 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,20 +3,20 @@ exclude: tests/etc/user-* repos: - repo: https://github.com/PyCQA/bandit - rev: 1.7.7 + rev: 1.8.2 hooks: - id: bandit args: - "-x *test*.py" - repo: https://github.com/psf/black - rev: 24.2.0 + rev: 24.10.0 hooks: - id: black - language_version: python3.11 + language_version: python3.12 - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.1 hooks: - id: flake8 args: @@ -28,22 +28,24 @@ repos: - id: isort - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - - id: requirements-txt-fixer - files: requirements/.*\.txt$ - - id: trailing-whitespace - id: check-added-large-files - - id: fix-byte-order-marker - id: check-docstring-first - id: check-executables-have-shebangs - id: check-merge-conflict + - id: check-toml + - id: check-yaml - id: debug-statements - id: detect-private-key - - id: check-toml + - id: end-of-file-fixer + - id: fix-byte-order-marker + - id: requirements-txt-fixer + files: requirements/.*\.txt$ + - id: trailing-whitespace - repo: https://github.com/adrienverge/yamllint - rev: v1.34.0 + rev: v1.35.1 hooks: - id: yamllint args: diff --git a/LICENSE b/LICENSE index 8cdb845..23cb790 100644 --- a/LICENSE +++ b/LICENSE @@ -337,4 +337,3 @@ proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. - diff --git a/edc_locator/model_mixins/subject_contact_fields_mixin.py b/edc_locator/model_mixins/subject_contact_fields_mixin.py index 43cb294..c4f76e8 100644 --- a/edc_locator/model_mixins/subject_contact_fields_mixin.py +++ b/edc_locator/model_mixins/subject_contact_fields_mixin.py @@ -10,8 +10,9 @@ class SubjectContactFieldsMixin(models.Model): max_length=25, choices=YES_NO, verbose_name=format_html( - "Has the participant given permission to contacted by telephone " - "or cell by study staff for follow-up purposes during the study?" + "Has the participant given permission {} " + "by study staff for follow-up purposes during the study?", + "to contacted by telephone or cell", ), ) @@ -20,7 +21,8 @@ class SubjectContactFieldsMixin(models.Model): choices=YES_NO, verbose_name=format_html( "Has the participant given permission for study " - "staff to make home visits for follow-up purposes?" + "staff {} for follow-up purposes?", + "to make home visits", ), ) @@ -30,8 +32,9 @@ class SubjectContactFieldsMixin(models.Model): null=True, blank=False, verbose_name=format_html( - "Has the participant given permission to be contacted by SMS " - "by study staff for follow-up purposes during the study?" + "Has the participant given permission {} " + "by study staff for follow-up purposes during the study?", + "to be contacted by SMS", ), ) diff --git a/edc_locator/model_mixins/subject_indirect_contact_fields_mixin.py b/edc_locator/model_mixins/subject_indirect_contact_fields_mixin.py index 1ebd38c..ab261d7 100644 --- a/edc_locator/model_mixins/subject_indirect_contact_fields_mixin.py +++ b/edc_locator/model_mixins/subject_indirect_contact_fields_mixin.py @@ -10,8 +10,10 @@ class SubjectIndirectContactFieldsMixin(models.Model): max_length=25, choices=YES_NO, verbose_name=format_html( - "Has the participant given permission for study staff " - "to contact anyone else for follow-up purposes during the study?" + "{} {} {}?", + "Has the participant given permission for study staff", + "to contact anyone else", + "for follow-up purposes during the study", ), help_text="For example a partner, spouse, family member, neighbour ...", ) diff --git a/edc_locator/model_mixins/subject_work_fields_mixin.py b/edc_locator/model_mixins/subject_work_fields_mixin.py index 9609836..f73aa46 100644 --- a/edc_locator/model_mixins/subject_work_fields_mixin.py +++ b/edc_locator/model_mixins/subject_work_fields_mixin.py @@ -10,9 +10,10 @@ class SubjectWorkFieldsMixin(models.Model): max_length=25, choices=YES_NO, verbose_name=format_html( - "Has the participant given permission to be contacted " - "at work, by telephone or cell, by study staff for follow-up " - "purposes during the study?" + "{} {}, {} ", + "Has the participant given permission to be contacted", + "at work", + "by telephone or cell, by study staff for follow-up purposes during the study?", ), ) diff --git a/edc_locator/modeladmin_mixins.py b/edc_locator/modeladmin_mixins.py index d770ebf..a806688 100644 --- a/edc_locator/modeladmin_mixins.py +++ b/edc_locator/modeladmin_mixins.py @@ -76,7 +76,7 @@ def subject(self, obj): age_in_years=age(born=consent.dob, reference_dt=get_utcnow()).years, initials=consent.initials, ) - return render_to_string("changelist_locator_subject.html", context=context) + return render_to_string("edc_locator/changelist_locator_subject.html", context=context) @admin.display(description="Contact Rules", ordering="may_call") def contact_rules(self, obj): @@ -89,7 +89,9 @@ def contact_rules(self, obj): YES=YES, NO=NO, ) - return render_to_string("changelist_locator_contact_rules.html", context=context) + return render_to_string( + "edc_locator/changelist_locator_contact_rules.html", context=context + ) @admin.display(description="Contacts") def contacts(self, obj): @@ -99,4 +101,6 @@ def contacts(self, obj): subject_phone=obj.subject_phone, subject_phone_alt=obj.subject_phone_alt, ) - return render_to_string("changelist_locator_contacts.html", context=context) + return render_to_string( + "edc_locator/changelist_locator_contacts.html", context=context + ) diff --git a/edc_locator/templates/changelist_locator_contact_rules.html b/edc_locator/templates/edc_locator/changelist_locator_contact_rules.html similarity index 100% rename from edc_locator/templates/changelist_locator_contact_rules.html rename to edc_locator/templates/edc_locator/changelist_locator_contact_rules.html diff --git a/edc_locator/templates/changelist_locator_contacts.html b/edc_locator/templates/edc_locator/changelist_locator_contacts.html similarity index 100% rename from edc_locator/templates/changelist_locator_contacts.html rename to edc_locator/templates/edc_locator/changelist_locator_contacts.html diff --git a/edc_locator/templates/changelist_locator_subject.html b/edc_locator/templates/edc_locator/changelist_locator_subject.html similarity index 100% rename from edc_locator/templates/changelist_locator_subject.html rename to edc_locator/templates/edc_locator/changelist_locator_subject.html diff --git a/edc_locator/templates/bootstrap3/locator_include.html b/edc_locator/templates/edc_locator/locator_include.html similarity index 100% rename from edc_locator/templates/bootstrap3/locator_include.html rename to edc_locator/templates/edc_locator/locator_include.html diff --git a/edc_locator/tests/holidays.csv b/edc_locator/tests/holidays.csv index 8114d09..6cad2f1 100644 --- a/edc_locator/tests/holidays.csv +++ b/edc_locator/tests/holidays.csv @@ -12,4 +12,4 @@ local_date,label,country 2017-10-02,Public Holiday,botswana 2017-12-25,Christmas Day,botswana 2017-12-26,Boxing Day,botswana -2017-01-01,New Year,botswana \ No newline at end of file +2017-01-01,New Year,botswana diff --git a/edc_locator/tests/test_settings.py b/edc_locator/tests/test_settings.py new file mode 100644 index 0000000..3d45e7d --- /dev/null +++ b/edc_locator/tests/test_settings.py @@ -0,0 +1,56 @@ +import sys +from pathlib import Path + +from edc_test_settings.default_test_settings import DefaultTestSettings + +app_name = "edc_locator" +base_dir = Path(__file__).parent.parent.parent +print(base_dir) + +project_settings = DefaultTestSettings( + calling_file=__file__, + template_dirs=[str(base_dir / app_name / "tests" / "templates")], + BASE_DIR=base_dir, + APP_NAME=app_name, + ETC_DIR=str(base_dir / app_name / "tests" / "etc"), + SILENCED_SYSTEM_CHECKS=[ + "sites.E101", + "edc_navbar.E002", + "edc_navbar.E003", + "edc_consent.E001", + ], + SUBJECT_VISIT_MODEL="edc_visit_tracking.subjectvisit", + EDC_SITES_REGISTER_DEFAULT=True, + INSTALLED_APPS=[ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "simple_history", + "django_crypto_fields.apps.AppConfig", + "edc_action_item.apps.AppConfig", + "edc_appointment.apps.AppConfig", + "edc_auth.apps.AppConfig", + "edc_data_manager.apps.AppConfig", + "edc_device.apps.AppConfig", + "edc_form_runners.apps.AppConfig", + "edc_identifier.apps.AppConfig", + "edc_lab.apps.AppConfig", + "edc_label.apps.AppConfig", + "edc_locator.apps.AppConfig", + "edc_metadata.apps.AppConfig", + "edc_notification.apps.AppConfig", + "edc_registration.apps.AppConfig", + "edc_sites.apps.AppConfig", + "edc_visit_schedule.apps.AppConfig", + "edc_visit_tracking.apps.AppConfig", + "edc_appconfig.apps.AppConfig", + ], + add_dashboard_middleware=True, +).settings + +for k, v in project_settings.items(): + setattr(sys.modules[__name__], k, v) diff --git a/pyproject.toml b/pyproject.toml index d2bf665..319e212 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,18 @@ [build-system] requires = ["setuptools>=60", "setuptools-scm>=8.0"] +build-backend = "setuptools.build_meta" [tool.setuptools_scm] -version_file="_version.py" +version_file = "_version.py" [tool.black] line-length = 95 -target-version = ["py311"] +target-version = ["py312"] extend-exclude = '''^(.*\/)*\b(migrations)\b($|\/.*$)''' [tool.isort] profile = "black" -py_version = "311" +py_version = "312" skip = [".tox", ".eggs", "migrations"] [tool.coverage.run] @@ -35,35 +36,33 @@ exclude_lines = [ legacy_tox_ini = """ [tox] envlist = - py{311}-dj{42,50}, - py{312}-dj{50,dev}, + py{312,313}-dj{51,dev}, lint + pre-commit isolated_build = true [gh-actions] python = - 3.11: py311 - 3.12: py312, lint + 3.12: py312, lint, pre-commit + 3.13: py313 [gh-actions:env] DJANGO = - 4.2: dj42 - 5.0: dj50 - dev: djdev, lint + 5.1: dj51 + dev: djdev, lint, pre-commit [testenv] deps = -r https://raw.githubusercontent.com/clinicedc/edc/develop/requirements.tests/tox.txt -r https://raw.githubusercontent.com/clinicedc/edc/develop/requirements.tests/test_utils.txt -r https://raw.githubusercontent.com/clinicedc/edc/develop/requirements.tests/edc.txt - -r https://raw.githubusercontent.com/clinicedc/edc/develop/requirements.tests/third_party_dev.txt - dj42: Django>=4.2,<5.0 - dj50: Django>=5.0 + dj51: Django>=5.1,<5.2 djdev: https://github.com/django/django/tarball/main commands = - pip install -U pip coverage[toml] + pip install -U pip + python --version pip --version pip freeze coverage run -a runtests.py @@ -72,7 +71,19 @@ commands = [testenv:lint] deps = -r https://raw.githubusercontent.com/clinicedc/edc/develop/requirements.tests/lint.txt commands = + python --version + pip --version + pip freeze isort --profile=black --check --diff . black --check --diff . flake8 . + +[testenv:pre-commit] +deps = pre-commit +commands = + python --version + pip --version + pip freeze + pre-commit autoupdate + pre-commit run --all-files """ diff --git a/runtests.py b/runtests.py index bc822ba..1a2d24d 100644 --- a/runtests.py +++ b/runtests.py @@ -1,62 +1,5 @@ #!/usr/bin/env python -import logging -from pathlib import Path - -from edc_test_utils import DefaultTestSettings, func_main - -app_name = "edc_locator" -base_dir = Path(__file__).absolute().parent - -project_settings = DefaultTestSettings( - calling_file=__file__, - template_dirs=[str(base_dir / app_name / "tests" / "templates")], - BASE_DIR=base_dir, - APP_NAME=app_name, - ETC_DIR=str(base_dir / app_name / "tests" / "etc"), - SILENCED_SYSTEM_CHECKS=[ - "sites.E101", - "edc_navbar.E002", - "edc_navbar.E003", - "edc_consent.E001", - ], - SUBJECT_VISIT_MODEL="edc_visit_tracking.subjectvisit", - EDC_SITES_REGISTER_DEFAULT=True, - INSTALLED_APPS=[ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "django.contrib.sites", - "simple_history", - "django_crypto_fields.apps.AppConfig", - "edc_action_item.apps.AppConfig", - "edc_appointment.apps.AppConfig", - "edc_auth.apps.AppConfig", - "edc_data_manager.apps.AppConfig", - "edc_device.apps.AppConfig", - "edc_form_runners.apps.AppConfig", - "edc_identifier.apps.AppConfig", - "edc_lab.apps.AppConfig", - "edc_label.apps.AppConfig", - "edc_locator.apps.AppConfig", - "edc_metadata.apps.AppConfig", - "edc_notification.apps.AppConfig", - "edc_registration.apps.AppConfig", - "edc_sites.apps.AppConfig", - "edc_visit_schedule.apps.AppConfig", - "edc_visit_tracking.apps.AppConfig", - "edc_appconfig.apps.AppConfig", - ], - add_dashboard_middleware=True, -).settings - - -def main(): - func_main(project_settings, *[f"{app_name}.tests"]) - +from edc_test_settings.func_main import func_main2 if __name__ == "__main__": - logging.basicConfig() - main() + func_main2("edc_locator.tests.test_settings", "edc_locator.tests") diff --git a/setup.cfg b/setup.cfg index 8efccb1..e8f58e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,16 +12,15 @@ keywords = django Edc participant locator, CRF, clinicedc, clinical trials classifiers= Environment :: Web Environment Framework :: Django - Framework :: Django :: 4.2 + Framework :: Django :: 5.1 Intended Audience :: Developers Intended Audience :: Science/Research Operating System :: OS Independent - Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 License :: OSI Approved :: GNU General Public License v3 (GPLv3) [options] -python_requires = >=3.11 +python_requires = >=3.12 zip_safe = False include_package_data = True packages = find: