diff --git a/config/settings/base.py b/config/settings/base.py index 11cf99c..c45c72c 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -3,7 +3,6 @@ generated with djinit Common settings shared between development and production environment """ -import os from datetime import timedelta from pathlib import Path @@ -24,11 +23,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent THIRD_PARTY_APPS = [ "rest_framework", "rest_framework_simplejwt", - "rest_framework_simplejwt.token_blacklist", "corsheaders", - "drf_yasg", + "drf_spectacular", "django_tailwind_cli", "django_htmx", + "import_export", + "django_filters", ] USER_DEFINED_APPS = [ @@ -71,13 +71,14 @@ ROOT_URLCONF = "config.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", + "src.config.context_processors.site_stats", ], }, }, @@ -86,6 +87,8 @@ TEMPLATES = [ WSGI_APPLICATION = "config.wsgi.application" +AUTH_USER_MODEL = "accounts.User" + # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators @@ -104,12 +107,14 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +LOGIN_URL = "/login/" +LOGIN_REDIRECT_URL = "/dashboard/" # Internationalization # https://docs.djangoproject.com/en/5.2/topics/i18n/ LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" +TIME_ZONE = "Asia/Kathmandu" USE_I18N = True USE_TZ = True @@ -133,29 +138,45 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # REST Framework Settings REST_FRAMEWORK = { - "DEFAULT_RENDERER_CLASSES": [ - "rest_framework.renderers.JSONRenderer", - ], - "DEFAULT_PARSER_CLASSES": [ - "rest_framework.parsers.JSONParser", - ], "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework_simplejwt.authentication.JWTAuthentication", "rest_framework.authentication.SessionAuthentication", ), - "DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema", + "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", - "PAGE_SIZE": 20, + "PAGE_SIZE": 50, + "DEFAULT_FILTER_BACKENDS": ( + "django_filters.rest_framework.DjangoFilterBackend", + "rest_framework.filters.SearchFilter", + "rest_framework.filters.OrderingFilter", + ), + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } -# JWT Settings +# SimpleJWT SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(days=1), - "REFRESH_TOKEN_LIFETIME": timedelta(days=3), + "ACCESS_TOKEN_LIFETIME": timedelta(hours=1), + "REFRESH_TOKEN_LIFETIME": timedelta(days=30), "ROTATE_REFRESH_TOKENS": True, - "BLACKLIST_AFTER_ROTATION": True, + "AUTH_HEADER_TYPES": ("Bearer",), } + +# Google OAuth +GOOGLE_CLIENT_ID = env("GOOGLE_CLIENT_ID", default="") +GOOGLE_CLIENT_SECRET = env("GOOGLE_CLIENT_SECRET", default="") +GOOGLE_REDIRECT_URI = env( + "GOOGLE_REDIRECT_URI", default="http://localhost:8000/api/auth/google/callback/" +) + +# Spectacular +SPECTACULAR_SETTINGS = { + "TITLE": "Project Management API", + "DESCRIPTION": "Django port of Project Management", + "VERSION": "1.0.0", +} + + # CORS Settings CORS_ALLOW_METHODS = [ "DELETE", diff --git a/config/urls.py b/config/urls.py index 1047589..c9aaeb3 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,58 +1,232 @@ -"""generated with djinit""" - -from django.contrib import admin -from django.urls import path, re_path from django.conf import settings -from django.views.static import serve -from django.urls import include -from drf_yasg.views import get_schema_view -from drf_yasg import openapi -from rest_framework_simplejwt.views import ( - TokenObtainPairView, - TokenRefreshView, - TokenBlacklistView, -) +from django.conf.urls.static import static +from django.contrib import admin +from django.contrib.auth import views as auth_views +from django.contrib.auth.decorators import login_required +from django.urls import include, path +from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView + +from src.config import board_views, list_views, page_views, views + +api_patterns = [ + path("auth/", include("src.accounts.urls")), + path("projects/", include("src.projects.urls")), + path("tickets/", include("src.tickets.urls")), + path("epics/", include("src.epics.urls")), + path("notes/", include("src.notes.urls")), + path("notifications/", include("src.notifications.urls")), + path("external/", include("src.external.urls")), +] + +login_req = login_required +dashboard_patterns = [ + path("", login_req(views.dashboard), name="dashboard"), + path("stats-cards/", login_req(views.stats_cards), name="stats-cards"), + path( + "charts/tickets-per-project/", + login_req(views.tickets_per_project_chart), + name="chart-tickets-per-project", + ), + path( + "charts/user-statistics-data/", + login_req(views.user_statistics_data), + name="chart-user-statistics-data", + ), + path( + "charts/monthly-trend/", + login_req(views.monthly_trend_chart), + name="chart-monthly-trend", + ), + path("recent-activity/", login_req(views.recent_activity), name="recent-activity"), + path( + "project-timeline-widget/", + login_req(views.project_timeline_widget), + name="project-timeline-widget", + ), + path( + "notifications/unread-count/", + login_req(list_views.notifications_unread_count), + name="notif-unread-count", + ), + path( + "notifications/dropdown/", + login_req(page_views.notifications_dropdown), + name="notifications-dropdown", + ), + path( + "notifications/", + login_req(page_views.notifications_list), + name="notifications-list", + ), + path( + "notifications/mark-all-read/", + login_req(page_views.notification_mark_all_read), + name="notifications-mark-all-read", + ), + path( + "notifications//mark-read/", + login_req(page_views.notification_mark_read), + name="notification-mark-read", + ), + path("projects/", login_req(list_views.projects_list), name="projects-list"), + path( + "project-timeline/", + login_req(page_views.project_timeline), + name="project-timeline", + ), + path("project-board/", login_req(board_views.project_board), name="project-board"), + path( + "project-board//", + login_req(board_views.project_board), + name="project-board-project", + ), + path( + "board//data/", + login_req(board_views.board_data), + name="board-data", + ), + path( + "tickets/bulk/status/", + login_req(page_views.tickets_bulk_status), + name="tickets-bulk-status", + ), + path( + "tickets/bulk/priority/", + login_req(page_views.tickets_bulk_priority), + name="tickets-bulk-priority", + ), + path( + "tickets/bulk/assign/", + login_req(page_views.tickets_bulk_assign), + name="tickets-bulk-assign", + ), + path( + "tickets/bulk/epic/", + login_req(page_views.tickets_bulk_epic), + name="tickets-bulk-epic", + ), + path( + "tickets//", + login_req(page_views.ticket_detail), + name="ticket-detail", + ), + path( + "tickets//description/", + login_req(page_views.ticket_update_description), + name="ticket-update-description", + ), + path( + "tickets//field/", + login_req(page_views.ticket_update_field), + name="ticket-update-field", + ), + path( + "tickets//comments/add/", + login_req(page_views.ticket_comment_add), + name="ticket-comment-add", + ), + path( + "tickets/export/", login_req(page_views.tickets_export), name="tickets-export" + ), + path( + "tickets/import/", login_req(page_views.tickets_import), name="tickets-import" + ), + path("tickets/", login_req(list_views.tickets_list), name="tickets-list"), + path( + "ticket-timeline/", + login_req(page_views.ticket_timeline), + name="ticket-timeline", + ), + path( + "ticket-timeline//", + login_req(page_views.ticket_timeline), + name="ticket-timeline-project", + ), + path("epics/", login_req(page_views.epics_overview), name="epics-overview"), + path( + "epics//", + login_req(page_views.epics_overview), + name="epics-overview-project", + ), + path("leaderboard/", login_req(page_views.leaderboard), name="leaderboard"), + path( + "contributions/", login_req(page_views.user_contributions), name="contributions" + ), + path( + "ticket-priorities/", + login_req(page_views.ticket_priorities), + name="ticket-priorities", + ), + path( + "ticket-priorities/add/", + login_req(page_views.ticket_priority_add), + name="ticket-priority-add", + ), + path( + "ticket-priorities//edit/", + login_req(page_views.ticket_priority_edit), + name="ticket-priority-edit", + ), + path( + "ticket-priorities//delete/", + login_req(page_views.ticket_priority_delete), + name="ticket-priority-delete", + ), + path("users/", login_req(page_views.users_list), name="users-list"), + path("settings/ui/", login_req(page_views.ui_settings), name="ui-settings"), + path( + "external/", + login_req(page_views.external_access_list), + name="external-access-list", + ), + path( + "external/create//", + login_req(page_views.external_access_create), + name="external-access-create", + ), + path( + "external//regenerate/", + login_req(page_views.external_access_regenerate), + name="external-access-regenerate", + ), + path( + "external//toggle/", + login_req(page_views.external_access_toggle), + name="external-access-toggle", + ), +] urlpatterns = [ path("admin/", admin.site.urls), - # JWT tokens - path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), - path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), - path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"), - # App URLs - path("", include("src.epics.urls")), - path("", include("src.notes.urls")), - path("", include("src.projects.urls")), - path("", include("src.accounts.urls")), - path("", include("src.external.urls")), - path("", include("src.notifications.urls")), - path("", include("src.tickets.urls")), + path("api/", include(api_patterns)), + path("api/schema/", SpectacularAPIView.as_view(), name="schema"), + path( + "api/docs/", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger-ui", + ), + path("dashboard/", include(dashboard_patterns)), + path( + "login/", + auth_views.LoginView.as_view( + template_name="auth/login.html", + redirect_authenticated_user=True, + ), + name="login", + ), + path("logout/", auth_views.LogoutView.as_view(next_page="login"), name="logout"), + path( + "external//", + page_views.external_access_view, + name="external-dashboard", + ), + path( + "external//logout/", + page_views.external_access_logout, + name="external-logout", + ), ] -# Don't show schema in production if settings.DEBUG: - schema_view = get_schema_view( - openapi.Info( - title="config API", - default_version="v1", - ), - public=True, - ) - urlpatterns += [ - path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), - path("schema/", schema_view.with_ui("redoc", cache_timeout=0)), - ] + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -# django-browser-reload for tailwind live reload (dev only) -if settings.DEBUG: - urlpatterns += [ - path("__reload__/", include("django_browser_reload.urls")), - ] - -urlpatterns += [ - re_path( - r"^media/(?P.*)$", - serve, - {"document_root": settings.MEDIA_ROOT}, - ), -] \ No newline at end of file