Reverting back to admin pages as login and updates in the mobile api
This commit is contained in:
889
API_Documentation.md
Normal file
889
API_Documentation.md
Normal file
@@ -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*
|
||||
|
||||
@@ -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
|
||||
@@ -60,3 +61,34 @@ class CustomerLoginForm(AuthenticationForm):
|
||||
"autocomplete": "current-password",
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
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"}),
|
||||
}
|
||||
@@ -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
|
||||
@@ -128,3 +130,26 @@ def logout_view(request):
|
||||
logout(request)
|
||||
messages.success(request, "You have been logged out successfully.")
|
||||
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})
|
||||
40
db_reset.py
Normal file
40
db_reset.py
Normal file
@@ -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()
|
||||
@@ -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'},
|
||||
|
||||
@@ -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/<int:pk>/edit/', views.UserUpdateView.as_view(), name='user_edit'),
|
||||
path('users/<int:pk>/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)
|
||||
|
||||
@@ -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'
|
||||
75
mobile_api/forms/user_forms.py
Normal file
75
mobile_api/forms/user_forms.py
Normal file
@@ -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
|
||||
@@ -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/<int:slug>/', 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'),
|
||||
]
|
||||
90
mobile_api/utils.py
Normal file
90
mobile_api/utils.py
Normal file
@@ -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
|
||||
))
|
||||
|
||||
361
mobile_api/views/events.py
Normal file
361
mobile_api/views/events.py
Normal file
@@ -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)},
|
||||
)
|
||||
107
mobile_api/views/user.py
Normal file
107
mobile_api/views/user.py
Normal file
@@ -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)
|
||||
@@ -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
|
||||
@@ -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})
|
||||
@@ -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)
|
||||
80
pg_to_sqlite_backup.py
Normal file
80
pg_to_sqlite_backup.py
Normal file
@@ -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()
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
<div class="card shadow-sm p-4">
|
||||
|
||||
<h2 class="text-center mb-3">Welcome To Eventify Admin Panel</h2>
|
||||
|
||||
<h4 class="text-center mb-3">Login</h4>
|
||||
|
||||
{% if messages %}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
||||
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -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 @@
|
||||
<div class="logo">EVENTIFY</div>
|
||||
|
||||
<div class="nav">
|
||||
<div class="nav-item active">🏠 Home</div>
|
||||
<div class="nav-item">📅 Calendar</div>
|
||||
<div class="nav-item">👤 Profile</div>
|
||||
<a class="nav-item {% if request.resolver_match.url_name == 'customer_dashboard' %}active{% endif %}"
|
||||
href="{% url 'customer_dashboard' %}">🏠 Home</a>
|
||||
|
||||
<a class="nav-item {% if request.resolver_match.url_name == 'customer_calendar' %}active{% endif %}"
|
||||
href="{% url 'customer_calendar' %}">📅 Calendar</a>
|
||||
|
||||
<a class="nav-item {% if request.resolver_match.url_name == 'customer_profile' %}active{% endif %}"
|
||||
href="{% url 'customer_profile' %}">👤 Profile</a>
|
||||
</div>
|
||||
|
||||
<div class="bottom-nav">
|
||||
|
||||
256
templates/customer/customer_calendar.html
Normal file
256
templates/customer/customer_calendar.html
Normal file
@@ -0,0 +1,256 @@
|
||||
{% extends "customer/base_dashboard.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<style>
|
||||
.calendar-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* LEFT CALENDAR PANEL */
|
||||
|
||||
.calendar-card {
|
||||
flex: 0.58;
|
||||
background: white;
|
||||
border-radius: 22px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.calendar-header button {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
color: #555;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-title h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.calendar-title p {
|
||||
margin: 0;
|
||||
color: #777;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.weekday-row,
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.weekday-row {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
height: 65px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.calendar-day.inactive {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.selected-day {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: #e7f0ff;
|
||||
border-radius: 14px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.calendar-day span {
|
||||
z-index: 2;
|
||||
font-weight: 600;
|
||||
color: #1a47d1;
|
||||
}
|
||||
|
||||
.day-icons {
|
||||
display: flex;
|
||||
gap: -8px;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.day-icons img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
/* RIGHT EVENTS PANEL */
|
||||
.events-card {
|
||||
flex: 0.42;
|
||||
background: white;
|
||||
border-radius: 22px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.events-header {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 25px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-tag {
|
||||
background: #1e3cfa;
|
||||
color: white;
|
||||
padding: 8px 14px;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.event-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #eee;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.event-card img {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.event-content {
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.event-content h4 {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.event-meta {
|
||||
font-size: 14px;
|
||||
color: #777;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<div class="calendar-wrapper">
|
||||
|
||||
<!-- LEFT CALENDAR -->
|
||||
<div class="calendar-card">
|
||||
|
||||
<div class="calendar-header">
|
||||
<button><</button>
|
||||
|
||||
<div class="calendar-title">
|
||||
<h2>{{ month_name }}</h2>
|
||||
<p>{{ year }}</p>
|
||||
</div>
|
||||
|
||||
<button>></button>
|
||||
</div>
|
||||
|
||||
<!-- Weekdays -->
|
||||
<div class="weekday-row">
|
||||
<div>M</div><div>T</div><div>W</div><div>T</div><div>F</div>
|
||||
<div style="color:red;">S</div>
|
||||
<div style="color:red;">S</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Grid -->
|
||||
<div class="calendar-grid">
|
||||
{% for week in calendar_weeks %}
|
||||
{% for day in week %}
|
||||
{% if day.month == current_month %}
|
||||
<div class="calendar-day">
|
||||
{% if day.day == selected_day %}
|
||||
<div class="selected-day"></div>
|
||||
{% endif %}
|
||||
|
||||
<span>{{ day.day }}</span>
|
||||
|
||||
{% if day.events %}
|
||||
<div class="day-icons">
|
||||
{% for ev in day.events %}
|
||||
<img src="{{ ev.icon }}">
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="calendar-day inactive">{{ day.day }}</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT EVENTS -->
|
||||
<div class="events-card">
|
||||
|
||||
<div class="events-header">
|
||||
<div class="date-tag">{{ selected_day }} {{ selected_month_short }}</div>
|
||||
<div>
|
||||
<h3 style="margin:0;font-size:18px;font-weight:700;">
|
||||
{{ selected_date_verbose }}
|
||||
</h3>
|
||||
<p style="margin:0;color:#777;font-size:14px;">
|
||||
{{ events|length }} Events
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for event in events %}
|
||||
<div class="event-card">
|
||||
<img src="{{ event.image }}">
|
||||
<div class="event-content">
|
||||
<h4>{{ event.title }}</h4>
|
||||
<div class="event-meta">
|
||||
📅 {{ event.date }}
|
||||
📍 {{ event.location }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
33
templates/customer/customer_profile.html
Normal file
33
templates/customer/customer_profile.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "customer/base_dashboard.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="card p-4">
|
||||
<h2 class="mb-3">Profile</h2>
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">{{ form.non_field_errors }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-3">
|
||||
{% for field in form %}
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.errors %}
|
||||
<div class="text-danger small">{{ field.errors|striptags }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
0
templates/customer/sampl.html
Normal file
0
templates/customer/sampl.html
Normal file
47
utils/errors_json_convertor.py
Normal file
47
utils/errors_json_convertor.py
Normal file
@@ -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}
|
||||
0
web_api/__init__.py
Normal file
0
web_api/__init__.py
Normal file
3
web_api/admin.py
Normal file
3
web_api/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
web_api/apps.py
Normal file
6
web_api/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WebApiConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'web_api'
|
||||
0
web_api/migrations/__init__.py
Normal file
0
web_api/migrations/__init__.py
Normal file
3
web_api/models.py
Normal file
3
web_api/models.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
web_api/tests.py
Normal file
3
web_api/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
2
web_api/views/__init__.py
Normal file
2
web_api/views/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .user import *
|
||||
from .events import *
|
||||
0
web_api/views/events.py
Normal file
0
web_api/views/events.py
Normal file
Reference in New Issue
Block a user