Skip to content

Commit

Permalink
feat: Add context processor for project name, update URLs for environ…
Browse files Browse the repository at this point in the history
…ment checks, and enhance views with environment info
  • Loading branch information
AhmedNassar7 committed Dec 22, 2024
1 parent 1202b17 commit 8bf446d
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 93 deletions.
2 changes: 2 additions & 0 deletions apps/users/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# apps/users/admin.py

from django.contrib import admin
from .models import User

Expand Down
7 changes: 7 additions & 0 deletions egypt_metro/context_processors.py
Original file line number Diff line number Diff line change
@@ -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
}
121 changes: 49 additions & 72 deletions egypt_metro/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -73,6 +70,7 @@
"apps.stations.apps.StationsConfig", # Stations app
]

# Middleware configuration
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", # Security middleware
"whitenoise.middleware.WhiteNoiseMiddleware", # WhiteNoise middleware
Expand Down Expand Up @@ -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 = [
{
Expand All @@ -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',
],
},
},
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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/

Expand Down
12 changes: 10 additions & 2 deletions egypt_metro/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/",
Expand All @@ -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

Expand All @@ -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
Expand Down
48 changes: 38 additions & 10 deletions egypt_metro/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -77,10 +79,9 @@ def home(request):
</head>
<body>
<h1>Welcome to Egypt Metro Backend</h1>
<p>Status: {data['status']}</p>
<p>Version: {data['version']}</p>
<p>Uptime: {data['uptime']}</p>
<p>Current Date & Time: {data['current_date_time']}</p>
<p>Date & Time: {data['current_date_time']}</p>
<p>Environment: {data['environment']}</p>
<h2>Quick Links</h2>
<ul>
<li><a href="{data['admin_panel']}">Admin Panel</a></li>
Expand Down Expand Up @@ -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):
Expand All @@ -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,
# )
Loading

0 comments on commit 8bf446d

Please sign in to comment.