commit c10d4692e60f3f9c69842b4a26036cc98c81a2b3 Author: thecyberlearn Date: Sun Sep 7 10:16:37 2025 +0530 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 diff --git a/WARP.md b/WARP.md new file mode 100644 index 0000000..98ed8af --- /dev/null +++ b/WARP.md @@ -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//` - Platform detail view + +## Dependencies +Key packages: +- Django 5.2.6 +- django-extensions 4.1 +- djangorestframework 3.16.1 diff --git a/branding_system/__init__.py b/branding_system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/branding_system/__pycache__/__init__.cpython-312.pyc b/branding_system/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..cec75b7 Binary files /dev/null and b/branding_system/__pycache__/__init__.cpython-312.pyc differ diff --git a/branding_system/__pycache__/settings.cpython-312.pyc b/branding_system/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000..d437258 Binary files /dev/null and b/branding_system/__pycache__/settings.cpython-312.pyc differ diff --git a/branding_system/__pycache__/urls.cpython-312.pyc b/branding_system/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..893db0a Binary files /dev/null and b/branding_system/__pycache__/urls.cpython-312.pyc differ diff --git a/branding_system/__pycache__/wsgi.cpython-312.pyc b/branding_system/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000..bdb5725 Binary files /dev/null and b/branding_system/__pycache__/wsgi.cpython-312.pyc differ diff --git a/branding_system/asgi.py b/branding_system/asgi.py new file mode 100644 index 0000000..81382a2 --- /dev/null +++ b/branding_system/asgi.py @@ -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() diff --git a/branding_system/settings.py b/branding_system/settings.py new file mode 100644 index 0000000..462c5e4 --- /dev/null +++ b/branding_system/settings.py @@ -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' diff --git a/branding_system/urls.py b/branding_system/urls.py new file mode 100644 index 0000000..c40ae75 --- /dev/null +++ b/branding_system/urls.py @@ -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')), +] diff --git a/branding_system/wsgi.py b/branding_system/wsgi.py new file mode 100644 index 0000000..9cb8992 --- /dev/null +++ b/branding_system/wsgi.py @@ -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() diff --git a/dashboard/__init__.py b/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/__pycache__/__init__.cpython-312.pyc b/dashboard/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..81a57d0 Binary files /dev/null and b/dashboard/__pycache__/__init__.cpython-312.pyc differ diff --git a/dashboard/__pycache__/admin.cpython-312.pyc b/dashboard/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..0815c5d Binary files /dev/null and b/dashboard/__pycache__/admin.cpython-312.pyc differ diff --git a/dashboard/__pycache__/apps.cpython-312.pyc b/dashboard/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..437c79c Binary files /dev/null and b/dashboard/__pycache__/apps.cpython-312.pyc differ diff --git a/dashboard/__pycache__/models.cpython-312.pyc b/dashboard/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..3de67b8 Binary files /dev/null and b/dashboard/__pycache__/models.cpython-312.pyc differ diff --git a/dashboard/__pycache__/urls.cpython-312.pyc b/dashboard/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..ff7f19a Binary files /dev/null and b/dashboard/__pycache__/urls.cpython-312.pyc differ diff --git a/dashboard/__pycache__/views.cpython-312.pyc b/dashboard/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000..12a90dc Binary files /dev/null and b/dashboard/__pycache__/views.cpython-312.pyc differ diff --git a/dashboard/admin.py b/dashboard/admin.py new file mode 100644 index 0000000..30b36a5 --- /dev/null +++ b/dashboard/admin.py @@ -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( + '{:.1f}%', + 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( + '{}', + 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( + '
' + + '
' + + '{}%
', + percentage, color, percentage + ) + completion_percentage_display.short_description = 'Progress' + + def is_overdue_display(self, obj): + if obj.is_overdue: + return format_html('⚠ OVERDUE') + elif obj.days_until_due is not None and obj.days_until_due <= 3 and obj.status != 'published': + return format_html('⏰ Due Soon') + 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" diff --git a/dashboard/apps.py b/dashboard/apps.py new file mode 100644 index 0000000..7b1cc05 --- /dev/null +++ b/dashboard/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'dashboard' diff --git a/dashboard/management/__init__.py b/dashboard/management/__init__.py new file mode 100644 index 0000000..a94f2a3 --- /dev/null +++ b/dashboard/management/__init__.py @@ -0,0 +1 @@ +# Management package diff --git a/dashboard/management/commands/__init__.py b/dashboard/management/commands/__init__.py new file mode 100644 index 0000000..65cb83a --- /dev/null +++ b/dashboard/management/commands/__init__.py @@ -0,0 +1 @@ +# Management commands package diff --git a/dashboard/migrations/0001_initial.py b/dashboard/migrations/0001_initial.py new file mode 100644 index 0000000..698d6f3 --- /dev/null +++ b/dashboard/migrations/0001_initial.py @@ -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'], + }, + ), + ] diff --git a/dashboard/migrations/__init__.py b/dashboard/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/migrations/__pycache__/0001_initial.cpython-312.pyc b/dashboard/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..58de13e Binary files /dev/null and b/dashboard/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/dashboard/migrations/__pycache__/__init__.cpython-312.pyc b/dashboard/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..48eb750 Binary files /dev/null and b/dashboard/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/dashboard/models.py b/dashboard/models.py new file mode 100644 index 0000000..26f042a --- /dev/null +++ b/dashboard/models.py @@ -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 diff --git a/dashboard/tests.py b/dashboard/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/dashboard/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/dashboard/urls.py b/dashboard/urls.py new file mode 100644 index 0000000..4a5349a --- /dev/null +++ b/dashboard/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.dashboard_view, name='dashboard'), + path('platform//', views.platform_detail_view, name='platform-detail'), +] diff --git a/dashboard/views.py b/dashboard/views.py new file mode 100644 index 0000000..2bf1973 --- /dev/null +++ b/dashboard/views.py @@ -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) diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..94f8ec7 --- /dev/null +++ b/manage.py @@ -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() diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..c6bd576 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,66 @@ + + + + + + {% block title %}Digital Branding Management System{% endblock %} + + + + {% block extra_css %}{% endblock %} + + + + + + +
+ {% block content %} + {% endblock %} +
+ + +
+
+

+ © 2024 Digital Branding Management System +

+
+
+ + {% block extra_js %}{% endblock %} + + diff --git a/templates/dashboard/dashboard.html b/templates/dashboard/dashboard.html new file mode 100644 index 0000000..92362ec --- /dev/null +++ b/templates/dashboard/dashboard.html @@ -0,0 +1,243 @@ +{% extends "base.html" %} + +{% block title %}Dashboard - Digital Branding Management{% endblock %} + +{% block content %} +
+ +
+

Content Strategy Dashboard

+

Monitor your content progress across all platforms

+
+ + +
+ +
+
+
+ +
+
+

Active Platforms

+

{{ total_platforms }}

+
+
+
+ + +
+
+
+ +
+
+

Committed

+

{{ committed_count }}

+
+
+
+ + +
+
+
+ +
+
+

Drafted

+

{{ drafted_count }}

+
+
+
+ + +
+
+
+ +
+
+

Published

+

{{ published_count }}

+
+
+
+ + +
+
+
+ +
+
+

Completion Rate

+

{{ overall_completion_rate }}%

+
+
+
+
+ + +
+

Content Status Overview

+
+ {% if total_deliverables > 0 %} +
+
+
+
+
+ {% endif %} +
+
+ Committed ({{ committed_count }}) + Drafted ({{ drafted_count }}) + Published ({{ published_count }}) +
+
+ + +
+

Platform Progress

+ + {% if platforms %} +
+ {% for platform in platforms %} +
+ +
+
+

{{ platform.name }}

+ + {{ platform.get_platform_type_display }} + +
+

{{ platform.primary_content_type }}

+
+ + +
+
+ Content Progress + {{ platform.completion_percentage }}% +
+ + +
+
+
+ + +
+
+
{{ platform.committed_content }}
+
Committed
+
+
+
{{ platform.drafted_content }}
+
Drafted
+
+
+
{{ platform.published_content }}
+
Published
+
+
+ + +
+ Total: {{ platform.total_content }} + {{ platform.total_strategies }} strategies +
+ + + + View Details + +
+
+ {% endfor %} +
+ {% else %} +
+ +

No platforms yet

+

Get started by adding your first platform

+ + Add Platform + +
+ {% endif %} +
+ + +
+ +
+
+

Recent Activity

+
+
+ {% for deliverable in recent_deliverables|slice:":5" %} +
+
+
+

{{ deliverable.title }}

+

{{ deliverable.platform.name }} • {{ deliverable.get_content_type_display }}

+
+
+ + {{ deliverable.get_status_display }} + +
+
+
+ {% empty %} +
+ No recent activity +
+ {% endfor %} +
+
+ + +
+
+

Alerts & Overdue

+
+
+ {% for deliverable in overdue_deliverables|slice:":5" %} +
+
+ +
+

{{ deliverable.title }}

+

{{ deliverable.platform.name }}

+

Due: {{ deliverable.target_date }}

+
+
+
+ {% empty %} +
+ +

All caught up! No overdue items

+
+ {% endfor %} +
+
+
+
+{% endblock %} + +{% block extra_js %} + +{% endblock %}