From d736c1681cd4f5e939965458343ef1b6fcfad89b Mon Sep 17 00:00:00 2001 From: saani Date: Sun, 23 Nov 2025 23:06:17 +0000 Subject: [PATCH] # Commit Message ``` refactor: update settings and Docker config for production - Configure ALLOWED_HOSTS and CORS from environment variables for better security - Switch default database from PostgreSQL to SQLite3 (PostgreSQL config commented) - Simplify DEBUG environment variable handling - Update Dockerfile to use Python 3.11 and gunicorn for production - Add static file collection in Docker build process - Add user appointment statistics endpoint (user_apointment_stats) - Add .dockerignore to exclude unnecessary files from build These changes improve production readiness by making critical settings configurable via environment variables and using production-grade WSGI server (gunicorn) instead of Django development server. --- Procfile | 2 ++ booking_system/settings.py | 36 +++++++++++++++++++++--------------- booking_system/views.py | 3 ++- dockerfile | 33 ++++++++++++++++++++++++--------- meetings/urls.py | 4 +++- meetings/views.py | 26 +++++++++++++++++++++++++- users/managers.py | 1 + 7 files changed, 78 insertions(+), 27 deletions(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..520f38e --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +release: python manage.py migrate +web: gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT --workers 3 \ No newline at end of file diff --git a/booking_system/settings.py b/booking_system/settings.py index e3fd12b..beba8b5 100644 --- a/booking_system/settings.py +++ b/booking_system/settings.py @@ -9,10 +9,16 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.getenv('JWT_SECRET', 'django-insecure-fallback-secret-key') -DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' +DEBUG = os.getenv('DEBUG') -ALLOWED_HOSTS = ["*"] +# Update ALLOWED_HOSTS for production +ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '*').split(',') +# Update CORS for production +CORS_ALLOWED_ORIGINS = os.getenv( + 'CORS_ALLOWED_ORIGINS', + 'http://localhost:3000,http://127.0.0.1:3000' +).split(',') @@ -71,24 +77,24 @@ if not DEBUG: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.sqlite3', -# 'NAME': BASE_DIR / 'db.sqlite3', -# } -# } - DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.getenv('POSTGRES_DB'), - 'USER': os.getenv('POSTGRES_USER'), - 'PASSWORD': os.getenv('POSTGRES_PASSWORD'), - 'HOST': os.getenv('POSTGRES_HOST', 'postgres'), - 'PORT': os.getenv('POSTGRES_PORT', 5432), + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', } } +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.postgresql', +# 'NAME': os.getenv('POSTGRES_DB'), +# 'USER': os.getenv('POSTGRES_USER'), +# 'PASSWORD': os.getenv('POSTGRES_PASSWORD'), +# 'HOST': os.getenv('POSTGRES_HOST', 'postgres'), +# 'PORT': os.getenv('POSTGRES_PORT', 5432), +# } +# } + ENCRYPTION_KEY = os.getenv('ENCRYPTION_KEY') diff --git a/booking_system/views.py b/booking_system/views.py index 68933e9..20d212d 100644 --- a/booking_system/views.py +++ b/booking_system/views.py @@ -293,7 +293,8 @@ def api_root(request, format=None): "rejected": "Number of rejected requests", "completion_rate": "Percentage of requests that were scheduled" } - } + }, + }, "jitsi_integration": { "description": "Automatic Jitsi video meeting integration", diff --git a/dockerfile b/dockerfile index c2701b3..9bfce45 100644 --- a/dockerfile +++ b/dockerfile @@ -1,20 +1,35 @@ -FROM python:3.12-slim +FROM python:3.11-slim +# Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 +# Set work directory WORKDIR /app -RUN apt-get update && apt-get install -y \ - build-essential \ - curl wget \ - && rm -rf /var/lib/apt/lists/* +# Install dependencies +COPY requirements.txt /app/ +RUN pip install --upgrade pip && \ + pip install -r requirements.txt -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +# Copy project +COPY . /app/ -COPY . . +# Collect static files +RUN python manage.py collectstatic --noinput +# Expose port EXPOSE 8000 -CMD ["bash", "-c", "python manage.py migrate && python manage.py collectstatic --noinput && python manage.py runserver 0.0.0.0:8000"] +# Run gunicorn +CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"] +``` + +**Optional: Create `.dockerignore`:** +``` +*.pyc +__pycache__ +db.sqlite3 +.env +.git +venv/ \ No newline at end of file diff --git a/meetings/urls.py b/meetings/urls.py index 4db38be..3caf0e6 100644 --- a/meetings/urls.py +++ b/meetings/urls.py @@ -8,7 +8,8 @@ from .views import ( reject_appointment, available_dates, user_appointments, - appointment_stats + appointment_stats, + user_apointment_stats ) urlpatterns = [ @@ -25,4 +26,5 @@ urlpatterns = [ path('user/appointments/', user_appointments, name='user-appointments'), path('appointments/stats/', appointment_stats, name='appointment-stats'), + path('user/appointments/stats/', user_apointment_stats, name='user-appointment-stats'), ] \ No newline at end of file diff --git a/meetings/views.py b/meetings/views.py index 0a93776..65564e8 100644 --- a/meetings/views.py +++ b/meetings/views.py @@ -168,4 +168,28 @@ def appointment_stats(request): 'rejected': rejected, 'users': users, 'completion_rate': round((scheduled / total * 100), 2) if total > 0 else 0 - }) \ No newline at end of file + }) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def user_apointment_stats(request): + if not request.user.is_staff: + return Response( + {'error': 'Unauthorized'}, + status=status.HTTP_403_FORBIDDEN + ) + + total = AppointmentRequest.objects.filter(email=request.user.email).count() + pending = AppointmentRequest.objects.filter(email=request.user.email, status='pending_review').count() + scheduled = AppointmentRequest.objects.filter(email=request.user.email, status='scheduled').count() + rejected = AppointmentRequest.objects.filter(email=request.user.email, status='rejected').count() + completed = AppointmentRequest.objects.filter(email=request.user.email, status='completed').count() + + return Response({ + 'total_requests': total, + 'pending_review': pending, + 'scheduled': scheduled, + 'rejected': rejected, + 'completed': completed, + 'completion_rate': round((scheduled / total * 100), 2) if total > 0 else 0 + }) diff --git a/users/managers.py b/users/managers.py index 34708f5..4503d5e 100644 --- a/users/managers.py +++ b/users/managers.py @@ -24,6 +24,7 @@ class CustomUserManager(BaseUserManager): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) extra_fields.setdefault('is_active', True) + extra_fields.setdefault('isVerified', True) if extra_fields.get('is_staff') is not True: raise ValueError(_('Superuser must have is_staff=True.')) if extra_fields.get('is_superuser') is not True: