diff --git a/.DS_Store b/.DS_Store index ec4ca45..24c3ea4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/API_Documentation.md b/API_Documentation.md new file mode 100644 index 0000000..76b5404 --- /dev/null +++ b/API_Documentation.md @@ -0,0 +1,889 @@ +# Eventify Plus API Documentation + +**Base URL:** `https://uat.eventifyplus.com/api/` + +**Version:** 1.0 +**Last Updated:** December 2025 + +--- + +## Table of Contents + +1. [Authentication](#authentication) +2. [User APIs](#user-apis) +3. [Event APIs](#event-apis) +4. [Error Responses](#error-responses) + +--- + +## Authentication + +All API endpoints (except registration and login) require token-based authentication. The token must be included in the request body along with the username. + +**Token Format:** The token is a string returned after successful login or registration. + +--- + +## User APIs + +### 1. User Registration + +**Endpoint:** `POST /user/register/` + +**Description:** Register a new user account. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "email": "user@example.com", + "phone_number": "+1234567890", + "password": "securepassword123" +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| email | string | Yes | User's email address (must be unique) | +| phone_number | string | Yes | User's phone number (must be unique) | +| password | string | Yes | User's password | + +**Success Response (201 Created):** +```json +{ + "message": "User registered successfully", + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "errors": { + "email": ["Email is already registered."], + "phone_number": ["Phone number is already registered."] + } +} +``` + +**Error Response (500 Internal Server Error):** +```json +{ + "error": "Error message here" +} +``` + +--- + +### 2. User Login + +**Endpoint:** `POST /user/login/` + +**Description:** Authenticate user and receive authentication token. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "username": "user@example.com", + "password": "securepassword123" +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| username | string | Yes | User's email or username | +| password | string | Yes | User's password | + +**Note:** The `username` field accepts either email address or username. + +**Success Response (200 OK):** +```json +{ + "message": "Login successful", + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe", + "email": "user@example.com", + "phone_number": "+1234567890", + "first_name": "John", + "last_name": "Doe", + "role": "staff", + "pincode": "560001", + "district": "Bangalore Urban", + "state": "Karnataka", + "country": "India", + "place": "Bangalore", + "latitude": "12.9716", + "longitude": "77.5946" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "errors": { + "username": ["Invalid credentials."] + } +} +``` + +**Error Response (500 Internal Server Error):** +```json +{ + "error": "Error message here" +} +``` + +--- + +### 3. Check User Status + +**Endpoint:** `POST /user/status/` + +**Description:** Verify if the authentication token is valid and get user status. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe" +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | + +**Success Response (200 OK):** +```json +{ + "status": "logged_in", + "username": "john_doe", + "email": "user@example.com" +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +### 4. User Logout + +**Endpoint:** `POST /user/logout/` + +**Description:** Logout user and invalidate authentication token. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe" +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | + +**Success Response (200 OK):** +```json +{ + "status": "logged_out", + "message": "Logout successful" +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +## Event APIs + +### 5. Get Event Types List + +**Endpoint:** `POST events/type-list/` + +**Description:** Retrieve list of all available event types with icons. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe" +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | + +**Success Response (200 OK):** +```json +{ + "status": "success", + "event_types": [ + { + "id": 1, + "event_type": "Concert", + "event_type_icon": "https://uat.eventifyplus.com/media/event_type_icons/concert.png" + }, + { + "id": 2, + "event_type": "Sports", + "event_type_icon": "https://uat.eventifyplus.com/media/event_type_icons/sports.png" + }, + { + "id": 3, + "event_type": "Conference", + "event_type_icon": null + } + ] +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +### 6. Get Events by Pincode + +**Endpoint:** `POST events/pincode-events/` + +**Description:** Retrieve list of events filtered by pincode. If pincode is not provided or set to 'all', returns all events. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe", + "pincode": "560001" +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | +| pincode | string | No | Pincode to filter events (optional, use 'all' or omit to get all events) | + +**Success Response (200 OK):** +```json +{ + "status": "success", + "events": [ + { + "id": 1, + "name": "Music Festival", + "description": "Annual music festival", + "start_date": "2025-06-15", + "end_date": "2025-06-17", + "start_time": "10:00:00", + "end_time": "22:00:00", + "latitude": "12.9716", + "longitude": "77.5946", + "pincode": "560001", + "district": "Bangalore Urban", + "state": "Karnataka", + "place": "Bangalore", + "is_bookable": true, + "is_eventify_event": true, + "outside_event_url": "NA", + "event_type": 1, + "event_status": "pending", + "cancelled_reason": "NA", + "title": "Summer Music Fest", + "important_information": "Free parking available", + "venue_name": "City Park", + "thumb_img": "https://uat.eventifyplus.com/media/event_images/festival_thumb.jpg" + } + ] +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +### 7. Get Event Details + +**Endpoint:** `POST events/event-details/` + +**Description:** Retrieve detailed information about a specific event including all images. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe", + "event_id": 1 +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | +| event_id | integer | Yes | ID of the event | + +**Success Response (200 OK):** +```json +{ + "id": 1, + "name": "Music Festival", + "description": "Annual music festival with multiple artists", + "start_date": "2025-06-15", + "end_date": "2025-06-17", + "start_time": "10:00:00", + "end_time": "22:00:00", + "latitude": "12.9716", + "longitude": "77.5946", + "pincode": "560001", + "district": "Bangalore Urban", + "state": "Karnataka", + "place": "Bangalore", + "is_bookable": true, + "is_eventify_event": true, + "outside_event_url": "NA", + "event_type": 1, + "event_status": "pending", + "cancelled_reason": "NA", + "title": "Summer Music Fest", + "important_information": "Free parking available", + "venue_name": "City Park", + "status": "success", + "images": [ + { + "is_primary": true, + "image": "https://uat.eventifyplus.com/media/event_images/festival_main.jpg" + }, + { + "is_primary": false, + "image": "https://uat.eventifyplus.com/media/event_images/festival_1.jpg" + }, + { + "is_primary": false, + "image": "https://uat.eventifyplus.com/media/event_images/festival_2.jpg" + } + ] +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +### 8. Get Event Images + +**Endpoint:** `POST events/event-images/` + +**Description:** Retrieve list of all image URLs for a specific event. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe", + "event_id": 1 +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | +| event_id | integer | Yes | ID of the event | + +**Success Response (200 OK):** +```json +{ + "status": "success", + "images": [ + "https://uat.eventifyplus.com/media/event_images/festival_main.jpg", + "https://uat.eventifyplus.com/media/event_images/festival_1.jpg", + "https://uat.eventifyplus.com/media/event_images/festival_2.jpg" + ] +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +### 9. Get Events by Category + +**Endpoint:** `POST events/events-by-category/` + +**Description:** Retrieve list of events filtered by event type/category ID. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe", + "category_id": 1 +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | +| category_id | integer | Yes | Event type/category ID | + +**Success Response (200 OK):** +```json +{ + "status": "success", + "events": [ + { + "id": 1, + "name": "Music Festival", + "description": "Annual music festival", + "start_date": "2025-06-15", + "end_date": "2025-06-17", + "event_type": 1, + "event_image": "https://uat.eventifyplus.com/media/event_images/festival_main.jpg" + } + ] +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (400 Bad Request - Missing category_id):** +```json +{ + "status": "error", + "message": "category_id is required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +### 10. Get Events by Month and Year + +**Endpoint:** `POST events/events-by-month-year/` + +**Description:** Retrieve events for a specific month and year with date-wise breakdown. Returns dates that have events, total count, and number of events per date. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe", + "month": "August", + "year": 2025 +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | +| month | string | Yes | Month name (e.g., "August", "august", "Aug") | +| year | integer | Yes | Year (e.g., 2025) | + +**Success Response (200 OK):** +```json +{ + "status": "success", + "dates": [ + "2025-08-01", + "2025-08-10", + "2025-08-15" + ], + "total_number_of_events": 10, + "date_events": [ + { + "date_of_event": "2025-08-01", + "events_of_date": 8 + }, + { + "date_of_event": "2025-08-10", + "events_of_date": 2 + }, + { + "date_of_event": "2025-08-15", + "events_of_date": 1 + } + ] +} +``` + +**Error Response (400 Bad Request):** +```json +{ + "status": "error", + "message": "month and year are required" +} +``` + +**Error Response (400 Bad Request - Invalid Month):** +```json +{ + "status": "error", + "message": "Invalid month name: InvalidMonth" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +### 11. Get Events by Date + +**Endpoint:** `POST events/events-by-date/` + +**Description:** Retrieve complete information for all events occurring on a specific date. Returns all event fields along with primary image thumbnails. + +**Request Headers:** +``` +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", + "username": "john_doe", + "date_of_event": "2025-08-15" +} +``` + +**Request Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| token | string | Yes | Authentication token | +| username | string | Yes | User's username | +| date_of_event | string | Yes | Date in YYYY-MM-DD format (e.g., "2025-08-15") | + +**Success Response (200 OK):** +```json +{ + "status": "success", + "events": [ + { + "id": 1, + "created_date": "2025-07-01", + "name": "Music Festival", + "description": "Annual music festival with multiple artists", + "start_date": "2025-08-10", + "end_date": "2025-08-20", + "start_time": "10:00:00", + "end_time": "22:00:00", + "latitude": "12.9716", + "longitude": "77.5946", + "pincode": "560001", + "district": "Bangalore Urban", + "state": "Karnataka", + "place": "Bangalore", + "is_bookable": true, + "is_eventify_event": true, + "outside_event_url": "NA", + "event_type": 1, + "event_status": "pending", + "cancelled_reason": "NA", + "title": "Summer Music Fest", + "important_information": "Free parking available", + "venue_name": "City Park", + "thumb_img": "https://uat.eventifyplus.com/media/event_images/festival_thumb.jpg" + }, + { + "id": 2, + "created_date": "2025-07-05", + "name": "Sports Championship", + "description": "Regional sports championship", + "start_date": "2025-08-15", + "end_date": "2025-08-15", + "start_time": "09:00:00", + "end_time": "18:00:00", + "latitude": "12.9352", + "longitude": "77.6245", + "pincode": "560001", + "district": "Bangalore Urban", + "state": "Karnataka", + "place": "Bangalore", + "is_bookable": false, + "is_eventify_event": true, + "outside_event_url": "NA", + "event_type": 2, + "event_status": "pending", + "cancelled_reason": "NA", + "title": "Regional Sports Championship", + "important_information": "Entry fee required", + "venue_name": "Sports Complex", + "thumb_img": "https://uat.eventifyplus.com/media/event_images/sports_thumb.jpg" + } + ] +} +``` + +**Error Response (400 Bad Request - Missing date_of_event):** +```json +{ + "status": "error", + "message": "date_of_event is required" +} +``` + +**Error Response (400 Bad Request - Invalid Date Format):** +```json +{ + "status": "error", + "message": "Invalid date format. Expected YYYY-MM-DD" +} +``` + +**Error Response (400 Bad Request - Missing Credentials):** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +**Error Response (401 Unauthorized):** +```json +{ + "status": "invalid_token" +} +``` + +--- + +## Error Responses + +### Common Error Codes + +| Status Code | Description | +|-------------|-------------| +| 200 | Success | +| 201 | Created (Registration successful) | +| 400 | Bad Request (Missing or invalid parameters) | +| 401 | Unauthorized (Invalid token or credentials) | +| 500 | Internal Server Error | + +### Standard Error Response Format + +```json +{ + "status": "error", + "message": "Error description here" +} +``` + +### Authentication Errors + +**Invalid Token:** +```json +{ + "status": "invalid_token" +} +``` + +**Token Mismatch:** +```json +{ + "status": "error", + "message": "token does not match user" +} +``` + +**Missing Credentials:** +```json +{ + "status": "error", + "message": "token and username required" +} +``` + +--- + +## Notes + +1. **Authentication:** All endpoints except registration and login require a valid token in the request body. + +2. **Content-Type:** All requests must include `Content-Type: application/json` header. + +3. **Date Format:** Dates are returned in `YYYY-MM-DD` format. The `date_of_event` parameter in `events-by-date` endpoint must also be provided in `YYYY-MM-DD` format. + +4. **Time Format:** Times are returned in `HH:MM:SS` format (24-hour). + +5. **Image URLs:** All image URLs are returned as absolute URLs starting with the base URL. + +6. **Pincode Filter:** The pincode parameter in `pincode-events` endpoint is optional. Omit it or set to `'all'` to retrieve all events. + +7. **Month Names:** The month parameter accepts full month names (e.g., "August") or abbreviations (e.g., "Aug"), case-insensitive. + +8. **Multi-day Events:** Events that span multiple days will be counted for each day they occur in the specified month. The `events-by-date` endpoint returns all events where the specified date falls between the event's `start_date` and `end_date` (inclusive). + +--- + +## Support + +For API support or questions, please contact the development team. + +**API Base URL:** `https://uat.eventifyplus.com/api/` + +--- + +*Document Version: 1.0* +*Last Updated: December 2025* + diff --git a/accounts/customer_forms.py b/accounts/customer_forms.py index 6a8bb79..d92e732 100644 --- a/accounts/customer_forms.py +++ b/accounts/customer_forms.py @@ -5,18 +5,19 @@ from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth import get_user_model +User = get_user_model() class RegisterForm(UserCreationForm): full_name = forms.CharField(max_length=150, required=False, label="Full name") email = forms.EmailField(required=True, label="Email") class Meta: - model = get_user_model() + model = User fields = ("username", "full_name", "email", "password1", "password2") def clean_email(self): email = self.cleaned_data.get("email") - user = get_user_model() + user = User if user.objects.filter(email__iexact=email).exists(): raise forms.ValidationError("A user with that email already exists.") return email @@ -59,4 +60,35 @@ class CustomerLoginForm(AuthenticationForm): "class": "input", "autocomplete": "current-password", }) - ) \ No newline at end of file + ) + + +class CustomerProfileForm(forms.ModelForm): + class Meta: + model = User + fields = [ + "first_name", + "last_name", + "email", + "phone_number", + "pincode", + "district", + "state", + "country", + "place", + "latitude", + "longitude", + ] + widgets = { + "first_name": forms.TextInput(attrs={"class": "form-control"}), + "last_name": forms.TextInput(attrs={"class": "form-control"}), + "email": forms.EmailInput(attrs={"class": "form-control"}), + "phone_number": forms.TextInput(attrs={"class": "form-control"}), + "pincode": forms.TextInput(attrs={"class": "form-control"}), + "district": forms.TextInput(attrs={"class": "form-control"}), + "state": forms.TextInput(attrs={"class": "form-control"}), + "country": forms.TextInput(attrs={"class": "form-control"}), + "place": forms.TextInput(attrs={"class": "form-control"}), + "latitude": forms.NumberInput(attrs={"class": "form-control", "step": "any"}), + "longitude": forms.NumberInput(attrs={"class": "form-control", "step": "any"}), + } \ No newline at end of file diff --git a/accounts/customer_views.py b/accounts/customer_views.py index d83d418..bc1b8aa 100644 --- a/accounts/customer_views.py +++ b/accounts/customer_views.py @@ -16,7 +16,9 @@ from django.contrib.auth.tokens import default_token_generator from django.contrib.auth import authenticate, login from django.shortcuts import render -from .customer_forms import RegisterForm, CustomerLoginForm +from .customer_forms import RegisterForm +from .customer_forms import CustomerLoginForm +from .customer_forms import CustomerProfileForm from django.contrib.auth import logout from django.shortcuts import redirect @@ -127,4 +129,27 @@ def customer_dashboard(request): def logout_view(request): logout(request) messages.success(request, "You have been logged out successfully.") - return redirect("login") \ No newline at end of file + return redirect("login") + + +@login_required(login_url="login") +def customer_calendar(request): + return render(request, "customer/customer_calendar.html") + + + +# ...existing imports... + +@login_required(login_url="login") +def customer_profile(request): + user = request.user + if request.method == "POST": + form = CustomerProfileForm(request.POST, instance=user) + if form.is_valid(): + form.save() + messages.success(request, "Profile updated.") + return redirect("customer_profile") + else: + form = CustomerProfileForm(instance=user) + + return render(request, "customer/customer_profile.html", {"form": form}) \ No newline at end of file diff --git a/db_reset.py b/db_reset.py new file mode 100644 index 0000000..26e6ab8 --- /dev/null +++ b/db_reset.py @@ -0,0 +1,40 @@ +# reset_db.py +import os +import sys +import django +from django.core.management import call_command +from django.db import connection + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eventify.settings") +django.setup() + +def reset_postgres_schema(): + user = connection.settings_dict.get("USER", "postgres") + with connection.cursor() as cursor: + cursor.execute(""" + DROP SCHEMA public CASCADE; + CREATE SCHEMA public; + GRANT ALL ON SCHEMA public TO {user}; + GRANT ALL ON SCHEMA public TO public; + """.format(user=user)) + +def main(): + if "--force" not in sys.argv: + print("Refusing to run without --force (this DROPS ALL TABLES).") + sys.exit(1) + + engine = connection.settings_dict.get("ENGINE", "") + if "postgresql" not in engine: + print("This script is intended for PostgreSQL. Aborting.") + sys.exit(1) + + print("Dropping all tables by recreating public schema…") + reset_postgres_schema() + + print("Running migrations…") + call_command("migrate", interactive=False) + + print("Done.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/eventify/settings.py b/eventify/settings.py index b368aeb..c5e6eeb 100644 --- a/eventify/settings.py +++ b/eventify/settings.py @@ -26,19 +26,29 @@ INSTALLED_APPS = [ 'events', 'accounts', 'templatetags', - 'mobile_web_api', + 'mobile_api', + 'web_api', 'rest_framework', 'rest_framework.authtoken' ] +INSTALLED_APPS += [ + "corsheaders", +] + MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', +] +CORS_ALLOWED_ORIGINS = [ + "http://localhost:5173", ] ROOT_URLCONF = 'eventify.urls' @@ -61,24 +71,24 @@ TEMPLATES = [ WSGI_APPLICATION = 'eventify.wsgi.application' -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.sqlite3', -# 'NAME': BASE_DIR / 'db.sqlite3', -# } -# } - DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'eventify_uat_db', # your DB name - 'USER': 'eventify_uat', # your DB user - 'PASSWORD': 'eventifyplus@!@#$', # your DB password - 'HOST': '0.0.0.0', # or IP/domain - 'PORT': '5440', # default PostgreSQL port + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', } } +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.postgresql', +# 'NAME': 'eventify_uat_db', # your DB name +# 'USER': 'eventify_uat', # your DB user +# 'PASSWORD': 'eventifyplus@!@#$', # your DB password +# 'HOST': '0.0.0.0', # or IP/domain +# 'PORT': '5440', # default PostgreSQL port +# } +# } + AUTH_PASSWORD_VALIDATORS = [ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, diff --git a/eventify/urls.py b/eventify/urls.py index 926eb7c..31e173b 100644 --- a/eventify/urls.py +++ b/eventify/urls.py @@ -1,23 +1,37 @@ from django.contrib import admin from django.urls import path, include from django.contrib.auth import views as auth_views -from accounts.views import dashboard -from accounts.customer_views import RegisterView -from accounts.customer_views import login_view, logout_view, customer_dashboard +# from accounts.views import dashboard, login_view, logout_view, UserListView, UserCreateView, UserUpdateView, UserDeleteView +# from accounts.customer_views import RegisterView +# from accounts.customer_views import login_view, logout_view, customer_dashboard, customer_calendar +# from accounts.customer_views import customer_profile +from accounts import views from django.conf.urls.static import static from django.conf import settings urlpatterns = [ path('admin/', admin.site.urls), - path("", login_view, name="login"), - path("logout/", logout_view, name="logout"), - path("register/", RegisterView.as_view(), name="register"), - path('dashboard/', customer_dashboard, name='customer_dashboard'), + # path("", login_view, name="login"), + # path("logout/", logout_view, name="logout"), + # path("register/", RegisterView.as_view(), name="register"), + # path('dashboard/', customer_dashboard, name='customer_dashboard'), + # path('calendar/', customer_calendar, name='customer_calendar'), + # path('profile/', customer_profile, name='customer_profile'), + + + path('', views.login_view, name='login'), + path('logout/', views.logout_view, name='logout'), + path('dashboard/', views.dashboard, name='dashboard'), + path('users/', views.UserListView.as_view(), name='user_list'), + path('users/add/', views.UserCreateView.as_view(), name='user_add'), + path('users//edit/', views.UserUpdateView.as_view(), name='user_edit'), + path('users//delete/', views.UserDeleteView.as_view(), name='user_delete'), path('master-data/', include('master_data.urls')), path('events/', include('events.urls')), path('accounts/', include('accounts.urls')), - path('api/', include('mobile_web_api.urls')), + path('api/', include('mobile_api.urls')), + # path('web-api/', include('web_api.urls')), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/mobile_web_api/__init__.py b/mobile_api/__init__.py similarity index 100% rename from mobile_web_api/__init__.py rename to mobile_api/__init__.py diff --git a/mobile_web_api/admin.py b/mobile_api/admin.py similarity index 100% rename from mobile_web_api/admin.py rename to mobile_api/admin.py diff --git a/mobile_web_api/apps.py b/mobile_api/apps.py similarity index 58% rename from mobile_web_api/apps.py rename to mobile_api/apps.py index 5d3a13b..b902e8a 100644 --- a/mobile_web_api/apps.py +++ b/mobile_api/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class MobileWebApiConfig(AppConfig): +class MobileApiConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'mobile_web_api' + name = 'mobile_api' diff --git a/mobile_web_api/forms/__init__.py b/mobile_api/forms/__init__.py similarity index 100% rename from mobile_web_api/forms/__init__.py rename to mobile_api/forms/__init__.py diff --git a/mobile_web_api/forms/event_forms.py b/mobile_api/forms/event_forms.py similarity index 100% rename from mobile_web_api/forms/event_forms.py rename to mobile_api/forms/event_forms.py diff --git a/mobile_api/forms/user_forms.py b/mobile_api/forms/user_forms.py new file mode 100644 index 0000000..2b7c7ab --- /dev/null +++ b/mobile_api/forms/user_forms.py @@ -0,0 +1,75 @@ +# accounts/forms.py +from django import forms +from django.contrib.auth import get_user_model +from django.contrib.auth import authenticate + +User = get_user_model() + + +class RegisterForm(forms.ModelForm): + password = forms.CharField(widget=forms.PasswordInput) + + class Meta: + model = User + fields = ['email', 'phone_number', 'password'] + + def clean_email(self): + email = self.cleaned_data.get('email') + if User.objects.filter(email=email).exists(): + raise forms.ValidationError("Email is already registered.") + return email + + def clean_phone_number(self): + phone_number = self.cleaned_data.get('phone_number') + if User.objects.filter(phone_number=phone_number).exists(): + raise forms.ValidationError("Phone number is already registered.") + return phone_number + + def save(self, commit=True): + user = super().save(commit=False) + user.set_password(self.cleaned_data['password']) + if commit: + user.save() + return user + + +class LoginForm(forms.Form): + username = forms.CharField() + password = forms.CharField(widget=forms.PasswordInput) + + def clean(self): + cleaned_data = super().clean() + username = cleaned_data.get('username') + password = cleaned_data.get('password') + + print('*' * 100) + print(username, password) + print('*' * 100) + + if not username or not password: + raise forms.ValidationError("Username and password are required.") + + # Check if username contains '@' (email) or is a regular username + try: + if '@' in username: + print('1 **********************') + # Try to find user by email + user = User.objects.get(email=username) + print(user) + print('2 **********************') + username = user.username + print('3 **********************') + else: + print('4 **********************')# Use username as-is + user = User.objects.get(username=username) + except User.DoesNotExist: + print('5 **********************') + raise forms.ValidationError("Invalid credentials.") + + # Authenticate with the resolved username + user = authenticate(username=username, password=password) + if not user: + raise forms.ValidationError("Invalid credentials.") + + cleaned_data['user'] = user + return cleaned_data diff --git a/mobile_web_api/migrations/__init__.py b/mobile_api/migrations/__init__.py similarity index 100% rename from mobile_web_api/migrations/__init__.py rename to mobile_api/migrations/__init__.py diff --git a/mobile_web_api/models.py b/mobile_api/models.py similarity index 100% rename from mobile_web_api/models.py rename to mobile_api/models.py diff --git a/mobile_web_api/tests.py b/mobile_api/tests.py similarity index 100% rename from mobile_web_api/tests.py rename to mobile_api/tests.py diff --git a/mobile_web_api/urls.py b/mobile_api/urls.py similarity index 68% rename from mobile_web_api/urls.py rename to mobile_api/urls.py index 864b27d..0a71339 100644 --- a/mobile_web_api/urls.py +++ b/mobile_api/urls.py @@ -17,5 +17,7 @@ urlpatterns += [ path('events/pincode-events/', EventListAPI.as_view()), path('events/event-details/', EventDetailAPI.as_view()), path('events/event-images/', EventImagesListAPI.as_view()), - path('events/events-by-category//', api_events_by_category, name='api_events_by_category'), + path('events/events-by-category/', EventsByCategoryAPI.as_view(), name='api_events_by_category'), + path('events/events-by-month-year/', EventsByMonthYearAPI.as_view(), name='events_by_month_year'), + path('events/events-by-date/', EventsByDateAPI.as_view(), name='events_by_date'), ] diff --git a/mobile_api/utils.py b/mobile_api/utils.py new file mode 100644 index 0000000..7b06306 --- /dev/null +++ b/mobile_api/utils.py @@ -0,0 +1,90 @@ +""" +Utility functions for mobile API authentication and validation. +""" +import json +from django.http import JsonResponse +from rest_framework.authtoken.models import Token +from accounts.models import User + + +def validate_token_and_get_user(request, error_status_code=None): + """ + Validates token and username from request body. + + This function handles: + - JSON parsing from request body + - Token and username extraction + - Token validation + - Username verification against token user + + Args: + request: Django request object with JSON body containing 'token' and 'username' + error_status_code: Optional HTTP status code for error responses (default: None) + + Returns: + tuple: On success, returns (user, token, data, None) + tuple: On error, returns (None, None, None, JsonResponse) + + Error Responses: + - Invalid JSON: {"status": "error", "message": "Invalid JSON"} (400 if status_code provided) + - Missing credentials: {"status": "error", "message": "token and username required"} (400 if status_code provided) + - Invalid token: {"status": "invalid_token"} (401 if status_code provided) + - Username mismatch: {"status": "error", "message": "token does not match user"} (401 if status_code provided) + """ + try: + # Parse JSON from request body + data = json.loads(request.body) + except json.JSONDecodeError: + status = 400 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "error", "message": "Invalid JSON"}, + status=status + )) + + # Extract token and username + token_key = data.get("token") + username = data.get("username") + + # Validate both are present + if not token_key or not username: + status = 400 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "error", "message": "token and username required"}, + status=status + )) + + try: + # Get token object + token = Token.objects.get(key=token_key) + + if username: + if '@' in username: + user = User.objects.get(email=username) + else: + user = User.objects.get(username=username) + + if not user: + status = 401 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "error", "message": "user not found"}, + status=status + )) + + # Verify username matches token user + # if user.username != username: + # status = 401 if error_status_code else None + # return (None, None, None, JsonResponse( + # {"status": "error", "message": "token does not match user"}, + # status=status + # )) + + # Success - return user, token, data, and None for error_response + return (user, token, data, None) + + except Token.DoesNotExist: + status = 401 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "invalid_token"}, + status=status + )) + diff --git a/mobile_web_api/views/__init__.py b/mobile_api/views/__init__.py similarity index 100% rename from mobile_web_api/views/__init__.py rename to mobile_api/views/__init__.py diff --git a/mobile_api/views/events.py b/mobile_api/views/events.py new file mode 100644 index 0000000..5103f21 --- /dev/null +++ b/mobile_api/views/events.py @@ -0,0 +1,361 @@ +from django.http import JsonResponse +from rest_framework.views import APIView +from rest_framework.authentication import TokenAuthentication +from rest_framework.permissions import IsAuthenticated +from events.models import Event, EventImages +from master_data.models import EventType +from django.forms.models import model_to_dict +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt +from django.db.models import Q +from datetime import datetime, timedelta +import calendar +from mobile_api.utils import validate_token_and_get_user + + +@method_decorator(csrf_exempt, name='dispatch') +class EventTypeListAPIView(APIView): + + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + # Fetch event types manually without serializer + event_types_queryset = EventType.objects.all() + event_types = [] + + for event_type in event_types_queryset: + event_type_data = { + "id": event_type.id, + "event_type": event_type.event_type, + "event_type_icon": request.build_absolute_uri(event_type.event_type_icon.url) if event_type.event_type_icon else None + } + event_types.append(event_type_data) + + print(event_types) + + return JsonResponse({ + "status": "success", + "event_types": event_types + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +class EventListAPI(APIView): + + def post(self, request): + try: + print('*' * 100) + print(request.body) + print('*' * 100) + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + pincode = data.get("pincode") + print('*' * 100) + print(pincode) + print('*' * 100) + # pincode is optional - if not provided or 'all', return all events + if not pincode or pincode == 'all': + events = Event.objects.all().order_by('-created_date') + else: + events = Event.objects.filter(pincode=pincode).order_by('-created_date') + + event_list = [] + + for e in events: + data_dict = model_to_dict(e) + try: + thumb_img = EventImages.objects.get(event=e.id, is_primary=True) + data_dict['thumb_img'] = request.build_absolute_uri(thumb_img.event_image.url) + except EventImages.DoesNotExist: + data_dict['thumb_img'] = '' + + event_list.append(data_dict) + + print('*' * 100) + print(event_list) + print('*' * 100) + + return JsonResponse({ + "status": "success", + "events": event_list + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +class EventDetailAPI(APIView): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + event_id = data.get("event_id") + + events = Event.objects.get(id=event_id) + event_images = EventImages.objects.filter(event=event_id) + event_data = model_to_dict(events) + event_data["status"] = "success" + event_images_list = [] + for ei in event_images: + event_img = {} + event_img['is_primary'] = ei.is_primary + event_img['image'] = request.build_absolute_uri(ei.event_image.url) + event_images_list.append(event_img) + event_data["images"] = event_images_list + + print(event_data) + + return JsonResponse(event_data) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +class EventImagesListAPI(APIView): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + event_id = data.get("event_id") + + event_images = EventImages.objects.filter(event=event_id) + res_data = {} + res_data["status"] = "success" + event_images_list = [] + for ei in event_images: + event_images_list.append(request.build_absolute_uri(ei.event_image.url)) + + res_data["images"] = event_images_list + + print(res_data) + + return JsonResponse(res_data) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +@method_decorator(csrf_exempt, name='dispatch') +class EventsByCategoryAPI(APIView): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + category_id = data.get("category_id") + + if not category_id: + return JsonResponse( + {"status": "error", "message": "category_id is required"} + ) + + events = Event.objects.filter(event_type=category_id) + events_dict = [model_to_dict(obj) for obj in events] + + for event in events_dict: + try: + event['event_image'] = request.build_absolute_uri( + EventImages.objects.get(event=event['id'], is_primary=True).event_image.url + ) + except EventImages.DoesNotExist: + event['event_image'] = '' + # event['start_date'] = convert_date_to_dd_mm_yyyy(event['start_date']) + print(events_dict) + + return JsonResponse({ + "status": "success", + "events": events_dict + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +@method_decorator(csrf_exempt, name='dispatch') +class EventsByMonthYearAPI(APIView): + """ + API to get events by month and year. + Returns dates that have events, total count, and date-wise breakdown. + """ + + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + month_name = data.get("month") # e.g., "August", "august", "Aug" + year = data.get("year") # e.g., 2025 + + if not month_name or not year: + return JsonResponse( + {"status": "error", "message": "month and year are required"} + ) + + # Convert month name to month number + month_name_lower = month_name.lower().capitalize() + month_abbr = month_name_lower[:3] + + # Try full month name first, then abbreviation + month_number = None + for i in range(1, 13): + if calendar.month_name[i].lower() == month_name_lower or calendar.month_abbr[i].lower() == month_abbr.lower(): + month_number = i + break + + if not month_number: + return JsonResponse( + {"status": "error", "message": f"Invalid month name: {month_name}"} + ) + + # Convert year to integer + try: + year = int(year) + except (ValueError, TypeError): + return JsonResponse( + {"status": "error", "message": "Invalid year format"} + ) + + # Filter events where start_date or end_date falls in the given month/year + # An event is included if any part of it (start_date to end_date) overlaps with the month + events = Event.objects.filter( + Q(start_date__year=year, start_date__month=month_number) | + Q(end_date__year=year, end_date__month=month_number) | + Q(start_date__lte=datetime(year, month_number, 1).date(), + end_date__gte=datetime(year, month_number, calendar.monthrange(year, month_number)[1]).date()) + ).distinct() + + # Group events by date + date_events_dict = {} + all_dates = set() + + # Calculate month boundaries + month_start = datetime(year, month_number, 1).date() + month_end = datetime(year, month_number, calendar.monthrange(year, month_number)[1]).date() + + for event in events: + # Get all dates between start_date and end_date that fall in the target month + current_date = max(event.start_date, month_start) + end_date = min(event.end_date, month_end) + + # Iterate through each date in the event's date range that falls in the target month + while current_date <= end_date: + if current_date.year == year and current_date.month == month_number: + date_str = current_date.strftime('%Y-%m-%d') + all_dates.add(date_str) + if date_str not in date_events_dict: + date_events_dict[date_str] = 0 + date_events_dict[date_str] += 1 + + # Move to next day + current_date += timedelta(days=1) + + # Sort dates + sorted_dates = sorted(all_dates) + + # Build date_events list + date_events = [ + { + "date_of_event": date_str, + "events_of_date": date_events_dict[date_str] + } + for date_str in sorted_dates + ] + + # Calculate total number of events (unique events, not date occurrences) + total_events = events.count() + + print(sorted_dates) + print(total_events) + print(date_events) + + return JsonResponse({ + "status": "success", + "dates": sorted_dates, + "total_number_of_events": total_events, + "date_events": date_events + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +@method_decorator(csrf_exempt, name='dispatch') +class EventsByDateAPI(APIView): + """ + API to get events occurring on a specific date. + Returns complete event information with primary images. + """ + + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + date_of_event = data.get("date_of_event") + + if not date_of_event: + return JsonResponse( + {"status": "error", "message": "date_of_event is required"} + ) + + # Parse date_of_event in YYYY-MM-DD format + try: + event_date = datetime.strptime(date_of_event, "%Y-%m-%d").date() + except ValueError: + return JsonResponse( + {"status": "error", "message": "Invalid date format. Expected YYYY-MM-DD"} + ) + + # Filter events where the provided date falls between start_date and end_date (inclusive) + events = Event.objects.filter( + start_date__lte=event_date, + end_date__gte=event_date + ).order_by('start_date', 'start_time') + + event_list = [] + + for e in events: + data_dict = model_to_dict(e) + try: + thumb_img = EventImages.objects.get(event=e.id, is_primary=True) + data_dict['thumb_img'] = request.build_absolute_uri(thumb_img.event_image.url) + except EventImages.DoesNotExist: + data_dict['thumb_img'] = '' + + event_list.append(data_dict) + + return JsonResponse({ + "status": "success", + "events": event_list + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) \ No newline at end of file diff --git a/mobile_api/views/user.py b/mobile_api/views/user.py new file mode 100644 index 0000000..5b3dab6 --- /dev/null +++ b/mobile_api/views/user.py @@ -0,0 +1,107 @@ +# accounts/views.py +import json +from django.views.decorators.csrf import csrf_exempt +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from rest_framework.authtoken.models import Token +from mobile_api.forms import RegisterForm, LoginForm +from rest_framework.authentication import TokenAuthentication +from django.contrib.auth import logout +from mobile_api.utils import validate_token_and_get_user +from utils.errors_json_convertor import simplify_form_errors + + +@method_decorator(csrf_exempt, name='dispatch') +class RegisterView(View): + def post(self, request): + try: + data = json.loads(request.body) + form = RegisterForm(data) + if form.is_valid(): + user = form.save() + token, _ = Token.objects.get_or_create(user=user) + return JsonResponse({'message': 'User registered successfully', 'token': token.key}, status=201) + return JsonResponse({'errors': form.errors}, status=400) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + + +@method_decorator(csrf_exempt, name='dispatch') +class LoginView(View): + def post(self, request): + print('0') + try: + data = json.loads(request.body) + form = LoginForm(data) + print('1') + if form.is_valid(): + print('2') + user = form.cleaned_data['user'] + token, _ = Token.objects.get_or_create(user=user) + print('3') + response = { + 'message': 'Login successful', + 'token': token.key, + 'username': user.username, + 'email': user.email, + 'phone_number': user.phone_number, + 'first_name': user.first_name, + 'last_name': user.last_name, + 'role': user.role, + 'pincode': user.pincode, + 'district': user.district, + 'state': user.state, + 'country': user.country, + 'place': user.place, + 'latitude': user.latitude, + 'longitude': user.longitude, + } + print('4') + print(response) + return JsonResponse(response, status=200) + + return JsonResponse(simplify_form_errors(form), status=401) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + + +@method_decorator(csrf_exempt, name='dispatch') +class StatusView(View): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) + if error_response: + return error_response + + return JsonResponse({ + "status": "logged_in", + "username": user.username, + "email": user.email + }) + + except Exception as e: + return JsonResponse({"status": "error", "message": str(e)}, status=500) + + +@method_decorator(csrf_exempt, name='dispatch') +class LogoutView(View): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) + if error_response: + return error_response + + # πŸ” Call Django's built-in logout + logout(request) + + # πŸ—‘ Delete the token to invalidate future access + token.delete() + + return JsonResponse({ + "status": "logged_out", + "message": "Logout successful" + }) + + except Exception as e: + return JsonResponse({"status": "error", "message": str(e)}, status=500) diff --git a/mobile_web_api/forms/user_forms.py b/mobile_web_api/forms/user_forms.py deleted file mode 100644 index 0bab4a5..0000000 --- a/mobile_web_api/forms/user_forms.py +++ /dev/null @@ -1,42 +0,0 @@ -# accounts/forms.py -from django import forms -from django.contrib.auth import get_user_model -from django.contrib.auth import authenticate - -User = get_user_model() - - -class RegisterForm(forms.ModelForm): - password = forms.CharField(widget=forms.PasswordInput) - - class Meta: - model = User - fields = ['email', 'phone_number', 'password'] - - def clean_email(self): - phone_number = self.cleaned_data.get('phone_number') - if User.objects.filter(phone_number=phone_number).exists(): - raise forms.ValidationError("phone_number is already registered.") - return phone_number - - def save(self, commit=True): - user = super().save(commit=False) - user.set_password(self.cleaned_data['password']) - if commit: - user.save() - return user - - -class LoginForm(forms.Form): - username = forms.CharField() - password = forms.CharField(widget=forms.PasswordInput) - - def clean(self): - cleaned_data = super().clean() - username = cleaned_data.get('username') - password = cleaned_data.get('password') - user = authenticate(username=username, password=password) - if not user: - raise forms.ValidationError("Invalid credentials.") - cleaned_data['user'] = user - return cleaned_data diff --git a/mobile_web_api/views/events.py b/mobile_web_api/views/events.py deleted file mode 100644 index fc7ce50..0000000 --- a/mobile_web_api/views/events.py +++ /dev/null @@ -1,231 +0,0 @@ -from django.http import JsonResponse -from rest_framework.views import APIView -from rest_framework.authentication import TokenAuthentication -from rest_framework.permissions import IsAuthenticated -from events.models import Event, EventImages -from rest_framework.authtoken.models import Token -from master_data.models import EventType -from django.forms.models import model_to_dict -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_exempt -import json - -from django.contrib.auth.decorators import login_required - - -@method_decorator(csrf_exempt, name='dispatch') -class EventTypeListAPIView(APIView): - - def post(self, request): - try: - # Manually load JSON because we are not using parsers - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - # Fetch event types manually without serializer - event_types = list(EventType.objects.values("id", "event_type")) - - return JsonResponse({ - "status": "success", - "event_types": event_types - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - return JsonResponse( - {"status": "error", "message": str(e)}, - ) - - -class EventListAPI(APIView): - - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - pincode = data.get("pincode") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - events = Event.objects.filter(pincode=pincode).order_by('-created_date') - event_list = [] - - for e in events: - data_dict = model_to_dict(e) - print('*' * 10) - print(e.id) - print('*' * 10) - try: - thumb_img = EventImages.objects.get(event=e.id, is_primary=True) - data_dict['thumb_img'] = request.build_absolute_uri(thumb_img.event_image.url) - except EventImages.DoesNotExist: - data_dict['thumb_img'] = '' - - event_list.append(data_dict) - - return JsonResponse({ - "status": "success", - "events": event_list - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - return JsonResponse( - {"status": "error", "message": str(e)}, - ) - - -class EventDetailAPI(APIView): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - event_id = data.get("event_id") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - events = Event.objects.get(id=event_id) - event_images = EventImages.objects.filter(event=event_id) - data = model_to_dict(events) - data["status"] = "success" - event_images_list = [] - for ei in event_images: - event_img = {} - event_img['is_primary'] = ei.is_primary - event_img['image'] = request.build_absolute_uri(ei.event_image.url) - event_images_list.append(event_img) - data["images"] = event_images_list - - return JsonResponse(data) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - return JsonResponse( - {"status": "error", "message": str(e)}, - ) - - -class EventImagesListAPI(APIView): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - event_id = data.get("event_id") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - event_images = EventImages.objects.filter(event=event_id) - res_data = {} - res_data["status"] = "success" - event_images_list = [] - for ei in event_images: - event_images_list.append(request.build_absolute_uri(ei.event_image.url)) - - res_data["images"] = event_images_list - - return JsonResponse(res_data) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - return JsonResponse( - {"status": "error", "message": str(e)}, - ) - - -@login_required(login_url="login") -def api_events_by_category(request, slug): - events = Event.objects.filter(event_type=slug) - - events_dict = [model_to_dict(obj) for obj in events] - - for event in events_dict: - event['event_image'] = EventImages.objects.get(event=event['id'], is_primary=True).event_image.url - # event['start_date'] = convert_date_to_dd_mm_yyyy(event['start_date']) - - return JsonResponse({"events": events_dict}) \ No newline at end of file diff --git a/mobile_web_api/views/user.py b/mobile_web_api/views/user.py deleted file mode 100644 index d094896..0000000 --- a/mobile_web_api/views/user.py +++ /dev/null @@ -1,124 +0,0 @@ -# accounts/views.py -import json -from django.views.decorators.csrf import csrf_exempt -from django.http import JsonResponse -from django.utils.decorators import method_decorator -from django.views import View -from rest_framework.authtoken.models import Token -from mobile_web_api.forms import RegisterForm, LoginForm -from rest_framework.authentication import TokenAuthentication -from django.contrib.auth import logout - - -@method_decorator(csrf_exempt, name='dispatch') -class RegisterView(View): - def post(self, request): - try: - data = json.loads(request.body) - form = RegisterForm(data) - if form.is_valid(): - user = form.save() - token, _ = Token.objects.get_or_create(user=user) - return JsonResponse({'message': 'User registered successfully', 'token': token.key}, status=201) - return JsonResponse({'errors': form.errors}, status=400) - except Exception as e: - return JsonResponse({'error': str(e)}, status=500) - - -@method_decorator(csrf_exempt, name='dispatch') -class LoginView(View): - def post(self, request): - try: - data = json.loads(request.body) - form = LoginForm(data) - if form.is_valid(): - user = form.cleaned_data['user'] - token, _ = Token.objects.get_or_create(user=user) - return JsonResponse({'message': 'Login successful', 'token': token.key}) - return JsonResponse({'errors': form.errors}, status=401) - except Exception as e: - return JsonResponse({'error': str(e)}, status=500) - - -@method_decorator(csrf_exempt, name='dispatch') -class StatusView(View): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"}, - status=400 - ) - - try: - token = Token.objects.get(key=token_key) - - if token.user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"}, - status=401 - ) - - return JsonResponse({ - "status": "logged_in", - "username": token.user.username, - "email": token.user.email - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}, status=401) - - except json.JSONDecodeError: - return JsonResponse({"status": "error", "message": "Invalid JSON"}, status=400) - except Exception as e: - return JsonResponse({"status": "error", "message": str(e)}, status=500) - - -@method_decorator(csrf_exempt, name='dispatch') -class LogoutView(View): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"}, - status=400 - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"}, - status=401 - ) - - # πŸ” Call Django's built-in logout - logout(request) - - # πŸ—‘ Delete the token to invalidate future access - token.delete() - - return JsonResponse({ - "status": "logged_out", - "message": "Logout successful" - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}, status=401) - - except json.JSONDecodeError: - return JsonResponse({"status": "error", "message": "Invalid JSON"}, status=400) - except Exception as e: - return JsonResponse({"status": "error", "message": str(e)}, status=500) diff --git a/pg_to_sqlite_backup.py b/pg_to_sqlite_backup.py new file mode 100644 index 0000000..a8fca89 --- /dev/null +++ b/pg_to_sqlite_backup.py @@ -0,0 +1,80 @@ +# pg_to_sqlite_backup.py +import os +import sys +import django +from django.conf import settings +from django.core.management import call_command +from django.db import connections, DEFAULT_DB_ALIAS + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eventify.settings") +django.setup() + +SQLITE_PATH = "backup.sqlite3" +BACKUP_ALIAS = "backup" + + +def ensure_backup_db(): + # Add a SQLite backup database alias + settings.DATABASES[BACKUP_ALIAS] = { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.abspath(SQLITE_PATH), + } + # Remove existing file so migrate creates a fresh DB + if os.path.exists(SQLITE_PATH): + os.remove(SQLITE_PATH) + + # Run migrations on the backup DB + print("Running migrations on backup (SQLite)…") + call_command("migrate", database=BACKUP_ALIAS, interactive=False, verbosity=0) + print("SQLite schema created at", SQLITE_PATH) + + +def copy_all_tables(): + pg_conn = connections[DEFAULT_DB_ALIAS] # default = PostgreSQL + sqlite_conn = connections[BACKUP_ALIAS] # new SQLite + + pg_cursor = pg_conn.cursor() + sqlite_cursor = sqlite_conn.cursor() + + tables = pg_conn.introspection.table_names() + + for table in tables: + # Fetch column names from PostgreSQL + cols = [c.name for c in pg_conn.introspection.get_table_description(pg_cursor, table)] + col_list = ", ".join(f'"{c}"' for c in cols) + placeholders = ", ".join(["?"] * len(cols)) + + # Fetch all rows + pg_cursor.execute(f'SELECT {col_list} FROM "{table}"') + rows = pg_cursor.fetchall() + + # Insert into SQLite + if rows: + insert_sql = f'INSERT INTO "{table}" ({col_list}) VALUES ({placeholders})' + sqlite_cursor.executemany(insert_sql, rows) + + print(f"Copied {len(rows)} rows from {table}") + + sqlite_conn.commit() + pg_cursor.close() + sqlite_cursor.close() + + +def main(): + if "--force" not in sys.argv: + print("Refusing to run without --force (destructive for existing backup file).") + sys.exit(1) + + # Safety: ensure default DB is PostgreSQL + engine = settings.DATABASES[DEFAULT_DB_ALIAS]["ENGINE"] + if "postgresql" not in engine: + print("Default database is not PostgreSQL. Aborting.") + sys.exit(1) + + ensure_backup_db() + copy_all_tables() + print("Done. SQLite backup at", SQLITE_PATH) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/templates/accounts/login.html b/templates/accounts/login.html index b19eedb..551ea81 100644 --- a/templates/accounts/login.html +++ b/templates/accounts/login.html @@ -15,6 +15,8 @@
+

Welcome To Eventify Admin Panel

+

Login

{% if messages %} diff --git a/templates/customer/base_dashboard.html b/templates/customer/base_dashboard.html index 442b5cb..449f3c1 100644 --- a/templates/customer/base_dashboard.html +++ b/templates/customer/base_dashboard.html @@ -1,9 +1,8 @@ - + - @@ -26,36 +25,44 @@ .sidebar { width: 260px; background: linear-gradient(180deg, #1e3cfa, #1436c7); - color: white; + color: #fff; padding: 30px 20px; display: flex; flex-direction: column; } + .nav-item { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 30px; + border-radius: 12px; + margin-bottom: 8px; + font-size: 15px; + line-height: 1.5; + text-decoration: none; + color: #000000; + /* force white text */ + background: transparent; + } + + .nav-item:hover, + .nav-item.active { + background: #fff; + color: #000000; + padding: 16px 30px; + } + + .nav-item:visited { + color: #000000; + } + .logo { font-size: 28px; font-weight: 700; margin-bottom: 40px; } - .nav-item { - padding: 12px 15px; - border-radius: 12px; - display: flex; - align-items: center; - gap: 12px; - cursor: pointer; - margin-bottom: 8px; - font-size: 15px; - transition: 0.2s; - } - - .nav-item:hover, - .nav-item.active { - background: white; - color: #1436c7; - } - .bottom-nav { margin-top: auto; } @@ -146,9 +153,14 @@
diff --git a/templates/customer/customer_calendar.html b/templates/customer/customer_calendar.html new file mode 100644 index 0000000..562b0da --- /dev/null +++ b/templates/customer/customer_calendar.html @@ -0,0 +1,256 @@ +{% extends "customer/base_dashboard.html" %} +{% load static %} + +{% block content %} + + + + + +
+ + +
+ +
+ + +
+

{{ month_name }}

+

{{ year }}

+
+ + +
+ + +
+
M
T
W
T
F
+
S
+
S
+
+ + +
+ {% for week in calendar_weeks %} + {% for day in week %} + {% if day.month == current_month %} +
+ {% if day.day == selected_day %} +
+ {% endif %} + + {{ day.day }} + + {% if day.events %} +
+ {% for ev in day.events %} + + {% endfor %} +
+ {% endif %} +
+ {% else %} +
{{ day.day }}
+ {% endif %} + {% endfor %} + {% endfor %} +
+
+ + +
+ +
+
{{ selected_day }} {{ selected_month_short }}
+
+

+ {{ selected_date_verbose }} +

+

+ {{ events|length }} Events +

+
+
+ + {% for event in events %} +
+ +
+

{{ event.title }}

+
+ πŸ“… {{ event.date }} + πŸ“ {{ event.location }} +
+
+
+ {% endfor %} + +
+
+ + +{% endblock %} diff --git a/templates/customer/customer_profile.html b/templates/customer/customer_profile.html new file mode 100644 index 0000000..3fae7f5 --- /dev/null +++ b/templates/customer/customer_profile.html @@ -0,0 +1,33 @@ +{% extends "customer/base_dashboard.html" %} +{% load static %} + +{% block content %} + +
+

Profile

+
+ {% csrf_token %} + {% if form.non_field_errors %} +
{{ form.non_field_errors }}
+ {% endif %} + +
+ {% for field in form %} +
+
+ + {{ field }} + {% if field.errors %} +
{{ field.errors|striptags }}
+ {% endif %} +
+
+ {% endfor %} +
+ +
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/customer/sampl.html b/templates/customer/sampl.html new file mode 100644 index 0000000..e69de29 diff --git a/utils/errors_json_convertor.py b/utils/errors_json_convertor.py new file mode 100644 index 0000000..166f582 --- /dev/null +++ b/utils/errors_json_convertor.py @@ -0,0 +1,47 @@ +def simplify_errors(error_response): + """ + Convert nested 'errors' dict like: + { + "errors": { + "__all__": ["Invalid credentials."] + } + } + into: + { + "errors": "Invalid credentials." + } + """ + errors = error_response.get("errors", {}) + + # Collect all messages into a flat list + messages = [] + if isinstance(errors, dict): + for field_errors in errors.values(): + if isinstance(field_errors, (list, tuple)): + messages.extend(str(msg) for msg in field_errors) + else: + messages.append(str(field_errors)) + else: + # If 'errors' is not a dict, just stringify it + messages.append(str(errors)) + + # Join multiple messages if needed + combined = " ".join(messages).strip() + return {"errors": combined} + + + +def simplify_form_errors(form): + """ + Flatten Django form.errors into a single 'errors' string. + """ + errors = form.errors # ErrorDict + messages = [] + + for field_errors in errors.values(): + # field_errors is usually an ErrorList (list-like) + for msg in field_errors: + messages.append(str(msg)) + + combined = " ".join(messages).strip() + return {"errors": combined} \ No newline at end of file diff --git a/web_api/__init__.py b/web_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_api/admin.py b/web_api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/web_api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/web_api/apps.py b/web_api/apps.py new file mode 100644 index 0000000..4d0aa3d --- /dev/null +++ b/web_api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WebApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'web_api' diff --git a/web_api/migrations/__init__.py b/web_api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_api/models.py b/web_api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/web_api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/web_api/tests.py b/web_api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/web_api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/web_api/views/__init__.py b/web_api/views/__init__.py new file mode 100644 index 0000000..2158289 --- /dev/null +++ b/web_api/views/__init__.py @@ -0,0 +1,2 @@ +from .user import * +from .events import * \ No newline at end of file diff --git a/web_api/views/events.py b/web_api/views/events.py new file mode 100644 index 0000000..e69de29