diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e6779..2d49e3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v2.0.1] - 2024-02-23 + +- Updated `coverage` config +- Moved `scheduler` into its own app, rather than being part of the `core` app +- Fixed deprecated `ping` route import in `urls.py` + ## [v2.0.0] - 2024-02-18 - **Deploy:** diff --git a/README.md b/README.md index 1bebb88..c474ba0 100644 --- a/README.md +++ b/README.md @@ -34,14 +34,15 @@ with Django+DRF and React, and comes with many features included. **A fully working a customizable Django/DRF:** - Views: - - Basic Django views for good measures: `ping`, `robots.txt` + - Basic Django views for good measures: `robots.txt` - A default/catch-all route that serves the React frontend (`index` view) - Authentication API (`login`, `logout`, `check`) - Current user API (`get`, `update`, `update_password`) + - Health check API (`api`, `db`, `scheduler`) - Others (`config`) - A swagger API documentation - Database: - - Flexible database management (SQLite or PostgreSQL) + - Flexible database management (SQLite or PostgreSQL with postgis) - A custom user model following best practices - QA: - Configuration files for `isort`, `flake8`, `black`, and `mypy` diff --git a/backend/.coveragerc b/backend/.coveragerc index 31c9363..833d4d2 100644 --- a/backend/.coveragerc +++ b/backend/.coveragerc @@ -1,14 +1,18 @@ [run] omit = - # Django + # [Django] Server manage.py - django_react_starter/* + django_react_starter/settings/development.py + django_react_starter/settings/production.py + django_react_starter/asgi.py + django_react_starter/wsgi.py + # [Django] Scheduler + scheduler/management/** + # [Django] Common files */__init__.py */migrations/* */tests/* */admin.py - # Scheduler - *scheduler.py # Others wait_for_db.py diff --git a/backend/.isort.cfg b/backend/.isort.cfg index 25f1321..0d9e8e3 100644 --- a/backend/.isort.cfg +++ b/backend/.isort.cfg @@ -11,7 +11,7 @@ skip_glob = src/* # Structure known_django = django,django_apscheduler,django_filters,drf_spectacular,rest_framework,dj_database_url,django_prometheus -known_application = django_react_starter,core,user,health +known_application = django_react_starter,core,user,health,scheduler default_section = THIRDPARTY # Sections diff --git a/backend/core/tests/factories.py b/backend/core/tests/factories.py deleted file mode 100644 index b7d682f..0000000 --- a/backend/core/tests/factories.py +++ /dev/null @@ -1,33 +0,0 @@ -# Built-in -from datetime import timedelta - -# Third-party -import factory - -# Django -from django.utils import timezone -from django_apscheduler.models import DjangoJob, DjangoJobExecution - - -class SchedulerJobFactory(factory.django.DjangoModelFactory): - class Meta: - model = DjangoJob - - id = factory.Sequence(lambda n: f"job_id_{n}") - next_run_time = None - job_state = factory.Sequence(lambda n: bytes(f"job_state_{n}", "utf-8")) - - -class SchedulerJobExecutionFactory(factory.django.DjangoModelFactory): - class Meta: - model = DjangoJobExecution - - job = factory.SubFactory(SchedulerJobFactory) - status = DjangoJobExecution.SUCCESS - run_time = factory.Sequence(lambda n: timezone.now() + timedelta(seconds=n)) - duration = 1.00 - finished = factory.Sequence( - lambda n: (timezone.now() + timedelta(seconds=n + 1)).timestamp() - ) - exception = "" - traceback = "" diff --git a/backend/django_react_starter/settings/base.py b/backend/django_react_starter/settings/base.py index f84b192..a303ea7 100755 --- a/backend/django_react_starter/settings/base.py +++ b/backend/django_react_starter/settings/base.py @@ -35,6 +35,7 @@ # Custom "core", "health", + "scheduler", "user", # Other third party "django_cleanup.apps.CleanupConfig", diff --git a/backend/django_react_starter/urls.py b/backend/django_react_starter/urls.py index 11ca335..e49fe8e 100644 --- a/backend/django_react_starter/urls.py +++ b/backend/django_react_starter/urls.py @@ -8,7 +8,7 @@ from rest_framework import routers # Application -from core.views import AppViewSet, index, ping, robots_txt +from core.views import AppViewSet, index, robots_txt from health.views import HealthViewSet from user.views import AuthViewSet, CurrentUserViewSet @@ -25,7 +25,6 @@ # URLs urlpatterns = [ # Django - path("ping/", ping), path("robots.txt/", robots_txt), path("admin/", admin.site.urls), *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), diff --git a/backend/health/tests.py b/backend/health/tests.py index 08a672c..ba41d62 100644 --- a/backend/health/tests.py +++ b/backend/health/tests.py @@ -7,8 +7,8 @@ from rest_framework.reverse import reverse # Application -from core import scheduler from core.tests import BaseActionTestCase +from scheduler import scheduler class HealthViewSetTestCase(BaseActionTestCase): diff --git a/backend/health/views.py b/backend/health/views.py index 05e29cd..4e2d2de 100644 --- a/backend/health/views.py +++ b/backend/health/views.py @@ -11,7 +11,7 @@ from rest_framework.status import HTTP_200_OK, HTTP_500_INTERNAL_SERVER_ERROR # Application -from core import scheduler +from scheduler import scheduler User = get_user_model() @@ -22,10 +22,7 @@ class HealthViewSet(ImprovedViewSet): @action(detail=False, methods=["get"]) def api(self, _request: Request) -> Response: - try: - return Response(status=HTTP_200_OK, data="API up") - except Exception: - return Response(status=HTTP_500_INTERNAL_SERVER_ERROR, data="API down") + return Response(status=HTTP_200_OK, data="API up") @action(detail=False, methods=["get"]) def database(self, _request: Request) -> Response: diff --git a/backend/scheduler/__init__.py b/backend/scheduler/__init__.py new file mode 100644 index 0000000..0a21583 --- /dev/null +++ b/backend/scheduler/__init__.py @@ -0,0 +1 @@ +default_app_config = "scheduler.apps.SchedulerConfig" diff --git a/backend/scheduler/apps.py b/backend/scheduler/apps.py new file mode 100644 index 0000000..ab49328 --- /dev/null +++ b/backend/scheduler/apps.py @@ -0,0 +1,6 @@ +# Django +from django.apps import AppConfig + + +class SchedulerConfig(AppConfig): + name = "scheduler" diff --git a/backend/scheduler/management/__init__.py b/backend/scheduler/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/scheduler/management/commands/__init__.py b/backend/scheduler/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/core/management/commands/startscheduler.py b/backend/scheduler/management/commands/startscheduler.py similarity index 78% rename from backend/core/management/commands/startscheduler.py rename to backend/scheduler/management/commands/startscheduler.py index a0a8ea6..c1d8bfe 100644 --- a/backend/core/management/commands/startscheduler.py +++ b/backend/scheduler/management/commands/startscheduler.py @@ -14,15 +14,14 @@ class Command(BaseCommand): def handle(self, *args: Any, **options: Any) -> None: # Application - from core.scheduler import IS_RUNNING_CACHE_KEY, scheduler + from scheduler.scheduler import IS_RUNNING_CACHE_KEY, blocking_scheduler try: - cache.set(IS_RUNNING_CACHE_KEY, False) LOGGER.info("[core] Starting scheduler...") cache.set(IS_RUNNING_CACHE_KEY, True) - scheduler.start() + blocking_scheduler.start() except KeyboardInterrupt: LOGGER.info("[core] Stopping scheduler...") cache.set(IS_RUNNING_CACHE_KEY, False) - scheduler.shutdown() + blocking_scheduler.shutdown() LOGGER.info("[core] Scheduler shut down successfully!") diff --git a/backend/core/management/commands/stopscheduler.py b/backend/scheduler/management/commands/stopscheduler.py similarity index 74% rename from backend/core/management/commands/stopscheduler.py rename to backend/scheduler/management/commands/stopscheduler.py index 2ba206e..8306377 100644 --- a/backend/core/management/commands/stopscheduler.py +++ b/backend/scheduler/management/commands/stopscheduler.py @@ -6,6 +6,7 @@ from apscheduler.schedulers import SchedulerNotRunningError # Django +from django.core.cache import cache from django.core.management import BaseCommand LOGGER = logging.getLogger("default") @@ -16,11 +17,12 @@ class Command(BaseCommand): def handle(self, *args: Any, **options: Any) -> None: # Application - from core.scheduler import scheduler + from scheduler.scheduler import IS_RUNNING_CACHE_KEY, blocking_scheduler try: + cache.set(IS_RUNNING_CACHE_KEY, False) LOGGER.info("[core] Stopping scheduler...") - scheduler.shutdown() + blocking_scheduler.shutdown() LOGGER.info("[core] Scheduler shut down successfully!") except SchedulerNotRunningError: LOGGER.error("[core] No scheduler currently running...") diff --git a/backend/core/scheduler.py b/backend/scheduler/scheduler.py similarity index 95% rename from backend/core/scheduler.py rename to backend/scheduler/scheduler.py index 89e9d43..694a22c 100644 --- a/backend/core/scheduler.py +++ b/backend/scheduler/scheduler.py @@ -20,7 +20,7 @@ IS_RUNNING_CACHE_KEY = "scheduler_is_running" # Create scheduler and register jobs -scheduler = BlockingScheduler( +blocking_scheduler = BlockingScheduler( jobstores={"default": DjangoJobStore()}, executors={"default": ThreadPoolExecutor(10)}, timezone=timezone.utc, diff --git a/docker-compose.yml b/docker-compose.yml index b4242b2..be5e3e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,10 +35,6 @@ services: image: postgis/postgis:16-3.4-alpine env_file: - ./backend/.env - environment: - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} networks: - django_react_starter_network ports: