mirror of
https://github.com/thecyberlearn/digital-branding-system.git
synced 2026-08-18 08:52:56 +00:00
Initial commit: Django Digital Branding Management System
- Core Django project structure with branding_system and dashboard apps - Platform, ContentDeliverable, and Strategy models with relationships - Comprehensive Django admin interface with custom displays - Dashboard views with completion tracking and overdue detection - Tailwind CSS styling with custom color scheme - WARP.md documentation for development guidance
This commit is contained in:
commit
c10d4692e6
184
WARP.md
Normal file
184
WARP.md
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# WARP.md
|
||||||
|
|
||||||
|
This file provides guidance to WARP (warp.dev) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a Django-based Digital Branding Management System designed to help manage content creation and publication across multiple digital platforms. The system tracks platforms, content deliverables, and strategies with comprehensive admin functionality.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Models & Relationships
|
||||||
|
The application is built around three main models with clear relationships:
|
||||||
|
|
||||||
|
- **Platform**: Represents social media/content platforms (LinkedIn, YouTube, Blog, etc.)
|
||||||
|
- Has many ContentDeliverable and Strategy records
|
||||||
|
- Includes completion rate calculations and status tracking
|
||||||
|
|
||||||
|
- **ContentDeliverable**: Individual content items with progress tracking
|
||||||
|
- Belongs to a Platform
|
||||||
|
- Tracks status progression: committed → drafted → published
|
||||||
|
- Includes due date tracking and overdue detection
|
||||||
|
|
||||||
|
- **Strategy**: Platform-specific strategic objectives and tactics
|
||||||
|
- Belongs to a Platform
|
||||||
|
- Follows OTAC structure (Objective, Tactics, Actions, Control)
|
||||||
|
- Has priority levels and active/inactive states
|
||||||
|
|
||||||
|
### Application Structure
|
||||||
|
```
|
||||||
|
digital-branding-system/
|
||||||
|
├── branding_system/ # Django project settings
|
||||||
|
├── dashboard/ # Main app with models, views, admin
|
||||||
|
├── templates/ # HTML templates with Tailwind CSS
|
||||||
|
├── static/ # Static files
|
||||||
|
└── manage.py # Django management script
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Environment Setup
|
||||||
|
```bash
|
||||||
|
# Activate virtual environment
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# Install dependencies (if requirements.txt is created)
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Django Management
|
||||||
|
```bash
|
||||||
|
# Run development server
|
||||||
|
python manage.py runserver
|
||||||
|
|
||||||
|
# Database operations
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
||||||
|
python manage.py createsuperuser
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
python manage.py test
|
||||||
|
python manage.py test dashboard
|
||||||
|
|
||||||
|
# Database shell and management
|
||||||
|
python manage.py shell
|
||||||
|
python manage.py shell_plus # Enhanced shell via django-extensions
|
||||||
|
python manage.py dbshell
|
||||||
|
```
|
||||||
|
|
||||||
|
### Django Extensions Commands
|
||||||
|
The project uses django-extensions which provides additional helpful commands:
|
||||||
|
```bash
|
||||||
|
# Show URL patterns
|
||||||
|
python manage.py show_urls
|
||||||
|
|
||||||
|
# Generate model graphs
|
||||||
|
python manage.py graph_models -a -o models.png
|
||||||
|
|
||||||
|
# Reset database (development only)
|
||||||
|
python manage.py reset_db
|
||||||
|
|
||||||
|
# Export data as Python script
|
||||||
|
python manage.py dumpscript dashboard > backup_data.py
|
||||||
|
|
||||||
|
# Show all available commands
|
||||||
|
python manage.py help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Management
|
||||||
|
```bash
|
||||||
|
# Create sample data via admin interface at /admin/
|
||||||
|
# Default admin is accessible once superuser is created
|
||||||
|
|
||||||
|
# Export/import data
|
||||||
|
python manage.py dumpdata dashboard > dashboard_data.json
|
||||||
|
python manage.py loaddata dashboard_data.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Admin Interface Customizations
|
||||||
|
- Comprehensive admin interface with custom displays and filters
|
||||||
|
- Color-coded status indicators and progress bars
|
||||||
|
- Bulk actions for status changes
|
||||||
|
- Custom admin site branding: "Digital Branding Management System"
|
||||||
|
|
||||||
|
### Dashboard Views
|
||||||
|
- Main dashboard with platform overview cards
|
||||||
|
- Platform-specific detail views
|
||||||
|
- Real-time completion rate calculations
|
||||||
|
- Overdue content tracking
|
||||||
|
|
||||||
|
### Model Properties & Methods
|
||||||
|
Models include calculated properties for:
|
||||||
|
- Completion rates and counts by status
|
||||||
|
- Overdue detection and days until due
|
||||||
|
- Total deliverable counts per platform
|
||||||
|
|
||||||
|
## Database Schema Notes
|
||||||
|
|
||||||
|
### Current Setup
|
||||||
|
- Uses SQLite database (`db.sqlite3`)
|
||||||
|
- Includes Django REST Framework (though no API endpoints are currently defined)
|
||||||
|
- Models use standard Django field types with proper relationships
|
||||||
|
|
||||||
|
### Important Field Validations
|
||||||
|
- Priority levels: 1-5 scale (1 = highest priority)
|
||||||
|
- Completion percentage: 0-100% validation
|
||||||
|
- Status choices are enforced at model level
|
||||||
|
- Date validations for target vs actual dates
|
||||||
|
|
||||||
|
## Styling & Frontend
|
||||||
|
|
||||||
|
### CSS Framework
|
||||||
|
- Uses Tailwind CSS via CDN
|
||||||
|
- Custom color scheme defined:
|
||||||
|
- brand-blue (#3B82F6)
|
||||||
|
- brand-green (#10B981)
|
||||||
|
- brand-orange (#F59E0B)
|
||||||
|
- brand-red (#EF4444)
|
||||||
|
- brand-purple (#8B5CF6)
|
||||||
|
|
||||||
|
### Template Structure
|
||||||
|
- Base template with navigation and common styling
|
||||||
|
- Dashboard template extends base template
|
||||||
|
- Font Awesome icons integrated
|
||||||
|
|
||||||
|
## Testing & Development
|
||||||
|
|
||||||
|
### Test Structure
|
||||||
|
- Basic test framework is in place (`dashboard/tests.py`)
|
||||||
|
- Tests should be added for models, views, and admin functionality
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
1. Activate virtual environment
|
||||||
|
2. Run migrations if needed
|
||||||
|
3. Start development server
|
||||||
|
4. Access admin interface for data management
|
||||||
|
5. View dashboard at root URL
|
||||||
|
|
||||||
|
## Deployment Considerations
|
||||||
|
|
||||||
|
### Security Settings
|
||||||
|
- SECRET_KEY is currently hardcoded (should be moved to environment variable)
|
||||||
|
- DEBUG=True (should be False in production)
|
||||||
|
- ALLOWED_HOSTS is empty (needs to be configured for production)
|
||||||
|
|
||||||
|
### Static Files
|
||||||
|
- Static files configured for development
|
||||||
|
- STATIC_ROOT set to 'staticfiles' for production collection
|
||||||
|
|
||||||
|
### Database
|
||||||
|
- Currently uses SQLite (consider PostgreSQL for production)
|
||||||
|
- Database migrations are tracked in dashboard/migrations/
|
||||||
|
|
||||||
|
## URL Structure
|
||||||
|
- `/` - Main dashboard
|
||||||
|
- `/admin/` - Django admin interface
|
||||||
|
- `/platform/<id>/` - Platform detail view
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
Key packages:
|
||||||
|
- Django 5.2.6
|
||||||
|
- django-extensions 4.1
|
||||||
|
- djangorestframework 3.16.1
|
||||||
0
branding_system/__init__.py
Normal file
0
branding_system/__init__.py
Normal file
BIN
branding_system/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
branding_system/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
branding_system/__pycache__/settings.cpython-312.pyc
Normal file
BIN
branding_system/__pycache__/settings.cpython-312.pyc
Normal file
Binary file not shown.
BIN
branding_system/__pycache__/urls.cpython-312.pyc
Normal file
BIN
branding_system/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
branding_system/__pycache__/wsgi.cpython-312.pyc
Normal file
BIN
branding_system/__pycache__/wsgi.cpython-312.pyc
Normal file
Binary file not shown.
16
branding_system/asgi.py
Normal file
16
branding_system/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for branding_system project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branding_system.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
129
branding_system/settings.py
Normal file
129
branding_system/settings.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
"""
|
||||||
|
Django settings for branding_system project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.2.6.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-0%lol6add_bnxa)-r4e#9w-!ld-mxgq5q7u98$@@cb5y0+n4u^'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework',
|
||||||
|
'django_extensions',
|
||||||
|
'dashboard',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'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',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'branding_system.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR / 'templates'],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'branding_system.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / 'static',
|
||||||
|
]
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
23
branding_system/urls.py
Normal file
23
branding_system/urls.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for branding_system project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('', include('dashboard.urls')),
|
||||||
|
]
|
||||||
16
branding_system/wsgi.py
Normal file
16
branding_system/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for branding_system project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branding_system.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
dashboard/__init__.py
Normal file
0
dashboard/__init__.py
Normal file
BIN
dashboard/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
dashboard/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dashboard/__pycache__/admin.cpython-312.pyc
Normal file
BIN
dashboard/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dashboard/__pycache__/apps.cpython-312.pyc
Normal file
BIN
dashboard/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dashboard/__pycache__/models.cpython-312.pyc
Normal file
BIN
dashboard/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dashboard/__pycache__/urls.cpython-312.pyc
Normal file
BIN
dashboard/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dashboard/__pycache__/views.cpython-312.pyc
Normal file
BIN
dashboard/__pycache__/views.cpython-312.pyc
Normal file
Binary file not shown.
181
dashboard/admin.py
Normal file
181
dashboard/admin.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from .models import Platform, ContentDeliverable, Strategy
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Platform)
|
||||||
|
class PlatformAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'name', 'platform_type', 'status', 'primary_content_type',
|
||||||
|
'total_deliverables', 'completion_rate_display', 'created_at'
|
||||||
|
)
|
||||||
|
list_filter = ('platform_type', 'status', 'created_at')
|
||||||
|
search_fields = ('name', 'description', 'primary_content_type')
|
||||||
|
readonly_fields = ('created_at', 'updated_at')
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Basic Information', {
|
||||||
|
'fields': ('name', 'platform_type', 'status', 'primary_content_type')
|
||||||
|
}),
|
||||||
|
('Details', {
|
||||||
|
'fields': ('description', 'target_audience', 'posting_frequency')
|
||||||
|
}),
|
||||||
|
('Timestamps', {
|
||||||
|
'fields': ('created_at', 'updated_at'),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
def completion_rate_display(self, obj):
|
||||||
|
rate = obj.completion_rate
|
||||||
|
color = 'green' if rate >= 70 else 'orange' if rate >= 40 else 'red'
|
||||||
|
return format_html(
|
||||||
|
'<span style="color: {};">{:.1f}%</span>',
|
||||||
|
color, rate
|
||||||
|
)
|
||||||
|
completion_rate_display.short_description = 'Completion Rate'
|
||||||
|
|
||||||
|
def total_deliverables(self, obj):
|
||||||
|
return obj.total_deliverables
|
||||||
|
total_deliverables.short_description = 'Total Content'
|
||||||
|
|
||||||
|
|
||||||
|
class ContentDeliverableInline(admin.TabularInline):
|
||||||
|
model = ContentDeliverable
|
||||||
|
extra = 0
|
||||||
|
fields = ('title', 'content_type', 'status', 'completion_percentage', 'target_date')
|
||||||
|
readonly_fields = ()
|
||||||
|
|
||||||
|
|
||||||
|
class StrategyInline(admin.TabularInline):
|
||||||
|
model = Strategy
|
||||||
|
extra = 0
|
||||||
|
fields = ('title', 'priority', 'is_active', 'start_date', 'end_date')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ContentDeliverable)
|
||||||
|
class ContentDeliverableAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'title', 'platform', 'content_type', 'status_display',
|
||||||
|
'completion_percentage_display', 'target_date', 'is_overdue_display'
|
||||||
|
)
|
||||||
|
list_filter = ('status', 'content_type', 'platform', 'target_date', 'created_at')
|
||||||
|
search_fields = ('title', 'description', 'tags', 'platform__name')
|
||||||
|
readonly_fields = ('created_at', 'updated_at', 'days_until_due')
|
||||||
|
date_hierarchy = 'target_date'
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Basic Information', {
|
||||||
|
'fields': ('platform', 'title', 'content_type', 'status')
|
||||||
|
}),
|
||||||
|
('Content Details', {
|
||||||
|
'fields': ('description', 'completion_percentage', 'tags')
|
||||||
|
}),
|
||||||
|
('Dates', {
|
||||||
|
'fields': ('target_date', 'actual_date', 'days_until_due')
|
||||||
|
}),
|
||||||
|
('Additional Info', {
|
||||||
|
'fields': ('url', 'notes')
|
||||||
|
}),
|
||||||
|
('Timestamps', {
|
||||||
|
'fields': ('created_at', 'updated_at'),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
def status_display(self, obj):
|
||||||
|
status_colors = {
|
||||||
|
'committed': 'blue',
|
||||||
|
'drafted': 'orange',
|
||||||
|
'published': 'green',
|
||||||
|
'cancelled': 'red'
|
||||||
|
}
|
||||||
|
color = status_colors.get(obj.status, 'black')
|
||||||
|
return format_html(
|
||||||
|
'<span style="color: {}; font-weight: bold;">{}</span>',
|
||||||
|
color, obj.get_status_display()
|
||||||
|
)
|
||||||
|
status_display.short_description = 'Status'
|
||||||
|
|
||||||
|
def completion_percentage_display(self, obj):
|
||||||
|
percentage = obj.completion_percentage
|
||||||
|
if percentage == 100:
|
||||||
|
color = 'green'
|
||||||
|
elif percentage >= 50:
|
||||||
|
color = 'orange'
|
||||||
|
else:
|
||||||
|
color = 'red'
|
||||||
|
return format_html(
|
||||||
|
'<div style="width: 100px; background-color: #f0f0f0; border-radius: 3px; padding: 2px;">' +
|
||||||
|
'<div style="width: {}%; background-color: {}; height: 20px; border-radius: 2px; text-align: center; color: white; line-height: 20px; font-size: 12px;">' +
|
||||||
|
'{}%</div></div>',
|
||||||
|
percentage, color, percentage
|
||||||
|
)
|
||||||
|
completion_percentage_display.short_description = 'Progress'
|
||||||
|
|
||||||
|
def is_overdue_display(self, obj):
|
||||||
|
if obj.is_overdue:
|
||||||
|
return format_html('<span style="color: red; font-weight: bold;">⚠ OVERDUE</span>')
|
||||||
|
elif obj.days_until_due is not None and obj.days_until_due <= 3 and obj.status != 'published':
|
||||||
|
return format_html('<span style="color: orange;">⏰ Due Soon</span>')
|
||||||
|
return '✓'
|
||||||
|
is_overdue_display.short_description = 'Due Status'
|
||||||
|
|
||||||
|
actions = ['mark_as_drafted', 'mark_as_published', 'mark_as_committed']
|
||||||
|
|
||||||
|
def mark_as_drafted(self, request, queryset):
|
||||||
|
updated = queryset.update(status='drafted')
|
||||||
|
self.message_user(request, f'{updated} deliverables marked as drafted.')
|
||||||
|
mark_as_drafted.short_description = 'Mark selected items as drafted'
|
||||||
|
|
||||||
|
def mark_as_published(self, request, queryset):
|
||||||
|
updated = queryset.update(status='published', completion_percentage=100)
|
||||||
|
self.message_user(request, f'{updated} deliverables marked as published.')
|
||||||
|
mark_as_published.short_description = 'Mark selected items as published'
|
||||||
|
|
||||||
|
def mark_as_committed(self, request, queryset):
|
||||||
|
updated = queryset.update(status='committed')
|
||||||
|
self.message_user(request, f'{updated} deliverables marked as committed.')
|
||||||
|
mark_as_committed.short_description = 'Mark selected items as committed'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Strategy)
|
||||||
|
class StrategyAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('title', 'platform', 'priority', 'is_active', 'start_date', 'end_date')
|
||||||
|
list_filter = ('platform', 'priority', 'is_active', 'start_date')
|
||||||
|
search_fields = ('title', 'objective', 'tactics', 'actions')
|
||||||
|
readonly_fields = ('created_at', 'updated_at')
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Basic Information', {
|
||||||
|
'fields': ('platform', 'title', 'priority', 'is_active')
|
||||||
|
}),
|
||||||
|
('Strategy Details', {
|
||||||
|
'fields': ('objective', 'tactics', 'actions', 'control')
|
||||||
|
}),
|
||||||
|
('Timeline', {
|
||||||
|
'fields': ('start_date', 'end_date')
|
||||||
|
}),
|
||||||
|
('Timestamps', {
|
||||||
|
'fields': ('created_at', 'updated_at'),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
actions = ['activate_strategies', 'deactivate_strategies']
|
||||||
|
|
||||||
|
def activate_strategies(self, request, queryset):
|
||||||
|
updated = queryset.update(is_active=True)
|
||||||
|
self.message_user(request, f'{updated} strategies activated.')
|
||||||
|
activate_strategies.short_description = 'Activate selected strategies'
|
||||||
|
|
||||||
|
def deactivate_strategies(self, request, queryset):
|
||||||
|
updated = queryset.update(is_active=False)
|
||||||
|
self.message_user(request, f'{updated} strategies deactivated.')
|
||||||
|
deactivate_strategies.short_description = 'Deactivate selected strategies'
|
||||||
|
|
||||||
|
|
||||||
|
# Customize admin site headers
|
||||||
|
admin.site.site_header = "Digital Branding Management System"
|
||||||
|
admin.site.site_title = "Branding Admin"
|
||||||
|
admin.site.index_title = "Welcome to Digital Branding Management"
|
||||||
6
dashboard/apps.py
Normal file
6
dashboard/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DashboardConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'dashboard'
|
||||||
1
dashboard/management/__init__.py
Normal file
1
dashboard/management/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Management package
|
||||||
1
dashboard/management/commands/__init__.py
Normal file
1
dashboard/management/commands/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Management commands package
|
||||||
79
dashboard/migrations/0001_initial.py
Normal file
79
dashboard/migrations/0001_initial.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-09-06 17:37
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import django.core.validators
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Platform',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text="Platform name (e.g., 'LinkedIn Business')", max_length=100)),
|
||||||
|
('platform_type', models.CharField(choices=[('linkedin', 'LinkedIn'), ('youtube', 'YouTube'), ('blog', 'Blog'), ('instagram', 'Instagram'), ('twitter', 'Twitter'), ('facebook', 'Facebook'), ('tiktok', 'TikTok'), ('podcast', 'Podcast'), ('newsletter', 'Newsletter'), ('other', 'Other')], max_length=20)),
|
||||||
|
('description', models.TextField(blank=True, help_text="Brief description of this platform's purpose")),
|
||||||
|
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('planning', 'Planning'), ('paused', 'Paused')], default='active', max_length=20)),
|
||||||
|
('primary_content_type', models.CharField(help_text="Main type of content for this platform (e.g., 'Articles', 'Videos', 'Posts')", max_length=100)),
|
||||||
|
('target_audience', models.CharField(blank=True, help_text='Target audience for this platform', max_length=200)),
|
||||||
|
('posting_frequency', models.CharField(blank=True, help_text="How often content is posted (e.g., 'Daily', '3x per week')", max_length=100)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContentDeliverable',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(help_text='Content title or brief description', max_length=200)),
|
||||||
|
('content_type', models.CharField(choices=[('article', 'Article'), ('video', 'Video'), ('post', 'Social Media Post'), ('story', 'Story'), ('reel', 'Reel/Short Video'), ('carousel', 'Carousel'), ('infographic', 'Infographic'), ('podcast', 'Podcast Episode'), ('newsletter', 'Newsletter'), ('other', 'Other')], max_length=20)),
|
||||||
|
('status', models.CharField(choices=[('committed', 'Committed'), ('drafted', 'Drafted'), ('published', 'Published'), ('cancelled', 'Cancelled')], default='committed', max_length=20)),
|
||||||
|
('description', models.TextField(blank=True, help_text='Detailed description of the content')),
|
||||||
|
('completion_percentage', models.IntegerField(default=0, help_text='Completion progress (0-100%)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
|
||||||
|
('target_date', models.DateField(blank=True, help_text='Target publication date', null=True)),
|
||||||
|
('actual_date', models.DateField(blank=True, help_text='Actual publication date', null=True)),
|
||||||
|
('url', models.URLField(blank=True, help_text='URL of published content')),
|
||||||
|
('notes', models.TextField(blank=True, help_text='Additional notes or comments')),
|
||||||
|
('tags', models.CharField(blank=True, help_text="Comma-separated tags (e.g., 'marketing, tutorial, beginner')", max_length=200)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('platform', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dashboard.platform')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Strategy',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(help_text='Strategy title', max_length=200)),
|
||||||
|
('objective', models.TextField(help_text='What you want to achieve')),
|
||||||
|
('tactics', models.TextField(help_text='How you plan to achieve the objective')),
|
||||||
|
('actions', models.TextField(help_text='Specific actions to implement the tactics')),
|
||||||
|
('control', models.TextField(blank=True, help_text="How you'll measure success and control/adjust the strategy")),
|
||||||
|
('priority', models.IntegerField(default=1, help_text='Priority level (1 = highest, 5 = lowest)', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)])),
|
||||||
|
('start_date', models.DateField(default=datetime.date.today)),
|
||||||
|
('end_date', models.DateField(blank=True, null=True)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('platform', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='strategies', to='dashboard.platform')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'strategies',
|
||||||
|
'ordering': ['priority', '-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
dashboard/migrations/__init__.py
Normal file
0
dashboard/migrations/__init__.py
Normal file
BIN
dashboard/migrations/__pycache__/0001_initial.cpython-312.pyc
Normal file
BIN
dashboard/migrations/__pycache__/0001_initial.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dashboard/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
dashboard/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
181
dashboard/models.py
Normal file
181
dashboard/models.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
|
from django.urls import reverse
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
|
class Platform(models.Model):
|
||||||
|
"""Model representing different social media/content platforms"""
|
||||||
|
|
||||||
|
PLATFORM_TYPES = [
|
||||||
|
('linkedin', 'LinkedIn'),
|
||||||
|
('youtube', 'YouTube'),
|
||||||
|
('blog', 'Blog'),
|
||||||
|
('instagram', 'Instagram'),
|
||||||
|
('twitter', 'Twitter'),
|
||||||
|
('facebook', 'Facebook'),
|
||||||
|
('tiktok', 'TikTok'),
|
||||||
|
('podcast', 'Podcast'),
|
||||||
|
('newsletter', 'Newsletter'),
|
||||||
|
('other', 'Other'),
|
||||||
|
]
|
||||||
|
|
||||||
|
STATUS_CHOICES = [
|
||||||
|
('active', 'Active'),
|
||||||
|
('inactive', 'Inactive'),
|
||||||
|
('planning', 'Planning'),
|
||||||
|
('paused', 'Paused'),
|
||||||
|
]
|
||||||
|
|
||||||
|
name = models.CharField(max_length=100, help_text="Platform name (e.g., 'LinkedIn Business')")
|
||||||
|
platform_type = models.CharField(max_length=20, choices=PLATFORM_TYPES)
|
||||||
|
description = models.TextField(blank=True, help_text="Brief description of this platform's purpose")
|
||||||
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
|
||||||
|
primary_content_type = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
help_text="Main type of content for this platform (e.g., 'Articles', 'Videos', 'Posts')"
|
||||||
|
)
|
||||||
|
target_audience = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True,
|
||||||
|
help_text="Target audience for this platform"
|
||||||
|
)
|
||||||
|
posting_frequency = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
help_text="How often content is posted (e.g., 'Daily', '3x per week')"
|
||||||
|
)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.get_platform_type_display()})"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('platform-detail', args=[str(self.id)])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_deliverables(self):
|
||||||
|
return self.contentdeliverable_set.count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def committed_count(self):
|
||||||
|
return self.contentdeliverable_set.filter(status='committed').count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def drafted_count(self):
|
||||||
|
return self.contentdeliverable_set.filter(status='drafted').count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def published_count(self):
|
||||||
|
return self.contentdeliverable_set.filter(status='published').count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def completion_rate(self):
|
||||||
|
total = self.total_deliverables
|
||||||
|
if total == 0:
|
||||||
|
return 0
|
||||||
|
published = self.published_count
|
||||||
|
return round((published / total) * 100, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class Strategy(models.Model):
|
||||||
|
"""Model for platform strategies containing objectives, tactics, and actions"""
|
||||||
|
|
||||||
|
platform = models.ForeignKey(Platform, on_delete=models.CASCADE, related_name='strategies')
|
||||||
|
title = models.CharField(max_length=200, help_text="Strategy title")
|
||||||
|
objective = models.TextField(help_text="What you want to achieve")
|
||||||
|
tactics = models.TextField(help_text="How you plan to achieve the objective")
|
||||||
|
actions = models.TextField(help_text="Specific actions to implement the tactics")
|
||||||
|
control = models.TextField(
|
||||||
|
blank=True,
|
||||||
|
help_text="How you'll measure success and control/adjust the strategy"
|
||||||
|
)
|
||||||
|
priority = models.IntegerField(
|
||||||
|
default=1,
|
||||||
|
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
||||||
|
help_text="Priority level (1 = highest, 5 = lowest)"
|
||||||
|
)
|
||||||
|
start_date = models.DateField(default=date.today)
|
||||||
|
end_date = models.DateField(blank=True, null=True)
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['priority', '-created_at']
|
||||||
|
verbose_name_plural = 'strategies'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.platform.name} - {self.title}"
|
||||||
|
|
||||||
|
|
||||||
|
class ContentDeliverable(models.Model):
|
||||||
|
"""Model for tracking content deliverables and their progress"""
|
||||||
|
|
||||||
|
STATUS_CHOICES = [
|
||||||
|
('committed', 'Committed'),
|
||||||
|
('drafted', 'Drafted'),
|
||||||
|
('published', 'Published'),
|
||||||
|
('cancelled', 'Cancelled'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONTENT_TYPES = [
|
||||||
|
('article', 'Article'),
|
||||||
|
('video', 'Video'),
|
||||||
|
('post', 'Social Media Post'),
|
||||||
|
('story', 'Story'),
|
||||||
|
('reel', 'Reel/Short Video'),
|
||||||
|
('carousel', 'Carousel'),
|
||||||
|
('infographic', 'Infographic'),
|
||||||
|
('podcast', 'Podcast Episode'),
|
||||||
|
('newsletter', 'Newsletter'),
|
||||||
|
('other', 'Other'),
|
||||||
|
]
|
||||||
|
|
||||||
|
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
|
||||||
|
title = models.CharField(max_length=200, help_text="Content title or brief description")
|
||||||
|
content_type = models.CharField(max_length=20, choices=CONTENT_TYPES)
|
||||||
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='committed')
|
||||||
|
description = models.TextField(blank=True, help_text="Detailed description of the content")
|
||||||
|
completion_percentage = models.IntegerField(
|
||||||
|
default=0,
|
||||||
|
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
||||||
|
help_text="Completion progress (0-100%)"
|
||||||
|
)
|
||||||
|
target_date = models.DateField(blank=True, null=True, help_text="Target publication date")
|
||||||
|
actual_date = models.DateField(blank=True, null=True, help_text="Actual publication date")
|
||||||
|
url = models.URLField(blank=True, help_text="URL of published content")
|
||||||
|
notes = models.TextField(blank=True, help_text="Additional notes or comments")
|
||||||
|
tags = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True,
|
||||||
|
help_text="Comma-separated tags (e.g., 'marketing, tutorial, beginner')"
|
||||||
|
)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.platform.name} - {self.title} ({self.get_status_display()})"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('deliverable-detail', args=[str(self.id)])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_overdue(self):
|
||||||
|
if self.target_date and self.status not in ['published', 'cancelled']:
|
||||||
|
return date.today() > self.target_date
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def days_until_due(self):
|
||||||
|
if self.target_date:
|
||||||
|
delta = self.target_date - date.today()
|
||||||
|
return delta.days
|
||||||
|
return None
|
||||||
3
dashboard/tests.py
Normal file
3
dashboard/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
7
dashboard/urls.py
Normal file
7
dashboard/urls.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.dashboard_view, name='dashboard'),
|
||||||
|
path('platform/<int:platform_id>/', views.platform_detail_view, name='platform-detail'),
|
||||||
|
]
|
||||||
122
dashboard/views.py
Normal file
122
dashboard/views.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Count, Q
|
||||||
|
from datetime import date
|
||||||
|
from .models import Platform, ContentDeliverable, Strategy
|
||||||
|
|
||||||
|
|
||||||
|
def dashboard_view(request):
|
||||||
|
"""Main dashboard view with statistics and platform cards"""
|
||||||
|
|
||||||
|
# Overall statistics
|
||||||
|
total_platforms = Platform.objects.filter(status='active').count()
|
||||||
|
total_deliverables = ContentDeliverable.objects.count()
|
||||||
|
committed_count = ContentDeliverable.objects.filter(status='committed').count()
|
||||||
|
drafted_count = ContentDeliverable.objects.filter(status='drafted').count()
|
||||||
|
published_count = ContentDeliverable.objects.filter(status='published').count()
|
||||||
|
|
||||||
|
# Calculate overall completion rate
|
||||||
|
if total_deliverables > 0:
|
||||||
|
overall_completion_rate = round((published_count / total_deliverables) * 100, 1)
|
||||||
|
else:
|
||||||
|
overall_completion_rate = 0
|
||||||
|
|
||||||
|
# Platform cards with detailed stats
|
||||||
|
platforms = Platform.objects.filter(status='active').prefetch_related(
|
||||||
|
'contentdeliverable_set', 'strategies'
|
||||||
|
).annotate(
|
||||||
|
total_content=Count('contentdeliverable'),
|
||||||
|
committed_content=Count('contentdeliverable', filter=Q(contentdeliverable__status='committed')),
|
||||||
|
drafted_content=Count('contentdeliverable', filter=Q(contentdeliverable__status='drafted')),
|
||||||
|
published_content=Count('contentdeliverable', filter=Q(contentdeliverable__status='published')),
|
||||||
|
total_strategies=Count('strategies', filter=Q(strategies__is_active=True))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add calculated fields to each platform
|
||||||
|
for platform in platforms:
|
||||||
|
if platform.total_content > 0:
|
||||||
|
platform.completion_percentage = round(
|
||||||
|
(platform.published_content / platform.total_content) * 100, 1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
platform.completion_percentage = 0
|
||||||
|
|
||||||
|
# Recent deliverables (last 10)
|
||||||
|
recent_deliverables = ContentDeliverable.objects.select_related(
|
||||||
|
'platform'
|
||||||
|
).order_by('-updated_at')[:10]
|
||||||
|
|
||||||
|
# Overdue deliverables
|
||||||
|
overdue_deliverables = ContentDeliverable.objects.filter(
|
||||||
|
target_date__lt=date.today(),
|
||||||
|
status__in=['committed', 'drafted']
|
||||||
|
).select_related('platform').order_by('target_date')
|
||||||
|
|
||||||
|
# Active strategies count
|
||||||
|
total_active_strategies = Strategy.objects.filter(is_active=True).count()
|
||||||
|
|
||||||
|
# Calculate percentages for progress bar
|
||||||
|
if total_deliverables > 0:
|
||||||
|
published_percentage = round((published_count / total_deliverables) * 100, 1)
|
||||||
|
drafted_percentage = round((drafted_count / total_deliverables) * 100, 1)
|
||||||
|
committed_percentage = round((committed_count / total_deliverables) * 100, 1)
|
||||||
|
else:
|
||||||
|
published_percentage = 0
|
||||||
|
drafted_percentage = 0
|
||||||
|
committed_percentage = 0
|
||||||
|
|
||||||
|
context = {
|
||||||
|
# Overall stats
|
||||||
|
'total_platforms': total_platforms,
|
||||||
|
'total_deliverables': total_deliverables,
|
||||||
|
'committed_count': committed_count,
|
||||||
|
'drafted_count': drafted_count,
|
||||||
|
'published_count': published_count,
|
||||||
|
'overall_completion_rate': overall_completion_rate,
|
||||||
|
'total_active_strategies': total_active_strategies,
|
||||||
|
|
||||||
|
# Platform data
|
||||||
|
'platforms': platforms,
|
||||||
|
|
||||||
|
# Recent activity
|
||||||
|
'recent_deliverables': recent_deliverables,
|
||||||
|
'overdue_deliverables': overdue_deliverables,
|
||||||
|
|
||||||
|
# Status for progress bars
|
||||||
|
'status_data': {
|
||||||
|
'committed': committed_count,
|
||||||
|
'drafted': drafted_count,
|
||||||
|
'published': published_count,
|
||||||
|
},
|
||||||
|
|
||||||
|
# Percentages for chart
|
||||||
|
'published_percentage': published_percentage,
|
||||||
|
'drafted_percentage': drafted_percentage,
|
||||||
|
'committed_percentage': committed_percentage,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'dashboard/dashboard.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def platform_detail_view(request, platform_id):
|
||||||
|
"""Detailed view for a specific platform"""
|
||||||
|
platform = Platform.objects.get(id=platform_id)
|
||||||
|
|
||||||
|
# Platform deliverables
|
||||||
|
deliverables = ContentDeliverable.objects.filter(
|
||||||
|
platform=platform
|
||||||
|
).order_by('-created_at')
|
||||||
|
|
||||||
|
# Platform strategies
|
||||||
|
strategies = Strategy.objects.filter(
|
||||||
|
platform=platform,
|
||||||
|
is_active=True
|
||||||
|
).order_by('priority')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'platform': platform,
|
||||||
|
'deliverables': deliverables,
|
||||||
|
'strategies': strategies,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'dashboard/platform_detail.html', context)
|
||||||
22
manage.py
Executable file
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branding_system.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
66
templates/base.html
Normal file
66
templates/base.html
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}Digital Branding Management System{% endblock %}</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'brand-blue': '#3B82F6',
|
||||||
|
'brand-green': '#10B981',
|
||||||
|
'brand-orange': '#F59E0B',
|
||||||
|
'brand-red': '#EF4444',
|
||||||
|
'brand-purple': '#8B5CF6',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 min-h-screen">
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="bg-white shadow-sm border-b border-gray-200">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<h1 class="text-xl font-semibold text-gray-900">
|
||||||
|
<i class="fas fa-chart-pie text-brand-blue mr-2"></i>
|
||||||
|
Digital Branding Management
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<a href="/" class="text-gray-700 hover:text-brand-blue px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="/admin/" class="text-gray-700 hover:text-brand-blue px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
Admin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-white border-t border-gray-200 mt-12">
|
||||||
|
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
||||||
|
<p class="text-center text-sm text-gray-500">
|
||||||
|
© 2024 Digital Branding Management System
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
243
templates/dashboard/dashboard.html
Normal file
243
templates/dashboard/dashboard.html
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Dashboard - Digital Branding Management{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="px-4 sm:px-6 lg:px-8">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">Content Strategy Dashboard</h2>
|
||||||
|
<p class="text-gray-600">Monitor your content progress across all platforms</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Overall Statistics Cards -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8">
|
||||||
|
<!-- Total Platforms -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-globe text-2xl text-brand-blue"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm text-gray-500">Active Platforms</p>
|
||||||
|
<p class="text-2xl font-semibold text-gray-900">{{ total_platforms }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Committed -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-clipboard-list text-2xl text-brand-blue"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm text-gray-500">Committed</p>
|
||||||
|
<p class="text-2xl font-semibold text-brand-blue">{{ committed_count }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Drafted -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-edit text-2xl text-brand-orange"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm text-gray-500">Drafted</p>
|
||||||
|
<p class="text-2xl font-semibold text-brand-orange">{{ drafted_count }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Published -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-check-circle text-2xl text-brand-green"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm text-gray-500">Published</p>
|
||||||
|
<p class="text-2xl font-semibold text-brand-green">{{ published_count }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Completion Rate -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-chart-line text-2xl text-brand-purple"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm text-gray-500">Completion Rate</p>
|
||||||
|
<p class="text-2xl font-semibold text-brand-purple">{{ overall_completion_rate }}%</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content by Status Chart -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6 mb-8">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Content Status Overview</h3>
|
||||||
|
<div class="w-full bg-gray-200 rounded-full h-6 mb-4">
|
||||||
|
{% if total_deliverables > 0 %}
|
||||||
|
<div class="bg-gray-200 rounded-full h-6 relative overflow-hidden">
|
||||||
|
<div class="bg-brand-green h-full absolute top-0 left-0" style="width: {{ published_percentage }}%"></div>
|
||||||
|
<div class="bg-brand-orange h-full absolute top-0" style="left: {{ published_percentage }}%; width: {{ drafted_percentage }}%"></div>
|
||||||
|
<div class="bg-brand-blue h-full absolute top-0" style="left: calc({{ published_percentage }}% + {{ drafted_percentage }}%); width: {{ committed_percentage }}%"></div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-sm text-gray-600">
|
||||||
|
<span><i class="fas fa-circle text-brand-blue mr-1"></i>Committed ({{ committed_count }})</span>
|
||||||
|
<span><i class="fas fa-circle text-brand-orange mr-1"></i>Drafted ({{ drafted_count }})</span>
|
||||||
|
<span><i class="fas fa-circle text-brand-green mr-1"></i>Published ({{ published_count }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Platform Cards - Kanban Style -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-6">Platform Progress</h3>
|
||||||
|
|
||||||
|
{% if platforms %}
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
{% for platform in platforms %}
|
||||||
|
<div class="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300 overflow-hidden">
|
||||||
|
<!-- Platform Header -->
|
||||||
|
<div class="px-6 py-4 bg-gradient-to-r from-brand-blue to-brand-purple">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h4 class="text-lg font-semibold text-white truncate">{{ platform.name }}</h4>
|
||||||
|
<span class="px-2 py-1 bg-white bg-opacity-20 rounded-full text-xs text-white">
|
||||||
|
{{ platform.get_platform_type_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-blue-100 mt-1">{{ platform.primary_content_type }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Platform Stats -->
|
||||||
|
<div class="px-6 py-4">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<span class="text-sm text-gray-500">Content Progress</span>
|
||||||
|
<span class="text-sm font-semibold text-gray-900">{{ platform.completion_percentage }}%</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<div class="w-full bg-gray-200 rounded-full h-2 mb-4">
|
||||||
|
<div class="bg-gradient-to-r from-brand-green to-green-400 h-2 rounded-full"
|
||||||
|
style="width: {{ platform.completion_percentage }}%"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content Status Breakdown -->
|
||||||
|
<div class="grid grid-cols-3 gap-3 mb-4">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-lg font-semibold text-brand-blue">{{ platform.committed_content }}</div>
|
||||||
|
<div class="text-xs text-gray-500">Committed</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-lg font-semibold text-brand-orange">{{ platform.drafted_content }}</div>
|
||||||
|
<div class="text-xs text-gray-500">Drafted</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-lg font-semibold text-brand-green">{{ platform.published_content }}</div>
|
||||||
|
<div class="text-xs text-gray-500">Published</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Additional Info -->
|
||||||
|
<div class="flex justify-between items-center text-sm text-gray-500 mb-4">
|
||||||
|
<span><i class="fas fa-list-ul mr-1"></i>Total: {{ platform.total_content }}</span>
|
||||||
|
<span><i class="fas fa-lightbulb mr-1"></i>{{ platform.total_strategies }} strategies</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Button -->
|
||||||
|
<a href="{% url 'platform-detail' platform.id %}"
|
||||||
|
class="w-full bg-brand-blue text-white py-2 px-4 rounded-md text-sm font-medium hover:bg-blue-700 transition duration-200 flex items-center justify-center">
|
||||||
|
<i class="fas fa-eye mr-2"></i>View Details
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center py-12 bg-white rounded-lg shadow">
|
||||||
|
<i class="fas fa-plus-circle text-4xl text-gray-400 mb-4"></i>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-2">No platforms yet</h3>
|
||||||
|
<p class="text-gray-500 mb-4">Get started by adding your first platform</p>
|
||||||
|
<a href="/admin/dashboard/platform/add/" class="bg-brand-blue text-white py-2 px-4 rounded-md text-sm font-medium hover:bg-blue-700">
|
||||||
|
Add Platform
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Activity & Alerts -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
|
<!-- Recent Deliverables -->
|
||||||
|
<div class="bg-white rounded-lg shadow">
|
||||||
|
<div class="px-6 py-4 border-b border-gray-200">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900">Recent Activity</h3>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y divide-gray-200">
|
||||||
|
{% for deliverable in recent_deliverables|slice:":5" %}
|
||||||
|
<div class="px-6 py-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-sm font-medium text-gray-900 truncate">{{ deliverable.title }}</p>
|
||||||
|
<p class="text-sm text-gray-500">{{ deliverable.platform.name }} • {{ deliverable.get_content_type_display }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="px-2 py-1 text-xs rounded-full
|
||||||
|
{% if deliverable.status == 'published' %}bg-green-100 text-green-800
|
||||||
|
{% elif deliverable.status == 'drafted' %}bg-orange-100 text-orange-800
|
||||||
|
{% elif deliverable.status == 'committed' %}bg-blue-100 text-blue-800
|
||||||
|
{% else %}bg-gray-100 text-gray-800{% endif %}">
|
||||||
|
{{ deliverable.get_status_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="px-6 py-8 text-center text-gray-500">
|
||||||
|
No recent activity
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Overdue & Alerts -->
|
||||||
|
<div class="bg-white rounded-lg shadow">
|
||||||
|
<div class="px-6 py-4 border-b border-gray-200">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900">Alerts & Overdue</h3>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y divide-gray-200">
|
||||||
|
{% for deliverable in overdue_deliverables|slice:":5" %}
|
||||||
|
<div class="px-6 py-4">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<i class="fas fa-exclamation-triangle text-red-500 mt-1 mr-3"></i>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-sm font-medium text-gray-900">{{ deliverable.title }}</p>
|
||||||
|
<p class="text-sm text-gray-500">{{ deliverable.platform.name }}</p>
|
||||||
|
<p class="text-xs text-red-600 mt-1">Due: {{ deliverable.target_date }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="px-6 py-8 text-center text-gray-500">
|
||||||
|
<i class="fas fa-check-circle text-green-500 text-2xl mb-2"></i>
|
||||||
|
<p>All caught up! No overdue items</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
// Add any dashboard-specific JavaScript here
|
||||||
|
console.log('Dashboard loaded');
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user