diff --git a/apps/users/admin.py b/apps/users/admin.py
index f6b3f68..201ebf7 100644
--- a/apps/users/admin.py
+++ b/apps/users/admin.py
@@ -1,3 +1,5 @@
+# apps/users/admin.py
+
from django.contrib import admin
from .models import User
diff --git a/egypt_metro/context_processors.py b/egypt_metro/context_processors.py
new file mode 100644
index 0000000..54dd1ae
--- /dev/null
+++ b/egypt_metro/context_processors.py
@@ -0,0 +1,7 @@
+# egypt_metro/context_processors.py
+
+# Context processors are functions that run before rendering a template.
+def project_name(request):
+ return {
+ 'project_name': 'Egypt Metro', # Project name
+ }
diff --git a/egypt_metro/settings.py b/egypt_metro/settings.py
index a9c49fa..76cb987 100644
--- a/egypt_metro/settings.py
+++ b/egypt_metro/settings.py
@@ -10,35 +10,29 @@
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
+# import logging
from pathlib import Path # File path helper
import os # Operating system dependent functionality
import dj_database_url # type: ignore # Parse database URLs
from dotenv import load_dotenv # Load environment variables from .env file
from datetime import timedelta # Time delta for JWT tokens
from corsheaders.defaults import default_headers # Default headers for CORS
-# from decouple import config
+from decouple import config
from datetime import datetime
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent # Base directory for the project
+PROJECT_NAME = "Egypt Metro"
# Load the appropriate .env file based on an environment variable
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev") # Default to dev
dotenv_path = BASE_DIR / f"env/.env.{ENVIRONMENT}"
load_dotenv(dotenv_path)
-# Check if the .env file exists and load it
-# if dotenv_path.is_file():
-# load_dotenv(dotenv_path)
-# else:
-# raise FileNotFoundError(f"Environment file not found: {dotenv_path}")
-
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("SECRET_KEY") # Secret key for Django
-# DEBUG = os.getenv("DEBUG", "False") == "True" # Default to False
-# ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="").split(",")
-DEBUG = True
-ALLOWED_HOSTS = ["*"] # Temporary for debugging purposes
+DEBUG = os.getenv("DEBUG", "False") == "True" # Default to False
+ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="").split(",")
# Set API start time to the application's boot time
API_START_TIME = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
@@ -47,8 +41,11 @@
SESSION_SAVE_EVERY_REQUEST = True # Extend session each request
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Session persists after closing the browser
-# Application definition
+# Debugging: Log the environment
+# logger = logging.getLogger(__name__)
+# logger.debug(f"Current environment: {ENVIRONMENT}")
+# Application definition
INSTALLED_APPS = [
"django.contrib.admin", # Admin panel
"django.contrib.auth", # Authentication framework
@@ -73,6 +70,7 @@
"apps.stations.apps.StationsConfig", # Stations app
]
+# Middleware configuration
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", # Security middleware
"whitenoise.middleware.WhiteNoiseMiddleware", # WhiteNoise middleware
@@ -109,7 +107,13 @@
CORS_ALLOW_CREDENTIALS = True # Allow credentials
if ENVIRONMENT == "dev":
- CORS_ALLOW_ALL_ORIGINS = True
+ INSTALLED_APPS += ["silk"]
+ MIDDLEWARE += ["silk.middleware.SilkyMiddleware"]
+
+ SILKY_PYTHON_PROFILER = True # Enables Python code profiling
+ SILKY_MAX_REQUESTS = 1000 # Limit the number of requests to profile
+ SILKY_RECORD_SQL = True # Records SQL queries
+ SILKY_AUTHENTICATION = True # Protect Silk interface with authentication
TEMPLATES = [
{
@@ -122,8 +126,8 @@
"django.template.context_processors.request", # Request context processor
"django.contrib.auth.context_processors.auth", # Auth context processor
"django.contrib.messages.context_processors.messages", # Messages context processor
- "django.template.context_processors.request", # Request context processor
'django.template.context_processors.static', # Static context processor
+ 'egypt_metro.context_processors.project_name',
],
},
},
@@ -135,10 +139,6 @@
# Custom User Model
AUTH_USER_MODEL = "users.User"
-# Load secret file if in production
-# if ENVIRONMENT == "prod":
-# load_dotenv("/etc/secrets/env.prod") # Load production secrets
-
# General settings
SECRET_KEY = os.getenv("SECRET_KEY") # Secret key for Django
BASE_URL = os.getenv("BASE_URL") # Base URL for the project
@@ -176,42 +176,38 @@
"sslmode": "require", # Enforce SSL for secure connections
})
+ SESSION_COOKIE_SECURE = True # Ensures cookies are only sent over HTTPS
+ CSRF_COOKIE_SECURE = True # CSRF cookie should also be secure
+ SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Keep sessions open until explicitly logged out
+ SESSION_COOKIE_HTTPONLY = True # Avoid client-side access to session cookie
+ SESSION_SAVE_EVERY_REQUEST = True # Save the session on every request to ensure data consistency
+ CSRF_COOKIE_HTTPONLY = True # Make sure CSRF cookie can't be accessed via JavaScript
+ CSRF_TRUSTED_ORIGINS = [
+ 'https://backend-54v5.onrender.com/', # Replace with your actual domain
+ ]
+
+ # For secure connections over HTTPS (especially for production)
+ SECURE_SSL_REDIRECT = True # Redirect HTTP to HTTPS
+ SECURE_HSTS_SECONDS = 3600 # HTTP Strict Transport Security (HSTS) in seconds
+ SECURE_HSTS_INCLUDE_SUBDOMAINS = True
+ SECURE_HSTS_PRELOAD = True
+
REQUIRED_ENV_VARS = ["SECRET_KEY", "DATABASE_URL", "JWT_SECRET", "BASE_URL"]
for var in REQUIRED_ENV_VARS:
if not os.getenv(var):
raise ValueError(f"{var} is not set in environment variables.")
-if ENVIRONMENT == "prod":
- CACHES = {
- "default": {
- "BACKEND": "django.core.cache.backends.redis.RedisCache",
- "LOCATION": "redis://127.0.0.1:6379/1",
- }
- }
- SECURE_SSL_REDIRECT = True
- SECURE_HSTS_SECONDS = 31536000
- SECURE_HSTS_PRELOAD = True
- SECURE_HSTS_INCLUDE_SUBDOMAINS = True
- CSRF_COOKIE_SECURE = True
- SESSION_COOKIE_SECURE = True
-else:
- SECURE_SSL_REDIRECT = False
- SECURE_HSTS_PRELOAD = False
- SECURE_HSTS_INCLUDE_SUBDOMAINS = False
- CSRF_COOKIE_SECURE = False
- SESSION_COOKIE_SECURE = False
-
-
-# if not DEBUG: # Enable only in production
-# SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT", "True") == "True"
-# SECURE_HSTS_SECONDS = int(os.getenv("SECURE_HSTS_SECONDS", "31536000"))
-# SECURE_HSTS_PRELOAD = os.getenv("SECURE_HSTS_PRELOAD", "True") == "True"
-# SECURE_HSTS_INCLUDE_SUBDOMAINS = (
-# os.getenv("SECURE_HSTS_INCLUDE_SUBDOMAINS", "True") == "True"
-# )
-# CSRF_COOKIE_SECURE = True
-# SESSION_COOKIE_SECURE = True
+# if ENVIRONMENT == "prod":
+# CACHES = {
+# "default": {
+# "BACKEND": "django.core.cache.backends.redis.RedisCache",
+# "LOCATION": "redis://127.0.0.1:6379/1",
+# 'OPTIONS': {
+# 'CLIENT_CLASS': 'django_redis.client.DefaultClient'
+# }
+# }
+# }
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
@@ -236,12 +232,6 @@
# "allauth.account.auth_backends.AuthenticationBackend", # For allauth
]
-# SOCIALACCOUNT_PROVIDERS = {
-# "google": {
-# "APP": {"client_id": "your-client-id", "secret": "your-secret", "key": ""}
-# }
-# }
-
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
@@ -317,15 +307,17 @@
},
}
+# Cache configuration
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache", # Local memory cache
- "LOCATION": "unique-snowflake",
+ "LOCATION": "unique-snowflake", # Unique identifier for the cache
}
}
-SESSION_ENGINE = "django.contrib.sessions.backends.cache"
-SESSION_CACHE_ALIAS = "default"
+# Session engine configuration
+SESSION_ENGINE = "django.contrib.sessions.backends.cache" # Session engine
+SESSION_CACHE_ALIAS = "default" # Cache alias for sessions
INTERNAL_IPS = [
"127.0.0.1", # Localhost
@@ -357,21 +349,6 @@
},
}
-# Initialize Sentry for Error Tracking
-# SENTRY_DSN = os.getenv("SENTRY_DSN") # Use environment variable
-
-# if SENTRY_DSN:
-# import sentry_sdk # type: ignore
-# from sentry_sdk.integrations.django import DjangoIntegration # type: ignore
-
-# sentry_sdk.init(
-# dsn=SENTRY_DSN,
-# integrations=[DjangoIntegration()],
-# send_default_pii=True,
-# )
-# else:
-# print("Sentry DSN not configured. Skipping Sentry initialization.")
-
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
diff --git a/egypt_metro/urls.py b/egypt_metro/urls.py
index f3e05aa..a2e5831 100644
--- a/egypt_metro/urls.py
+++ b/egypt_metro/urls.py
@@ -14,10 +14,12 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
+# egypt_metro/urls.py
+
import logging
from django.contrib import admin
from django.urls import path, include
-# from egypt_metro import settings
+from egypt_metro import settings
from .views import health_check, home
# from django.conf.urls.static import static
from drf_yasg.views import get_schema_view
@@ -31,7 +33,7 @@
# OpenAPI schema view
schema_view = get_schema_view(
openapi.Info(
- title="Metro API",
+ title=f"{settings.PROJECT_NAME} API",
default_version="v1",
description="API documentation for Metro application",
terms_of_service="https://www.google.com/policies/terms/",
@@ -53,6 +55,9 @@
# Home
path("", home, name="home"), # Home view
+ # Check environment
+ # path('check-environment/', check_environment, name='check_environment'),
+
# Authentication
path("accounts/", include("allauth.urls")), # Allauth authentication
@@ -73,6 +78,9 @@
), # Swagger UI
path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), # ReDoc
+
+ # Silk Profiling
+ path("silk/", include("silk.urls", namespace="silk")) if settings.ENVIRONMENT == "dev" else None,
]
# Debug the URL loading process
diff --git a/egypt_metro/views.py b/egypt_metro/views.py
index cd89438..58ba97c 100644
--- a/egypt_metro/views.py
+++ b/egypt_metro/views.py
@@ -1,4 +1,7 @@
+# egypt_metro/views.py
+
import logging
+import os
from django.http import JsonResponse
from django.db import connection
# from django.utils.timezone import now
@@ -39,21 +42,20 @@ def home(request):
hours, remainder = divmod(remainder, 3600) # 3600 seconds in an hour
minutes, seconds = divmod(remainder, 60) # 60 seconds in a minute
- # Format uptime
- uptime = f"{int(days)} days, {int(hours)} hours, {int(minutes)} minutes, {int(seconds)} seconds"
+ # Get the current environment (dev or prod)
+ environment = os.getenv("ENVIRONMENT", "dev") # Default to dev if not set
+ logger.debug(f"Current environment: {environment}")
# Data to return as JSON response
data = {
- "message": "Welcome to Egypt Metro Backend",
- "status": "OK", # Status indicating the API is operational
"admin_panel": "/admin/", # Link to Django admin panel
"api_documentation": "/docs/", # Link to API documentation
"health_check": "/health/", # Health check endpoint
"swagger": "/swagger/", # Swagger API documentation
"redoc": "/redoc/", # Redoc API documentation
"version": "1.0.0", # Backend version
- "uptime": uptime, # Dynamically calculated uptime
"current_date_time": current_date_time, # Current date and time with minutes and seconds
+ "environment": environment, # Current environment (dev or prod)
"api_routes": {
"users": "/api/users/", # User-related routes
"register": "/api/users/register/", # User registration
@@ -77,10 +79,9 @@ def home(request):
Welcome to Egypt Metro Backend
- Status: {data['status']}
Version: {data['version']}
- Uptime: {data['uptime']}
- Current Date & Time: {data['current_date_time']}
+ Date & Time: {data['current_date_time']}
+ Environment: {data['environment']}
Quick Links
- Admin Panel
@@ -115,8 +116,8 @@ def health_check(request):
return JsonResponse({"status": "ok"}, status=200)
except Exception as e:
- logger.error("Health check failed", exc_info=e)
- return JsonResponse({"status": "error", "message": str(e)}, status=500)
+ logger.error(f"Health check failed: {str(e)}", exc_info=True)
+ return JsonResponse({"status": "error", "message": "Database connectivity issue"}, status=500)
def custom_404(request, exception=None):
@@ -125,3 +126,30 @@ def custom_404(request, exception=None):
def custom_500(request):
return JsonResponse({"error": "Internal server error"}, status=500)
+
+
+# def check_environment(request):
+# """
+# View to check the current environment (dev or prod).
+# """
+# try:
+# # Get the environment (default to 'dev' if not set)
+# environment = os.getenv("ENVIRONMENT", "dev") # Default to dev if not set
+# logger.debug(f"Current environment: {environment}")
+
+# # Respond with JSON, which is a standard format for APIs
+# response_data = {
+# "environment": environment,
+# "status": "success",
+# }
+# return JsonResponse(response_data)
+
+# except Exception as e:
+# # Log the error for debugging purposes
+# logger.error(f"Error in check_environment view: {str(e)}")
+
+# # Return an error response in case of failure
+# return JsonResponse(
+# {"error": "Unable to determine the environment", "status": "fail"},
+# status=500,
+# )
diff --git a/poetry.lock b/poetry.lock
index 58b51eb..9f5e1de 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -174,6 +174,21 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+[[package]]
+name = "autopep8"
+version = "2.3.1"
+description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "autopep8-2.3.1-py2.py3-none-any.whl", hash = "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"},
+ {file = "autopep8-2.3.1.tar.gz", hash = "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda"},
+]
+
+[package.dependencies]
+pycodestyle = ">=2.12.0"
+tomli = {version = "*", markers = "python_version < \"3.11\""}
+
[[package]]
name = "beautifulsoup4"
version = "4.12.3"
@@ -412,13 +427,13 @@ files = [
[[package]]
name = "click"
-version = "8.1.7"
+version = "8.1.8"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
- {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
- {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
+ {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
]
[package.dependencies]
@@ -673,6 +688,23 @@ redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1"
[package.extras]
hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"]
+[[package]]
+name = "django-silk"
+version = "5.3.2"
+description = "Silky smooth profiling for the Django Framework"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "django_silk-5.3.2-py3-none-any.whl", hash = "sha256:49f1caebfda28b1707f0cfef524e0476beb82b8c5e40f5ccff7f73a6b4f6d3ac"},
+ {file = "django_silk-5.3.2.tar.gz", hash = "sha256:b0db54eebedb8d16f572321bd6daccac0bd3f547ae2618bb45d96fe8fc02229d"},
+]
+
+[package.dependencies]
+autopep8 = "*"
+Django = ">=4.2"
+gprof2dot = ">=2017.09.19"
+sqlparse = "*"
+
[[package]]
name = "djangorestframework"
version = "3.15.2"
@@ -923,6 +955,17 @@ requests-oauthlib = ">=0.7.0"
[package.extras]
tool = ["click (>=6.0.0)"]
+[[package]]
+name = "gprof2dot"
+version = "2024.6.6"
+description = "Generate a dot graph from the output of several profilers."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "gprof2dot-2024.6.6-py2.py3-none-any.whl", hash = "sha256:45b14ad7ce64e299c8f526881007b9eb2c6b75505d5613e96e66ee4d5ab33696"},
+ {file = "gprof2dot-2024.6.6.tar.gz", hash = "sha256:fa1420c60025a9eb7734f65225b4da02a10fc6dd741b37fa129bc6b41951e5ab"},
+]
+
[[package]]
name = "gunicorn"
version = "23.0.0"
@@ -1141,13 +1184,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
[[package]]
name = "jinja2"
-version = "3.1.4"
+version = "3.1.5"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
- {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
- {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
+ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
+ {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
]
[package.dependencies]
@@ -1578,6 +1621,17 @@ files = [
[package.dependencies]
pyasn1 = ">=0.4.6,<0.7.0"
+[[package]]
+name = "pycodestyle"
+version = "2.12.1"
+description = "Python style guide checker"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"},
+ {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"},
+]
+
[[package]]
name = "pycparser"
version = "2.22"
@@ -2187,6 +2241,47 @@ files = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
+[[package]]
+name = "tomli"
+version = "2.2.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
+ {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
+ {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
+ {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
+ {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
+ {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
+ {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
+ {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
+ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
+ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
+]
+
[[package]]
name = "tornado"
version = "6.4.1"
@@ -2354,4 +2449,4 @@ brotli = ["brotli"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<4.0"
-content-hash = "89722855bc817e7d8bc71e2e71fa2fe96723821ff122c796cd5ba44b8262023a"
+content-hash = "2e81e418c8f8a2e3a983194f9b7e2989457c659b89b6a9ed877be7f38043d4c0"
diff --git a/pyproject.toml b/pyproject.toml
index 789326c..a49123e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -112,6 +112,7 @@ whitenoise = "6.7.0" # WhiteNoise for serving static files in Django
# [tool.poetry.dev-dependencies]
# Development dependencies including testing and development tools.
django-db-geventpool = "^4.0.7"
+django-silk = "^5.3.2"
[tool.poetry.dev-dependencies]
pytest = "^6.2.4" # Pytest for unit testing
pytest-asyncio = "^0.15.1" # Pytest plugin for asyncio tests
diff --git a/requirements.txt b/requirements.txt
index 0783f1d..36a3885 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,12 +4,13 @@ asgiref==3.8.1 ; python_version >= "3.10" and python_version < "4.0"
asttokens==2.4.1 ; python_version >= "3.10" and python_version < "4.0"
async-timeout==5.0.1 ; python_version >= "3.10" and python_full_version < "3.11.3"
asyncpg==0.30.0 ; python_version >= "3.10" and python_version < "4.0"
+autopep8==2.3.1 ; python_version >= "3.10" and python_version < "4.0"
beautifulsoup4==4.12.3 ; python_version >= "3.10" and python_version < "4.0"
cachetools==5.5.0 ; python_version >= "3.10" and python_version < "4.0"
certifi==2024.8.30 ; python_version >= "3.10" and python_version < "4.0"
cffi==1.17.1 ; python_version >= "3.10" and python_version < "4.0"
charset-normalizer==3.4.0 ; python_version >= "3.10" and python_version < "4.0"
-click==8.1.7 ; python_version >= "3.10" and python_version < "4.0"
+click==8.1.8 ; python_version >= "3.10" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0"
comm==0.2.2 ; python_version >= "3.10" and python_version < "4.0"
cryptography==44.0.0 ; python_version >= "3.10" and python_version < "4.0"
@@ -22,6 +23,7 @@ django-cors-headers==4.6.0 ; python_version >= "3.10" and python_version < "4.0"
django-db-geventpool==4.0.7 ; python_version >= "3.10" and python_version < "4.0"
django-environ==0.11.2 ; python_version >= "3.10" and python_version < "4"
django-redis==5.4.0 ; python_version >= "3.10" and python_version < "4.0"
+django-silk==5.3.2 ; python_version >= "3.10" and python_version < "4.0"
django==4.2.16 ; python_version >= "3.10" and python_version < "4.0"
djangorestframework-simplejwt==5.3.1 ; python_version >= "3.10" and python_version < "4.0"
djangorestframework==3.15.2 ; python_version >= "3.10" and python_version < "4.0"
@@ -37,6 +39,7 @@ git-filter-repo==2.45.0 ; python_version >= "3.10" and python_version < "4.0"
google-auth-httplib2==0.2.0 ; python_version >= "3.10" and python_version < "4.0"
google-auth-oauthlib==1.2.1 ; python_version >= "3.10" and python_version < "4.0"
google-auth==2.36.0 ; python_version >= "3.10" and python_version < "4.0"
+gprof2dot==2024.6.6 ; python_version >= "3.10" and python_version < "4.0"
gunicorn==23.0.0 ; python_version >= "3.10" and python_version < "4.0"
h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0"
httpcore==0.16.3 ; python_version >= "3.10" and python_version < "4.0"
@@ -47,7 +50,7 @@ inflection==0.5.1 ; python_version >= "3.10" and python_version < "4.0"
ipykernel==6.29.5 ; python_version >= "3.10" and python_version < "4.0"
ipython==8.27.0 ; python_version >= "3.10" and python_version < "4.0"
jedi==0.19.1 ; python_version >= "3.10" and python_version < "4.0"
-jinja2==3.1.4 ; python_version >= "3.10" and python_version < "4.0"
+jinja2==3.1.5 ; python_version >= "3.10" and python_version < "4.0"
jupyter-client==8.6.3 ; python_version >= "3.10" and python_version < "4.0"
jupyter-core==5.7.2 ; python_version >= "3.10" and python_version < "4.0"
markupsafe==3.0.2 ; python_version >= "3.10" and python_version < "4.0"
@@ -66,6 +69,7 @@ ptyprocess==0.7.0 ; python_version >= "3.10" and python_version < "4.0" and (sys
pure-eval==0.2.3 ; python_version >= "3.10" and python_version < "4.0"
pyasn1-modules==0.4.1 ; python_version >= "3.10" and python_version < "4.0"
pyasn1==0.6.1 ; python_version >= "3.10" and python_version < "4.0"
+pycodestyle==2.12.1 ; python_version >= "3.10" and python_version < "4.0"
pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0"
pydantic==1.10.19 ; python_version >= "3.10" and python_version < "4.0"
pygments==2.18.0 ; python_version >= "3.10" and python_version < "4.0"
@@ -89,6 +93,7 @@ soupsieve==2.6 ; python_version >= "3.10" and python_version < "4.0"
sqlparse==0.5.2 ; python_version >= "3.10" and python_version < "4.0"
stack-data==0.6.3 ; python_version >= "3.10" and python_version < "4.0"
starlette==0.14.2 ; python_version >= "3.10" and python_version < "4.0"
+tomli==2.2.1 ; python_version >= "3.10" and python_version < "3.11"
tornado==6.4.1 ; python_version >= "3.10" and python_version < "4.0"
traitlets==5.14.3 ; python_version >= "3.10" and python_version < "4.0"
typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0"