A comprehensive web application for managing event photography with AI-powered photo tagging, real-time notifications, and role-based access control.
- Overview
- Features
- Technology Stack
- Architecture
- Prerequisites
- Installation & Setup
- Running the Application
- Project Structure
- API Endpoints
- Feature Implementation Details
- User Roles & Permissions
- Contributing
Autumn Photo is a full-stack event photography management platform designed for the Information Management Group (IMG). The platform enables seamless event creation, photo uploads, AI-powered tagging, person identification, and collaborative photo management with role-based permissions.
Live Demo: https://mail.google.com/mail/u/0/#search/img?projector=1
- 🎭 Event Management: Create, edit, and manage events with cover photos, dates, and locations
- 📸 Photo Upload & Management: Bulk photo uploads with automatic thumbnail and display generation
- 🤖 AI-Powered Tagging: Automatic photo categorization using AI tags
- 👤 Person Recognition: Tag people in photos with manual tagging support
- 🔍 Advanced Search: Search photos by AI tags, tagged users, or event information
- 🔐 OAuth2 Authentication: Integrated with Omniport (Channeli) for secure authentication
- 🔔 Real-time Notifications: WebSocket-based notifications for photo uploads and tagging
- 🎨 Responsive UI: Modern, gradient-based design with dark theme
- Admin Panel: Complete CRUD operations for users and events
- Event Coordinator Dashboard: Edit and manage assigned events
- Photographer Portal: Upload and manage event photos
- Public Gallery: Browse public events and photos
- Framework: Django 6.0
- API: Django REST Framework (DRF)
- Authentication:
- djangorestframework-simplejwt (JWT tokens)
- Omniport OAuth2 integration
- WebSockets: Django Channels with Redis
- Async Tasks: Celery with Redis broker
- Database: SQLite (development), PostgreSQL-ready
- Image Processing: Pillow (thumbnails, watermarks, EXIF data)
- CORS: django-cors-headers
- Framework: React 18 with TypeScript
- Build Tool: Vite
- Styling: Tailwind CSS
- State Management: Redux Toolkit
- HTTP Client: Axios with interceptors
- Icons: Lucide React
- Routing: React Router v6
- Cache & Message Broker: Redis
- Task Queue: Celery
- WebSocket Layer: Channels with Redis channel layer
┌─────────────────┐
│ React Frontend │
│ (Vite + TS) │
└────────┬────────┘
│
│ HTTP/WebSocket
│
┌────────▼────────────────────────┐
│ Django Backend │
│ ┌──────────────────────────┐ │
│ │ REST API (DRF) │ │
│ ├──────────────────────────┤ │
│ │ WebSocket (Channels) │ │
│ ├──────────────────────────┤ │
│ │ Celery Tasks │ │
│ └──────────────────────────┘ │
└────────┬────────────────────────┘
│
┌────┴────┐
│ Redis │ (Cache + Message Broker)
└─────────┘
Backend Apps:
accounts: User management, authentication, OAuth integrationevents: Event CRUD operations, coordinator managementphotos: Photo uploads, tagging, search functionalitynotifications: Real-time WebSocket notificationsadminpanel: Admin-specific views and permissionsdashboard: User dashboards and analytics
- Python 3.10+
- Node.js 18+
- Redis Server
- Git
Django==6.0
djangorestframework
djangorestframework-simplejwt
django-cors-headers
channels
channels-redis
daphne
celery
redis
Pillow
requests
react
react-redux
@reduxjs/toolkit
axios
react-router-dom
tailwindcss
lucide-react
git clone <repository-url>
cd djangopython -m venv myenv
myenv\Scripts\activate # Windows
# or
source myenv/bin/activate # Linux/Maccd autumn_photo_backend
pip install -r requirements.txtCreate .env file in autumn_photo_backend/:
SECRET_KEY=your-secret-key
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
# Omniport OAuth Settings
OMNIPORT_BASE_URL=https://channeli.in
OMNIPORT_CLIENT_ID=your-client-id
OMNIPORT_CLIENT_SECRET=your-client-secret
OMNIPORT_REDIRECT_URI=http://localhost:5173/auth/callback
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
# CORS
CORS_ALLOWED_ORIGINS=http://localhost:5173python manage.py migratepython manage.py createsuperusercd ../frontend/autumn_photo_frontend
npm installUpdate src/services/axiosinstances.ts if needed:
const api = axios.create({
baseURL: "http://localhost:8000/api",
withCredentials: true,
});Download and run Redis from Redis Windows Release
redis-server.exeredis-servercd autumn_photo_backend
python manage.py runserverServer runs on: http://localhost:8000
cd autumn_photo_backend
celery -A autumn_photo worker --loglevel=info --pool=soloredis-server.exe # Windows
# or
redis-server # Linux/Maccd frontend/autumn_photo_frontend
npm run devApp runs on: http://localhost:5173
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000/api
- Admin Panel: http://localhost:8000/admin
- WebSocket: ws://localhost:8000/ws/notifications/
django/
├── autumn_photo_backend/ # Django Backend
│ ├── manage.py
│ ├── db.sqlite3
│ ├── requirements.txt
│ ├── autumn_photo/ # Main settings
│ │ ├── settings.py
│ │ ├── urls.py
│ │ ├── celery.py
│ │ └── wsgi.py
│ ├── accounts/ # User & Auth
│ │ ├── models.py # Custom User model
│ │ ├── views.py # OAuth callbacks
│ │ ├── serializers.py
│ │ └── auth_backend.py
│ ├── events/ # Event Management
│ │ ├── models.py # Event model
│ │ ├── views.py # CRUD APIs
│ │ ├── serializers.py
│ │ └── permissions.py
│ ├── photos/ # Photo Management
│ │ ├── models.py # Photo, PersonTag models
│ │ ├── views.py # Upload, search APIs
│ │ ├── tasks.py # Celery tasks
│ │ └── utils.py # Image processing
│ ├── notifications/ # WebSocket Notifications
│ │ ├── consumers.py
│ │ ├── routing.py
│ │ └── models.py
│ ├── adminpanel/ # Admin Features
│ └── dashboard/ # User Dashboards
├── frontend/
│ └── autumn_photo_frontend/ # React Frontend
│ ├── src/
│ │ ├── app/ # Redux store
│ │ ├── components/ # Reusable components
│ │ ├── pages/ # Page components
│ │ │ ├── events/ # Events & Photos
│ │ │ ├── admin/ # Admin Panel
│ │ │ └── dashboard/ # Dashboards
│ │ ├── services/ # API clients
│ │ └── utils/ # Helpers
│ ├── package.json
│ └── vite.config.ts
└── README.md
POST /api/auth/login/- Login with credentialsPOST /api/auth/register/- Register new userGET /api/auth/omniport/- OAuth redirect URLPOST /api/auth/omniport/callback/- OAuth callbackPOST /api/auth/refresh/- Refresh JWT token
GET /api/events/- List events (with search)POST /api/events/- Create event (Admin/Coordinator)GET /api/events/:id/- Event detailsPATCH /api/events/:id/- Update eventDELETE /api/events/:id/- Delete event (Admin only)GET /api/events/:id/photos/- Event photos
POST /api/events/:id/upload/- Upload photosGET /api/photos/search/- Search photos by tags/peoplePOST /api/photos/:id/tag/- Tag person in photoDELETE /api/photos/:id/tag/:tagId/- Remove tag
GET /api/adminpanel/users/- List all usersPATCH /api/adminpanel/users/:id/- Update user roleDELETE /api/adminpanel/users/:id/- Delete user
WebSocket /ws/notifications/- Real-time notifications
Implementation: accounts/views.py
class OmniportCallbackAPIView(APIView):
def post(self, request):
# Exchange authorization code for access token
token_response = requests.post(
f"{settings.OMNIPORT_BASE_URL}/open_auth/token/",
auth=HTTPBasicAuth(client_id, client_secret),
data={'grant_type': 'authorization_code', 'code': code}
)
# Fetch user data from Omniport
user_data = requests.get(
f"{settings.OMNIPORT_BASE_URL}/open_auth/get_user_data/",
headers={'Authorization': f'Bearer {access_token}'}
)
# Create or update user
user, created = User.objects.get_or_create(email=email)Key Challenges:
- OAuth endpoint URLs differed from documentation (
/open_auth/token/vs/oauth/token/) - Token exchange required
HTTPBasicAuthinstead of form data - User data fields were camelCase (
contactInformation.emailAddress) - Cross-origin session cookies needed
SESSION_COOKIE_SAMESITE="None"
Implementation: photos/views.py
class PhotoSearchAPIView(APIView):
def get(self, request):
q = request.GET.get('q', '').strip()
if len(q) < 2:
return Response({'photos': []})
# Search in AI tags (JSONField), person tags, and event info
photos = Photo.objects.filter(
Q(tags__icontains=q) | # AI tags
Q(person_tags__tagged_user__email__icontains=q) | # Tagged user
Q(person_tags__tagged_user__full_name__icontains=q) |
Q(event__name__icontains=q) | # Event name
Q(event__description__icontains=q)
).distinct()Key Challenges:
- SQLite doesn't support
__icontainson JSONField directly - Solution: Cast JSONField to string for substring search
- Added 2-character minimum to avoid overly broad matches
Implementation: events/permissions.py
class ISADMIN_OR_COORDINATOR(BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.role == 'ADMIN':
return True
if request.user.role == 'EVENT_COORDINATOR':
return obj.coordinators.filter(id=request.user.id).exists()
return FalseFrontend Logic: EventsPage.tsx
- Admins see edit options only in Admin Panel
- Event coordinators see edit buttons on their event cards
- Coordinators can only edit events they're assigned to
Implementation: notifications/consumers.py
class NotificationConsumer(AsyncWebsocketConsumer):
async def connect(self):
user_id = self.scope['user'].id
await self.channel_layer.group_add(f"user_{user_id}", self.channel_name)
async def notification_message(self, event):
await self.send(text_data=json.dumps(event['data']))Trigger: Photo uploads, tagging events automatically send notifications
Implementation: photos/tasks.py
@shared_task
def process_photo(photo_id):
photo = Photo.objects.get(id=photo_id)
# Generate thumbnail (300x300)
thumbnail = create_thumbnail(photo.original_file, size=(300, 300))
# Generate display image (1920x1080)
display = create_display(photo.original_file, size=(1920, 1080))
# Extract EXIF data
exif = extract_exif(photo.original_file)
# AI tagging (placeholder for ML integration)
tags = generate_ai_tags(photo.original_file)| Role | Permissions |
|---|---|
| ADMIN | Full access: manage users, all events, all photos |
| EVENT_COORDINATOR | Create events, edit assigned events, upload photos |
| PHOTOGRAPHER | Upload photos to assigned events |
| IMG_MEMBER | View all events, view all photos |
| PUBLIC | View public events only |
- JWT-based authentication with refresh tokens
- Role-based access control (RBAC)
- CORS configured for cross-origin requests
- Session cookie security (
SameSite=None,Secure) - Permission classes on all API endpoints
- File upload validation (image types only)
- Gradient Design: Modern purple/green gradient theme
- Dark Mode: Eye-friendly dark background
- Responsive: Mobile, tablet, and desktop support
- Animations: Smooth transitions and hover effects
- Loading States: Skeleton loaders and spinners
- Error Handling: User-friendly error messages
- Modals: Elegant popups for forms and photo viewing
cd autumn_photo_backend
python manage.py testcd frontend/autumn_photo_frontend
npm testSECRET_KEY=
DEBUG=
ALLOWED_HOSTS=
OMNIPORT_BASE_URL=
OMNIPORT_CLIENT_ID=
OMNIPORT_CLIENT_SECRET=
OMNIPORT_REDIRECT_URI=
REDIS_HOST=
REDIS_PORT=- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit changes (
git commit -m 'Add AmazingFeature') - Push to branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is developed for the Information Management Group (IMG).
Developed as part of the IMG Autumn Assignment 2025/26
Contact: [Your Email/GitHub]
- Information Management Group (IMG)
- Omniport team for OAuth integration
- Django & React communities