From c10d4692e60f3f9c69842b4a26036cc98c81a2b3 Mon Sep 17 00:00:00 2001 From: thecyberlearn Date: Sun, 7 Sep 2025 10:16:37 +0530 Subject: [PATCH] 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 --- WARP.md | 184 +++++++++++++ branding_system/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 168 bytes .../__pycache__/settings.cpython-312.pyc | Bin 0 -> 2702 bytes .../__pycache__/urls.cpython-312.pyc | Bin 0 -> 1123 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 672 bytes branding_system/asgi.py | 16 ++ branding_system/settings.py | 129 ++++++++++ branding_system/urls.py | 23 ++ branding_system/wsgi.py | 16 ++ dashboard/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 162 bytes dashboard/__pycache__/admin.cpython-312.pyc | Bin 0 -> 8003 bytes dashboard/__pycache__/apps.cpython-312.pyc | Bin 0 -> 474 bytes dashboard/__pycache__/models.cpython-312.pyc | Bin 0 -> 9837 bytes dashboard/__pycache__/urls.cpython-312.pyc | Bin 0 -> 489 bytes dashboard/__pycache__/views.cpython-312.pyc | Bin 0 -> 4574 bytes dashboard/admin.py | 181 +++++++++++++ dashboard/apps.py | 6 + dashboard/management/__init__.py | 1 + dashboard/management/commands/__init__.py | 1 + dashboard/migrations/0001_initial.py | 79 ++++++ dashboard/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 6682 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 173 bytes dashboard/models.py | 181 +++++++++++++ dashboard/tests.py | 3 + dashboard/urls.py | 7 + dashboard/views.py | 122 +++++++++ manage.py | 22 ++ templates/base.html | 66 +++++ templates/dashboard/dashboard.html | 243 ++++++++++++++++++ 32 files changed, 1280 insertions(+) create mode 100644 WARP.md create mode 100644 branding_system/__init__.py create mode 100644 branding_system/__pycache__/__init__.cpython-312.pyc create mode 100644 branding_system/__pycache__/settings.cpython-312.pyc create mode 100644 branding_system/__pycache__/urls.cpython-312.pyc create mode 100644 branding_system/__pycache__/wsgi.cpython-312.pyc create mode 100644 branding_system/asgi.py create mode 100644 branding_system/settings.py create mode 100644 branding_system/urls.py create mode 100644 branding_system/wsgi.py create mode 100644 dashboard/__init__.py create mode 100644 dashboard/__pycache__/__init__.cpython-312.pyc create mode 100644 dashboard/__pycache__/admin.cpython-312.pyc create mode 100644 dashboard/__pycache__/apps.cpython-312.pyc create mode 100644 dashboard/__pycache__/models.cpython-312.pyc create mode 100644 dashboard/__pycache__/urls.cpython-312.pyc create mode 100644 dashboard/__pycache__/views.cpython-312.pyc create mode 100644 dashboard/admin.py create mode 100644 dashboard/apps.py create mode 100644 dashboard/management/__init__.py create mode 100644 dashboard/management/commands/__init__.py create mode 100644 dashboard/migrations/0001_initial.py create mode 100644 dashboard/migrations/__init__.py create mode 100644 dashboard/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 dashboard/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 dashboard/models.py create mode 100644 dashboard/tests.py create mode 100644 dashboard/urls.py create mode 100644 dashboard/views.py create mode 100755 manage.py create mode 100644 templates/base.html create mode 100644 templates/dashboard/dashboard.html 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 0000000000000000000000000000000000000000..cec75b7989553ec3d7df30715566803d3366e0b6 GIT binary patch literal 168 zcmX@j%ge<81P62WWPs?$AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxd<*uKRpPQ~vm6}{qte=vZo>`KZqnlKen3s~7m#$k}SzMBus}JMHL;3OXnR%Hd@$q^EmA^P_ ea`RJ4b5iY!Sb^p;0&y{j@sXL4k+Fyw$N~UXlPlZ+ literal 0 HcmV?d00001 diff --git a/branding_system/__pycache__/settings.cpython-312.pyc b/branding_system/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4372582cc820fea285f842cebac51e39165a21b GIT binary patch literal 2702 zcmb7G&2JM&6yLQSf2=>^7()z{%7_+fsMnc7zcpckSZKy!-JQ*Q>;3#Yza+rctOrK@D-y?O8b-kUe?&G?UW zTHxSI+Q09dPjlQ~J~REI6XJ!ybKFlH;#3atC}i+vNDc8^h{GWq{taaQx0@Kxv*T|Q zW;gILD*^pCtf^5HR%0ll#(SKa=y7P_7hV-$j>4QoF*SwaIEoTplH+{Dmhx zO8p$^@oE~S)eOp@MbOInT4?F#5aHEDltU*#D~IyIcrqAI5xy5uvuIgeLIpJkeow%E z9-UTCg8iAX7xfg-XMI{-M(?PFgL9>K^=q(cp|-1AJ-bM;>k_L+i`%wS>^QoGfYay! zb+K9OJN7Q_x}qSgVGBFDi&3$2P`rc!+p>;KVil>YJMKjDQgJ}RzBmEDE`BWD0P9u2 zHf;wNi3M&<-6ghFggsb4Gsq9?C7jEvA&l{+0?)M&EfB5O(=4bESzTNFyKltd1 zb#3r%yOi+qV;He(TdqSoVvtnt#Qf?2Jf59f^i8nkx(9tsz2!L}#gsytUl*7l(0gEU zX5PYugX#LmL0QJZ)YY~f-Nbvg^MEalbv3*X9s^vKKsw#&*t&yCF|RzqG6_Nk-qRf{ z23aBQfvEWD0b>1F=3Se2JqcEvu~)`xGghV7oj_ffwk1}B=|5Ddvn^I(xJU5bjOwRz zvFMQM**GNNA3V~~R91?x*osuWD>rK_Qmbz&EG}(q zXaN;#ZM5sHrsBYh>Jh*1X!HVr4WnC z&9!<{W|5|}E<20R_}-nl4FrYK*C~Y#K^j~x1O@KIEs7n<^}BiCVuhH*(Ea+qH`cHn z@7CKQtrLrw1G9mxp1bqP{>!(uANR$^B*s?&x8D|^IWVz9y00TFV5>Z!EWFjOdNFtc zpg$@sIwn|hJO@{acW&Yf(relX8&q5&J!mR>m0$xkOQ%^()8NY2G#2Z_#cGg_6NY@V zxTg6C{yN-7w(%Wi(LUTPmdoNRk|O(Mzyw9EZpv-#uKW#KsL3l^Yb-4_8m;?sO}o=l z+6v3mn@Ss&U;%1JVZwU7R%^)jrA?WoHe0Q>wzb))wwkLf(U#XY8d6(USoXfMR`>NA z^{V8T8B5fpw&c52SV7uq-_bTCMY-SFtZDb8MjeDLsLpglYOZZbYqC~t)novyugjX+ zf;x(CLBRE^pEX(3r`jq+-Uj5V@114G^lH5!E92@;PNaT-ELW3PrL9I=1II0GwJtYm zzj819*XZS1WydygMK_6CnLL)Xf=G|Jx>2598RhYNUYYWf_x<>36Z;1*<6pq(3=DkB z5dqi#Swt_wI12MT|12c%k$(ytfBIRJOJ#qI|8VO`^88`){GZ7SPm-4plb4?)uO23^ z4&$Q?m)Ycp!YIlm(!+(LY;Kr-E(p2MF!Q_+&xAmd=du@vsZpHE%ly-&(~s^RE?s(@ z{orZ7aFi-O=OPy}qi{4YJWC`OqN8v+9U3M^?{fJYkGU+MFJzwP#HZQ9qc0A#m!2(z zv!JsO=$zpai;uJFC>#R2c))DTGGEOyn`7o=`kl~Yt}wdDEft38qtwzcag literal 0 HcmV?d00001 diff --git a/branding_system/__pycache__/urls.cpython-312.pyc b/branding_system/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..893db0a55c6263008403418c4dda0c452c7d58fe GIT binary patch literal 1123 zcmb7DL2DE-6wb`fbhlI6iXeE5NSzL}6D;(ySc?>k1@Rz?u$M8r$?j&EOok+@Zt>zp z>dl_|8>D}s|G7#t|T(1BgT1)ZuQ&b*$Y;8{F%7-0%1fZ~^s*N8Qo28sO?XTDr1p>)4%I zs+-FBBG~aGQ!zF)aiPHBbuSjcmrvXvz|gW7@7161&`ihZm?84*d1rR zl6sl81icr-hlD_!LB?|-bsNII6ddwNg7ds`PHC3$8&$kjU0i3PPam9hf%PTVx`u*Mi`kv{TmCRhg4c3aml!*^Vneh4;vdFW0hEt)#wjKds#0X z+a8)Cl=-YQ+v&PKdu3sup<>!&W9e!H`&B91({fGQ5!-^-B&*o9vMB$|wwsCpG6k$N zh1OBxhEWv!v@bMr3Lg{BvdAqwTk)bWW841!e9_d@RrNP4v*nnxA zuGiDCKAjF!pQ~vp-)EdpE?f7xAmfxikk<{S9YeKcwzG!gIOp!NZ%S@GIr*`)_zpAg&Exyt_uiY2Yij{=_CdZMo??W4&Vte^ zOJ{oEoKwV64{^*riF?@HzVuSR=VJtTv|2(g?@{g2FFju2*Ur2}gpr59KU=1HD-*A= za{MiL-EFl=tkpmyWT10$kTb=FN+L5c7E+SsdIYfzgP?5*yveiyL+lX90?4Ff2NDP~ zgppM9LkUd@V~}LbaxqUSmI5dJKFcyGVrGR_{l4RS=BlMS2O^X!oX!(OD@QI283+mY zhye(O)@J4*rCi4*wO;eOIukf*Ollt0Hhf4g+>5ZBe^Y`5l_hlwCY^!c(9u6_zf$GL(x%SSFjL zh~{Dyw2YUlnYS2blZ(|yi@`t6>yFcXDr7d@@yEv)|MjZ4GQEj#@C)7lj=Z~vm6}{qte=vZo>`KZqnlKen3s~7m#$k}SzMButDllsoRO5DSd^k4AD@|*SrQ+w jS5Wzj!zMRBr8Fniu80+AA|ntNgBTx~85tRin1L(+AT%jG literal 0 HcmV?d00001 diff --git a/dashboard/__pycache__/admin.cpython-312.pyc b/dashboard/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0815c5dc23ab8d14ce012858452175857f70d57c GIT binary patch literal 8003 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 CGOr~7 literal 0 HcmV?d00001 diff --git a/dashboard/__pycache__/apps.cpython-312.pyc b/dashboard/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..437c79cc329425a1970fccc11617406085cbb1b0 GIT binary patch literal 474 zcmX|7y-ve05Vn&P^%oFAMGOoqNEw=}41iRqVCw^9vD}`UrgDCWlOb$KeE=r<42W0Y z1rQ5k6^V(74XLnn!cI~=+vWn(o>%@deU$lAkSPv zO(WkKP%#~aG=_4(-4V=a;D#2AEI$Osg>~t<{WJ-$J;ot#z(zkbYk8fJfclJ(yh(_Z zr(EGSA-5^zRb>y@gr+b44&D5FHujXnA=0ZgiKe?)@NEH${DD>Upc$PBq8M r%d)+#P$ArwXk9-mce#jnB~>;GW#PAuG5$cuKU*3;c<_FaOhwlpA&h&U literal 0 HcmV?d00001 diff --git a/dashboard/__pycache__/models.cpython-312.pyc b/dashboard/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3de67b8bcfa1a7a533486df398ba44c85aba533e GIT binary patch literal 9837 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 C?+oq$ literal 0 HcmV?d00001 diff --git a/dashboard/__pycache__/urls.cpython-312.pyc b/dashboard/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff7f19ad52723685e1fcefca2aeb120cc5218620 GIT binary patch literal 489 zcmYLEyG{Z@6rI@@ESm+RcG}PwC}0~af)sQ%b~bB424~z|vaiez8t7~cf8Z}*^b`C8 z8%Riqn^0NNVd$uw0T*s@?>RH)+{c$`766YI`svPj{ShP!?H{aT4qgEU82TW9HiQ66 zSoWnrwq*z;Kp<9#+{>;9o)$~Hv$XLh2Eo)fHXblsFJpZPeOAXvZ zp%c~n)K93`l8Te5%2Qh0_wR_2>1pQg?EJe|gIoD}jLtK`5aGU0TJ(r_6K5w4o>nr1Z~@L2 x;N({Wsy;d%-OkPO)GUvUnR5B9H9hS^2n1s@iQ_0tG>FR^qDsD6^|L zQYb(Mdhnr#-0Gr6tDfq@I^@s-KBPd80(#jERV=ZPk)SP5_@+bx3Isj$4ap^GS&kid zK)!wR=FOWoZ{~e=|K@f(5VX7QzZCxLN9dolV>ELM*}DzMuMkGqR753H#$=+nxo9pi z8K!|VMN7$=v6k2j+vr=0wi1`&0Jmbc$dv3Ed&!Y;l$;qS&}_dz85iaNx>xK8$5XmY z!lbiAC}V+8tW?25QA(I~>$S>qS=LRnK(*kUEIc()W=7|JSj@@u6;jfBu2ssiP?o2J zqWGaea&tvN=Wfabm={*^l-+1vKp6P%{liN4BO7$IXza92L1+29?lt!RJYTo5PUng3*kP=)NN>}DCW8kx8+ID^q?;!9w#S)G zm%*{=He|kl*YX{BtuNtqbm`#P^gge{rteYfhOODEwkh;8u=*NTZSQ-JY+MN}?xG`DX0uHls5bLrQ5MK8 zK>IqPvRP5ewluctZn+}oirJPN-QQNcP4c!j&?flX8)=h#O(~s4bk4BRsx!H~L~}9D z=Hz#u()s~1wO7uO1wqczfo!(e*ekUhC|A~(*qqXmIOjYA&FE>u8@t{V|OnQTY*#PpW+K)7uK4tnrsMeoE!1YW(#2dpp5#EqF!^p3#Em)!_MB z@SV#GGiqR_7I<%c=H)VWS;U3zU;W%RxN~ImVNAI=qb6pQ*<0$^E#)Wg*N)s)x!9LS zh7~Th&mwG)Y?38jlw|#MLdm#TjM=*N7`lqW~Q`?iX+rc00 zI+4x0hgciCYeiPiE|2=6UnAB!agVDzdo|~f>Kxj*`nmJiPV#geRP>Ll{_%$eEj6XW z&p)-rsJU2C^gZ<+#qJZ2ZfVbXi^PLYN2at=vtlY0p@UXBlp&7 z?BM>(z? z{)a`jzPsg5GBq~-hk@S@JWSSx&h6TeHMs9Wu~99WQllv?npUIfzgm=Nx)!~zMQ^Cl z8@1?+VhML0PVDGLMLGA5I`Ym|UOhIe+`Ltb{aE25JA+3yW|YZk_2~50usS@m^+9cL zR^dWBhfYwr$d|D=Osfw@fvMiW!5oqCdh}#He7qhUp>X&D{9tiD1|WW>9-joBP@>*{ zv>qJ$%4QjG?0V2pn$C$&eswnDW!K-_zl2;ppNuNu^rv@f&MS)L${siY%C`z@iX$2{ z8ZS{J@FMg&FHu#~rpYKo*32@EKaF;1Awz@QfmStly)@8>bD&|SEuE3u#pJ6?$CgcN zyGT1sKHHWL1rIGsuX7fa#Asf={t%SK5C5K_Rkcx>|A)>sJU>a22)t#6Kt<+be?T|KM z*!zJe$5+Gy)n%@ta^txK32TLHSd}Aw;)tOXzWd@ zy-BTiQth3Du&~RZKza|E16^)SqxhJIghgAQN<{wl2W58VR;bE*N0Z!c0aANP> z!oAfR8=}7J-F2b9!TP|_Iv)pTXk30*>+)ZG{C>@OU9nszS3t#gp(63&#awxzl9CD) zBIlPO-BJ(G?JY8%BNo~>*zjDc`2nMoVReqyMY32D8VR#$dzz=4cBc7;P|N%gH5P3c z@Eh(Dh*>TQm&p%ch<= 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 0000000000000000000000000000000000000000..58de13ef28692fa604e76fe401bb2ff376be9f37 GIT binary patch literal 6682 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 literal 0 HcmV?d00001 diff --git a/dashboard/migrations/__pycache__/__init__.cpython-312.pyc b/dashboard/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48eb750bb32fb825e0567d6dcee40a29285b2564 GIT binary patch literal 173 zcmX@j%ge<81SfO$WPs?$AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxd<)fdGpPQ~vm6}{qte=vZo>`KZqnlKen3s~7m#$k}SzMButDllsoRO5DSd^llo0(peSdy8a uSF9f&pP83g5+AQuQ2C3)CO1E&G$+-rh!to!BM=vZ7$2D#85xV1fh+)68ZEQ{ literal 0 HcmV?d00001 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 %}