mirror of
https://github.com/thecyberlearn/digital-branding-system.git
synced 2026-08-18 06:52:55 +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