From 02b88019d31f4d96a324a17b09e50aa33cfd7dcc Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 09:52:27 +0000 Subject: [PATCH] feat: Implement production-ready corporate CMS with Django - Initialize Django project with Core, News, and Contact apps. - Implement Page model with Summernote rich text editor. - Implement Post model for news/blog with image support. - Implement Contact form with database storage and admin integration. - Setup Bootstrap 5 frontend templates. - Configure settings for static/media files and security. - Add comprehensive tests for models and views. - Add run script and documentation. Co-authored-by: muumuu8181 <87556753+muumuu8181@users.noreply.github.com> --- README.md | 120 ++++++++++------- config/__init__.py | 0 config/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 120 bytes config/__pycache__/settings.cpython-312.pyc | Bin 0 -> 2470 bytes config/__pycache__/urls.cpython-312.pyc | Bin 0 -> 1009 bytes config/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 606 bytes config/asgi.py | 16 +++ config/settings.py | 126 ++++++++++++++++++ config/urls.py | 16 +++ config/wsgi.py | 16 +++ contact/__init__.py | 0 contact/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 121 bytes contact/__pycache__/admin.cpython-312.pyc | Bin 0 -> 657 bytes contact/__pycache__/apps.cpython-312.pyc | Bin 0 -> 429 bytes contact/__pycache__/forms.cpython-312.pyc | Bin 0 -> 1054 bytes contact/__pycache__/models.cpython-312.pyc | Bin 0 -> 990 bytes contact/__pycache__/tests.cpython-312.pyc | Bin 0 -> 1544 bytes contact/__pycache__/urls.cpython-312.pyc | Bin 0 -> 316 bytes contact/__pycache__/views.cpython-312.pyc | Bin 0 -> 889 bytes contact/admin.py | 8 ++ contact/apps.py | 6 + contact/forms.py | 13 ++ contact/migrations/0001_initial.py | 32 +++++ contact/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 1190 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 132 bytes contact/models.py | 11 ++ contact/tests.py | 19 +++ contact/urls.py | 6 + contact/views.py | 15 +++ core/__init__.py | 0 core/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 118 bytes core/__pycache__/admin.cpython-312.pyc | Bin 0 -> 710 bytes core/__pycache__/apps.cpython-312.pyc | Bin 0 -> 420 bytes core/__pycache__/models.cpython-312.pyc | Bin 0 -> 1248 bytes core/__pycache__/tests.cpython-312.pyc | Bin 0 -> 2254 bytes core/__pycache__/urls.cpython-312.pyc | Bin 0 -> 395 bytes core/__pycache__/views.cpython-312.pyc | Bin 0 -> 956 bytes core/admin.py | 10 ++ core/apps.py | 6 + core/migrations/0001_initial.py | 42 ++++++ core/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 1565 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 129 bytes core/models.py | 18 +++ core/tests.py | 28 ++++ core/urls.py | 7 + core/views.py | 12 ++ db.sqlite3 | Bin 0 -> 159744 bytes hello.py | 94 ------------- hello_0718.py | 5 - manage.py | 23 ++++ news/__init__.py | 0 news/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 118 bytes news/__pycache__/admin.cpython-312.pyc | Bin 0 -> 744 bytes news/__pycache__/apps.cpython-312.pyc | Bin 0 -> 420 bytes news/__pycache__/models.cpython-312.pyc | Bin 0 -> 1773 bytes news/__pycache__/tests.cpython-312.pyc | Bin 0 -> 2268 bytes news/__pycache__/urls.cpython-312.pyc | Bin 0 -> 394 bytes news/__pycache__/views.cpython-312.pyc | Bin 0 -> 898 bytes news/admin.py | 11 ++ news/apps.py | 6 + news/migrations/0001_initial.py | 61 +++++++++ news/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 2264 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 129 bytes news/models.py | 24 ++++ news/tests.py | 28 ++++ news/urls.py | 7 + news/views.py | 10 ++ requirements.txt | 5 + run.sh | 2 + templates/base.html | 60 +++++++++ templates/contact/contact.html | 51 +++++++ templates/core/home.html | 40 ++++++ templates/core/page_detail.html | 19 +++ templates/news/post_detail.html | 29 ++++ templates/news/post_list.html | 32 +++++ test_hello_0718.py | 16 --- 79 files changed, 887 insertions(+), 163 deletions(-) create mode 100644 config/__init__.py create mode 100644 config/__pycache__/__init__.cpython-312.pyc create mode 100644 config/__pycache__/settings.cpython-312.pyc create mode 100644 config/__pycache__/urls.cpython-312.pyc create mode 100644 config/__pycache__/wsgi.cpython-312.pyc create mode 100644 config/asgi.py create mode 100644 config/settings.py create mode 100644 config/urls.py create mode 100644 config/wsgi.py create mode 100644 contact/__init__.py create mode 100644 contact/__pycache__/__init__.cpython-312.pyc create mode 100644 contact/__pycache__/admin.cpython-312.pyc create mode 100644 contact/__pycache__/apps.cpython-312.pyc create mode 100644 contact/__pycache__/forms.cpython-312.pyc create mode 100644 contact/__pycache__/models.cpython-312.pyc create mode 100644 contact/__pycache__/tests.cpython-312.pyc create mode 100644 contact/__pycache__/urls.cpython-312.pyc create mode 100644 contact/__pycache__/views.cpython-312.pyc create mode 100644 contact/admin.py create mode 100644 contact/apps.py create mode 100644 contact/forms.py create mode 100644 contact/migrations/0001_initial.py create mode 100644 contact/migrations/__init__.py create mode 100644 contact/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 contact/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 contact/models.py create mode 100644 contact/tests.py create mode 100644 contact/urls.py create mode 100644 contact/views.py create mode 100644 core/__init__.py create mode 100644 core/__pycache__/__init__.cpython-312.pyc create mode 100644 core/__pycache__/admin.cpython-312.pyc create mode 100644 core/__pycache__/apps.cpython-312.pyc create mode 100644 core/__pycache__/models.cpython-312.pyc create mode 100644 core/__pycache__/tests.cpython-312.pyc create mode 100644 core/__pycache__/urls.cpython-312.pyc create mode 100644 core/__pycache__/views.cpython-312.pyc create mode 100644 core/admin.py create mode 100644 core/apps.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/__init__.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 core/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 core/models.py create mode 100644 core/tests.py create mode 100644 core/urls.py create mode 100644 core/views.py create mode 100644 db.sqlite3 delete mode 100644 hello.py delete mode 100644 hello_0718.py create mode 100755 manage.py create mode 100644 news/__init__.py create mode 100644 news/__pycache__/__init__.cpython-312.pyc create mode 100644 news/__pycache__/admin.cpython-312.pyc create mode 100644 news/__pycache__/apps.cpython-312.pyc create mode 100644 news/__pycache__/models.cpython-312.pyc create mode 100644 news/__pycache__/tests.cpython-312.pyc create mode 100644 news/__pycache__/urls.cpython-312.pyc create mode 100644 news/__pycache__/views.cpython-312.pyc create mode 100644 news/admin.py create mode 100644 news/apps.py create mode 100644 news/migrations/0001_initial.py create mode 100644 news/migrations/__init__.py create mode 100644 news/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 news/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 news/models.py create mode 100644 news/tests.py create mode 100644 news/urls.py create mode 100644 news/views.py create mode 100644 requirements.txt create mode 100755 run.sh create mode 100644 templates/base.html create mode 100644 templates/contact/contact.html create mode 100644 templates/core/home.html create mode 100644 templates/core/page_detail.html create mode 100644 templates/news/post_detail.html create mode 100644 templates/news/post_list.html delete mode 100644 test_hello_0718.py diff --git a/README.md b/README.md index a3a7888..bd71229 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,72 @@ -# Python Visual Effects 🎬✨ - -Python で作成した美しい視覚効果とアニメーションのコレクション - -## 機能 🌟 - -- **🌈 レインボーテキスト**: 文字が色とりどりに表示される効果 -- **📝 タイプライター効果**: 文字が一文字ずつゆっくり現れる演出 -- **🖍️ カラータイプライター効果**: 文字ごとに色が変わるタイプライティング -- **🔄 スピナーエフェクト**: くるくる回るスピナーアニメーション -- **💻 マトリックス風エフェクト**: 日本語文字が緑色で画面を流れる -- **💥 爆発アニメーション**: 殡階的に拡大する爆発エフェクト - -## 実行方法 🚀 - -```bash -python3 hello.py -``` - -## 必要な環境 📋 - -- Python 3.x -- ターミナル/コマンドプロンプト (カラー表示対応) - -## デモ 🎥 - -実行すると以下のエフェクトが順番に表示されます: - -1. 長文のタイプライター効果でメッセージが表示 -2. カラータイプライター効果 -3. スピナーエフェクト -4. レインボーカラーでテキストが輝く -5. 日本語文字のマトリックス風エフェクト -6. 爆発アニメーション - -## 技術詳細 🔧 - -- **カラーエフェクト**: ANSI エスケープコードを使用 -- **アニメーション**: time.sleep() による時間制御 -- **文字セット**: 日本語ひらがな + 数字の組み合わせ - -## 作成者 👨‍💻 - -Claude と協力して作成された視覚効果プロジェクト - ---- - -*美しいターミナルアートをお楽しみください!* ✨ \ No newline at end of file +# Corporate CMS + +A production-ready Corporate Content Management System built with Django. + +## Features + +- **Core Pages:** Manage static pages (About, Services, etc.) with a Rich Text Editor. +- **News/Blog:** Post company news with images, categories, and tags. +- **Contact Form:** Secure contact form with admin notification (messages saved to DB). +- **Admin Interface:** Fully customized admin dashboard for easy content management. +- **Responsive Design:** Built with Bootstrap 5. + +## Tech Stack + +- Python 3.12+ +- Django 4.2+ +- SQLite (Development) / PostgreSQL (Production ready) +- Bootstrap 5 +- Django Summernote (WYSIWYG Editor) + +## Setup Instructions + +1. **Clone the repository:** + ```bash + git clone + cd + ``` + +2. **Create a virtual environment:** + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +4. **Run Migrations:** + ```bash + python manage.py migrate + ``` + +5. **Create a Superuser:** + ```bash + python manage.py createsuperuser + ``` + +6. **Run the Server:** + ```bash + python manage.py runserver + ``` + Or use the helper script: + ```bash + ./run.sh + ``` + +## Usage + +1. Access the Admin Panel at `http://127.0.0.1:8000/admin/`. +2. Login with your superuser credentials. +3. Create Pages and News Posts. +4. View the site at `http://127.0.0.1:8000/`. + +## Production Deployment Notes + +- Set `DEBUG = False` in `config/settings.py`. +- Configure `ALLOWED_HOSTS`. +- Use a production database like PostgreSQL. +- Serve static files using Nginx/Apache. +- Use Gunicorn as the WSGI server. diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/__pycache__/__init__.cpython-312.pyc b/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f94899df09986c22704052f4d7e688927c009db GIT binary patch literal 120 zcmX@j%ge<81Unv2$^_AmK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^OI$y(pg=!4KQApa tT|YiPGcU6wK3=b&@)n0pZhlH>PO4oID^MLH5Ep|OADI~$8H<>KEC7)V7}5X$ literal 0 HcmV?d00001 diff --git a/config/__pycache__/settings.cpython-312.pyc b/config/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d96af2e6e12aaac594922488fe343fb119a5e31b GIT binary patch literal 2470 zcma)8J8#=o6ejh4TXN)QlE#LSha8B88#l=iq^2d>5u#T`DNZ?*pmnchI(#T36~~;^ zxl7Qc9x`Ps@>9Cl=n&&}G0-mEn9j_g&ug4aV~5B9x>80XoI_V?lS{Xp4; z3Zu_OHX(?7LJ0YVFbcG2A=08y@Et8gVGqF`Lt!C~A~=YmHdJfT$d6*+i@&2~8YSNP z0Fyvt!Wc@T6hw_XQRvEBk4y_mltvQ}l}3~9I_0j@GTrhCDKsODqgmk!R8GU+1iC6r z0{)tV7pBm4VH&irTx|y35N6SwaCP6CnYV97^1G7O(&tQUS+dqL=eBiYuBmI=a%-+_ z=)1USF_FKWchAtoSCy=tvb}~}Ojg!R-0T=Qdq2Bis0;H-_6Kc6d9b=IOZu(5FQt`L zb!SytmK9?`+S_mMx3@Fd&dt{kJBnN<^kODrr~8>0C}rYY;%d4~!PsRfAHQG_K~a&uc&ut`#VpJ;Ye6&spvVd87*2CN$1 zGfB`XEj6u7(9RCBs4_wd-jfW>xRl7&KEpfQfKdAEvsXro4%%df@OJ`DD=TdFA|VE+y@If@GCcg7hM|yj=sbhrNiJ-pXMRd{R>_~ zk()1erREEmJLdViA*s01c8ZxpxM+VCnU0~D_R=7Uh1IqqS(w=ZG}e&G3>_^HB`KTb-7@c!@{@zwBVsvhNOJon-Dne4)mZ5L>N^t`w|P>xD|0x2GTh9AjqBY{`t& zZUf!!wkE_SOGUQuJv#!%ft>M>09W29lsV!nvn9?*!F6Ci8R^>{ zptfY1d%A(dSCS$lw;aluG9*^dgdAliXzgRqDX>mxPcXU%kaV{ARgT$ zAyIS+I6ecLy7CH>KpQS1%_5;Smgk(IfNWf@#?{5A-1o$v=hhkm*#~O4TA;=XjFbd-vOJk)8}%n*mF4-(N-ZxwV~YiFR$x}+MYg=rU^h51SIKjbuu$Sep#tL- zYCyw<<%eYwaHP5bo!23A&Z$nK1KDYZgiBn$z`FKGUv$mmd2XF;6zd{XuZZgfu9zod z&&Bl`&?yd1#;?>T=LhZIsU}FCKOu^q`GX3d2B=u_P3Wh`AENU|(fQw_3m>A3N72O((dDD)ayQgVP>CAdjr0Ok zINtT2q|$Hv-T22ybllTTe4Gs>Jm5=H$?M%%FGQs|`ZzUn@boBk`!IRuI6Zw5oBK%l z7ZN>hFdaD!$NYhwHxc)A!@W5wojs(Ikl&v;9%qh|(+7`^lDAL&-Xz5M-I!S_Ja(8A zdR`CULatIBR=yinYJFuQe$8`8P4{k5sp)R~B$n!iPoiVp&v~yQZc5oYQ(H+?F`UD!G64;{C+4)ZsJuP^w7Vy)d>SYo!buQyKAY#U^jczVL7rKN%Z||)85ot^{a@dP-*l8KR*&}m`CEY);m*ad8w_M^lJ(%Tx5 zVqI8$j{S6bA{~%y7>1O={9&5bE@6Q&{hjV5Z$_&192{UOv{V}r$Xtj-BmE9GeVRr1 z$*H>w_ek*J)a3MdVIlW`gjz6{&CZk10>BwJNhE)UwRX zQ`Kc6rgc-PX()rw&>HqLTAyfW`Z{`%17bvJs&g+nZj*B3XRxu=pY9A;_s?c(}VH+;c`7n_n0 z@vTVBJ^*{(WL7g@VWfU@^XhIq*@!2%@9(CwjdXS=Hrs%+D>MJZ;YNIT`^oO;Tw`=@ TCzfl#-0PVp4`3Ac)2;skjTQ4` literal 0 HcmV?d00001 diff --git a/config/__pycache__/wsgi.cpython-312.pyc b/config/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0255d2cba74ff3fa8da873144ca910738a39d666 GIT binary patch literal 606 zcmYjPJ#P~+7`E?1Q(b64g?6?OOC&g#iV2}gMJ-Vfq(h33vRvHro5YdNKAqi^bnAfF zS@;d8zkuJsQUn6&CQ!w|2Fk{SFV|E14)*hYK967d)5=PKoP9ihC*EL$&I_lq+69;& zIPe~E)JGh1PvSmyw=ccK@B0`5o-9`_m-nP`i7Uiw{MxBkdKh^K{L_n6e`)5mmX5y# zuX^nciM1Mv5gF>Nf@!8F5Zf>aI+noeR2wkFj)9c*OiFejfiOcDNi;u{a9_d{B$={I zEK-Ukz{z01(o~9=S)tWn;O0GbB#F*|2<7;aMS^JMh6_W6Lc%>_0D`f#sd+>x*Rcut zgsG9PG{rihpy5&-;8vHI^*=^zc_6TfL#G)%Q^7Wl(+uinF z)Z5+P?{wR}=*8a7%jdhtH_APP2CNloWLovS9z{+;6y=^a`OQ*C7(4l4F*WlYPhJN# z6`59f;2Jp$*`c&~;|k#iG%p1GyG31)mLvv%9^gmeFt6xMF;_N7#2>7BaFZSj%J;8(Z84Dl~J_jz-wUc88FXe*8Q6J8dtdSR&5~i zOFs;5EDMqLC`*&<#%dYXe>Kkv8W$6;6rJ+8EvcWj9aIktJqHdEVh}kDMVL8}%W&kC zZs_U3{~8BYKXkR5(UfbCr!)~dP>bUkj|<(6C8q^vgchMI?Vj?E1-pk1(q@bh$-9Oh zhQ96)Vs!}7T|&}1TL|lK5OT7hqW#$ri7E(7R4!<#dx}#TzbEe!E|}7rrXQPUqHF_S zpfb|%XE>&LK8~C9$MsS6^HK)puLjpR>Yjl!xE{P%zPa3bwwzqv-#tB&TkD*|FwlKA zquF%6Z{*5ntTJ| zQ)noolc=DfLozf}-1^KFyEC^ltJS>Nb_diOd@Q4->hDDUU#kJtu>va)ATWk0`iNk_ z6$oPi!VK}*)H>goTW;%LP-n5O5+Y#dDjJ=PT)QaK8&AOs%;_=Z5=6ozljP1lmP z@)bNNwEg~2-16BZ@x+ZcO+*;y-dQvmmPK+Ig|U#EfmV;)mTWUN0WdZAhlUHkh z-E|%=GWx0KBiEGO=43Kw6pyy|2-U)Eg=Bl6Pdv?b6s2TxO6AK$#5Xwl-Y{_Q@%jf; Gt6hKJ=V^)n literal 0 HcmV?d00001 diff --git a/contact/__pycache__/forms.cpython-312.pyc b/contact/__pycache__/forms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d81c1c090ff230732f5393c6479ec8abeceb74e3 GIT binary patch literal 1054 zcmZuvzi$&U7`5+b?wVGGHVP6@RYgHM)HW*vVnHopXgZW4aRXQP?1n^lN$_1nlc8NY zlr3S&(uoa~p??JnOCv=hvQ!BPmTo{mNS(0HiP}i@@b207{672n`!YFc13uP&yf5u4 z0DKpd(a?uvu*yjXV1Qu&Lg+#Wu!7Y<2~}5xKmkty)(!yH2`r~Zn=ik|s^qTTFdXw- zOdQDcohl8Zb0}w1PpXv|`SBWwB2*;4+#yjLP6qRwbN~S^#K2XOvXGZmj`_jGaMgzD zXl!EWwaN>O8I8_1_{_zZMnrYBBEQ=Q=;{g}MtLf68v}7)tQ{$z^kZ>iJ`&}$xOh~ zrjy0t!xfwfmhwDN-t(C4d0`dT1Hsdt_oj{l`G<WGxj@^-r<$#9EXCVQSp{^M!@W7 zncfyGQQZ~H9D!)_D9zyj{8Li8{TpzQLZK-*1~YIORf<)al#!$t$tbNg5vAyOX*gbl pr5?$5cuW_#mFVC|D1NFC!ZUE|3@rT7ENHiKoqRk07jT*s{tpF8{MG;f literal 0 HcmV?d00001 diff --git a/contact/__pycache__/models.cpython-312.pyc b/contact/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edc6923b8570a08c8ed46e4c992fca7f22612f4d GIT binary patch literal 990 zcmZ`&%}X0W6o0cHW=%G0;~^AM(?IdClqQ!_q?BI#0zog89v0hS&5W9`o7mkARVgJ0 z4<5Cb2vV@8w&_m-72lmZ-zxVy#?6-6}3H;3cd{=pi z0DcJJ;^=**_n0#+AV81@K5`HO1!jRL+dx!`s@jFa)qSK&-cdI-TR#_qjo6T1C)5ke zS9Sn>L+E|wXbUJfh=8LgK-H=-XefDtj+G-`s5p)uTbWWCq(^@8Cep!!6hPJ^;ZzgPmS(Eizfr&T1dT>P^2vU&6eEt^-@`uU6QLRu5Ypg#$gm-yp_G)Qn8PV z2NdQwX+n22+p_oU-Q51mJA zIY6)*1tZ+&GX%Hz6*ND;+D}w(_6uoms`^>;Q6V6}X%k-_3S14dR>I z#4mBWt7rMG(w610eH=Jcv0Gdw*F4DqTY`VXh2;A*`&YddcN4Fj+~iVvo4lRnXBI=S z&1^73!bywzq^0?Eo3wOa18Z#Qj-e{~9JoxoNWw%s4en^R4Q5F0kpTMREy(wL*q;s^OSc{S{Hdl4$-@oB1ed+IE4jqU>-9gE*SubDEuVQU0U~6h zx24`j1gR;-ZVw1{4^2044iVS**U%CyBiF!uN2axt!;23t4zE18^5mnP3*(jbaqawl zHf8e@W{;Tt$ex@!J34iCN4{d`W{^UT+zE1&-bopF{l9nOYp6uu^h=M8g6IEuKUR@P zQ6ClD@6ndb7vrE>po2bm>i`$X*PpRMEkUjD9`w68EMih+%rym;<$!$}f7PvDkNMz4 z0O%^e>qWhgH`;MDm`8=zKkt7T@OF}q^1^{KFuGpEmB}No6Dmt)QOb(4%IY+LZ-)x3 zC8sR#P|E1VGSi&EqlraymNWI;5`8Z4+LaZj>q%AG=!6Lus>JVhKw#no{LKX@@R;iK z5e;p`B649HQAf&7+#Rg`quP!|A`h|$=8f1UwUtqA<;jhmPsV4?PnX`AE`KmR`96HV z&Y2g=Gh!}P?z7ibGPiJCgD=@RQ05Pd-!DF{PVC0WZcOa;k-h%nQuBqqKDIxb*tbXa z?XmsED|SaL!=g>7N#?pprt7M*>qfEP4-wD1E|z5WM=U|CsDpV*(!7)cf$~lbz94b| z%r^N=)65TNY|d=aEE0p7j-@Jo;B`0SwDbfP#T!pwgfe>!O0YbK0bWHm`x!*I(~NX> pDcj&>u?jwXtmJ7huXRf49;xq^x$ literal 0 HcmV?d00001 diff --git a/contact/__pycache__/urls.cpython-312.pyc b/contact/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cff18c702c5fcb5299ecb6fbf00218f233cce546 GIT binary patch literal 316 zcmX@j%ge<81kq0?WtIWy#~=<2utFK1J%Ehq3@HpLj5!Rsj8Tk?3@J=0%sEWC%u&pY z3``8Ej43Rs%vl^TjR-o0bq(8UW(Yr>F^aX4U6bP_h^xtXi=`m3B;zF`gu_~vnOa_a zi{Tb~a(-S(VsZ&cL0)2Rs-Gs?E$)=8#Ju!;z0#tb;v$e~MQk7fm(`NiE7NUdiwosDR;@h<;*0fj(53KFAEcg34PQHo5sJr8%i~Mf^ZTAXgNt0f`UH tjEszT8D#D<2!CW_VCC!(>Il8gD}Rw!enI9H7K0DWO#DoZ+(o=Vr2wGUOWFVc literal 0 HcmV?d00001 diff --git a/contact/__pycache__/views.cpython-312.pyc b/contact/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f33357f623a2d9de67682651e0631b9758e47c6 GIT binary patch literal 889 zcmYjP&ubGw6rM>oyJ@p+S`$UuqANm74kqZugK33U3PGrqh+-~fyE|<*?q=i6CX$jA z1;G{z6)%k^&yq`z{tsTF5e!3$2kW6XAvX`=OtM3Ku=Bq8-rM)%+xH3^vIf4#E? z5P+ZjA}rv8!A%B^fC)@!0|yqM6DR~AzX$A~BNZfugQjGM9JwGf9C`%`N?lGVXS_fv zKxK@|Cc-p~k!fLMcqvH34kAR~M&yjO1&oNSxMfc_yj2%F2Iqvy3*olrb+*+XwhIi%$r4>_r>`FCy5ID$v+Bm^f`; zK$aU75i=+z$b<2$qG}U*vm%pKkjzj6TCnZ!lm{t!jY{kH^AC5DPuwcjd=0gtPP9#g z${ImsPa{>sVB&67ZM%MjD#l3HowyG&zE2lD$EMO2(>pamWe0gh*Th^C%onF&i)_?% z+cK#{^cun;PAYzJ%(caQF!p^FjGk2y@i3Q!@U1oN&-sjAsfb>gnuVT|bfr#JzxxJf z@Hv*t>k`C{7oew1zutYh+q&>ox!jM=wQhaLzRz}}$vvqTi5@QQFCN_KsWXSU{oKL5 zz8e3oE__iJT8}%4^>6Bfer&##>&6ltHE|l9d(+&LP9w8#uXW~c9It-5d%V<*EO(^k z0c!--Qeiq{mh|$rnQ^WMC9^Cc1y0F`&& zU#o9?0DNOcUjiODTcqGEKmfri&|nd2T#y42lqeB8OnnR|Z8t2XLU$=k#OfA92V> z*9I!$C8q}wq@N!Jj`*hEb!~EJz+uP#!44jUnlv{P$`c%3!nH4JG+1erjtSM^s^fz501LpQd1(&ST&yJ;esRb{UtXMSI&92v;%L+mg zXG9s7-&AoGnQKy%1Z5|qEE7SN>5yz!F~wn7ezvVtSryb8P<7R!b&@c`b*v+-msD&T zZ0N|~l5?VW_%;9t=HPcGX9GmUb9a~)J#!_~A_OQ@N&$iODAAIfff-T;? z>iwFYv&CFDHg0F;4|h75`BrBBW1^iY*y2iWd?*n;55z-4&zFevFCQ7x{WGo6Sqh_NTJs*VdCl8=>&Zi)MAaumZmN@wX IP{WD-12e2C7XSbN literal 0 HcmV?d00001 diff --git a/contact/migrations/__pycache__/__init__.cpython-312.pyc b/contact/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13223474c92bb6f5649aeff5683a300f579b6584 GIT binary patch literal 132 zcmX@j%ge<81U645WrFC(AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdrL3P=P@tcjpI4HY zT%wAwl9``ZtREkrnU`4-AFo$Xd5gm)H$SB`C)KWq6{v?1h>JmtkIamWj77{q F761X49Yp{D literal 0 HcmV?d00001 diff --git a/contact/models.py b/contact/models.py new file mode 100644 index 0000000..c004bf6 --- /dev/null +++ b/contact/models.py @@ -0,0 +1,11 @@ +from django.db import models + +class ContactMessage(models.Model): + name = models.CharField(max_length=100) + email = models.EmailField() + subject = models.CharField(max_length=200) + message = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.name} - {self.subject}" diff --git a/contact/tests.py b/contact/tests.py new file mode 100644 index 0000000..0f59cdf --- /dev/null +++ b/contact/tests.py @@ -0,0 +1,19 @@ +from django.test import TestCase +from django.urls import reverse +from .models import ContactMessage + +class ContactTest(TestCase): + def test_contact_page_status_code(self): + response = self.client.get(reverse('contact')) + self.assertEqual(response.status_code, 200) + + def test_contact_form_submission(self): + response = self.client.post(reverse('contact'), { + 'name': 'John Doe', + 'email': 'john@example.com', + 'subject': 'Test Subject', + 'message': 'Test Message' + }) + # Check for redirect (success) + self.assertEqual(response.status_code, 302) + self.assertTrue(ContactMessage.objects.filter(email='john@example.com').exists()) diff --git a/contact/urls.py b/contact/urls.py new file mode 100644 index 0000000..447de4e --- /dev/null +++ b/contact/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.contact_view, name='contact'), +] diff --git a/contact/views.py b/contact/views.py new file mode 100644 index 0000000..cb91d83 --- /dev/null +++ b/contact/views.py @@ -0,0 +1,15 @@ +from django.shortcuts import render, redirect +from django.contrib import messages +from .forms import ContactForm + +def contact_view(request): + if request.method == 'POST': + form = ContactForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, 'Your message has been sent successfully!') + return redirect('contact') + else: + form = ContactForm() + + return render(request, 'contact/contact.html', {'form': form}) diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/__init__.cpython-312.pyc b/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d485b4626dcf29ec600e33f7fdab8aadef716f6 GIT binary patch literal 118 zcmX@j%ge<81TP*>$^_AmK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^OH@Cxpg=!4zbI8d rK0Y%qvm`!Vub}c4hfQvNN@-52T@fo#6(bNAgBTx~85tRin1L(+WSAH; literal 0 HcmV?d00001 diff --git a/core/__pycache__/admin.cpython-312.pyc b/core/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf0ac29fb0eb3603f7e1e7aa6042fa2c87ae93f4 GIT binary patch literal 710 zcmYLHy>AmS6o0<+UDA*MrA<@uQMFPBZb=3fgeoK^7Fr>(-C~*8q*r_&*mpy=R;tt$ zv27!nYp5Fv{l;lys}8tgJGaO{O10tYxc>8=QFE%#=I-HHRRfqCb^eD2QNIyS2f zgso{SXx~^bK-!}$juHbl-xYDpWs>InFlAi4vhf?sXOj3Wo$zr(P#LFj%3T94fkTKG zgbqWY%bXC~b|Vye%nSXgAGGwEWvDm}U2B{sIZyHcYcG#-!8KN*m}p<+G%u9y6d9vA zXN2bRj^PiGv4-pqk>8^-W zPFSQeL8p2nlRQhaLR1BtBR$2b93M9~zCmd!!k-5tnq{MLD*33|+Ay2SwnKv}N_vg63*f=Y*g4zzjDMj1ZwI4bet*9NV@nsDtzR}$k^Te9-?ttB literal 0 HcmV?d00001 diff --git a/core/__pycache__/apps.cpython-312.pyc b/core/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..101620799d6418fe9e0e4cb22ddde332d987c0ff GIT binary patch literal 420 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 W6wkz~ocJg?kV<{2^e3PoFz^o|@sq9q literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/__init__.cpython-312.pyc b/core/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e15fd10075a48d15018d6f71ac95f9393d8ee15d GIT binary patch literal 129 zcmX@j%ge<81TP*>$^_AmK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^OF=)epg=!4zbI8d zH#5B`u_QA;uUJ1mJ~J<~BtBlRpz;=nO>TZlX-=wL5i3vwBM=vZ7$2D#85xV1fh+*v CeH^L) literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..b7c1de4 --- /dev/null +++ b/core/models.py @@ -0,0 +1,18 @@ +from django.db import models +from django_summernote.fields import SummernoteTextField + +class Page(models.Model): + STATUS_CHOICES = ( + ('draft', 'Draft'), + ('published', 'Published'), + ) + title = models.CharField(max_length=200) + slug = models.SlugField(unique=True) + content = SummernoteTextField() + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft') + meta_description = models.CharField(max_length=160, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.title diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..039a885 --- /dev/null +++ b/core/tests.py @@ -0,0 +1,28 @@ +from django.test import TestCase +from django.urls import reverse +from .models import Page + +class PageModelTest(TestCase): + def test_string_representation(self): + page = Page(title="About Us") + self.assertEqual(str(page), "About Us") + +class PageViewTest(TestCase): + def setUp(self): + self.page = Page.objects.create( + title="About Us", + slug="about-us", + content="This is the about page.", + status="published" + ) + + def test_home_view(self): + response = self.client.get(reverse('home')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'core/home.html') + + def test_page_detail_view(self): + response = self.client.get(reverse('page_detail', args=[self.page.slug])) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "About Us") + self.assertContains(response, "This is the about page.") diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..d897265 --- /dev/null +++ b/core/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), + path('page//', views.page_detail, name='page_detail'), +] diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..09c08b6 --- /dev/null +++ b/core/views.py @@ -0,0 +1,12 @@ +from django.shortcuts import render, get_object_or_404 +from .models import Page +from news.models import Post + +def home(request): + # Get latest 3 published posts + latest_posts = Post.objects.filter(status='published').order_by('-created_on')[:3] + return render(request, 'core/home.html', {'latest_posts': latest_posts}) + +def page_detail(request, slug): + page = get_object_or_404(Page, slug=slug, status='published') + return render(request, 'core/page_detail.html', {'page': page}) diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..ac065fc7d185c55503d10f9d95afc1c3c9176491 GIT binary patch literal 159744 zcmeI5du&_ReaCr4N)#!QM}Ek3EGwcU%N8fg;#;KCv{78ec9STvV<&Oe;P#5Vl4$c~ zlJcVk2GB{`t=<0Eh6UJ$Edz!D1GWKM_vjy8{^>Sk4{Y5d9k8w)uwg(k3|Y4TYyVgV z>~{{&eem*1)*J^hT^>^l;$cJ?tI!CM&V2$lphPGV)~jf5Tr7-wbC$-wVAJdNH(b zr`A?)hcbhscP-ER@|)WwYFL=xfY#G=5nc(G19c;swt&z zHA-4rU)aPGcuOVvsxRc|)yO|?qnL)*|>{fI~z*;Gg`kC3wShO(U6)UR)qo4U%| zoH}*;c0+He)bc4;6}enGk$o~qDxNk}WbO45fGYKka!svmRa}N9nUTvRm-0^pNQcqZ ziHu!`$)?_DyH_Ne&ZN_4hDn3b*5>dO{Z6lH<;sp-j?BnV3+2!GNr};U6GrE?`r6%l z4QWb~If-*agPz&5V(1tcIYr`It=};5s+_M->WVj&$Rv~J2M0Y-vZ+)p*GNTLC7z_R zd0VtIjZ2yU)%A+Dp%ypm<>EGtok-Ga*+;5~vQ(9_R7taF)YiFLH*4~RR5F!GcuB>q zsbYsoB!4QOPo$E$#Q|bUImCpp)2u@oa`}8Fx1@V22+1o75euaIOJOHcg zD<3;Y7ytqw00JNY0w4eaAOHd&00JNY0v{iNBfjytBKP;mH@Rmd81{{gDSUy>*XIFo z#&-@iXg|qt%(pmYEWoX8QPy54|3TlxxMJ+ilJ7%DeX}#7FypemZIj8MD1f^NxQDS$g75#I3;e5xeTRLO{Wx1=r&%cSPm$k? zycuamRwJh(`@{bk{;TkBhu;Wa3eSXo5PCQCo1xc3p9|$f!IAHbe0Ai_k@m>Nk<7^9 z;17cD2LB-VMsPJ4C53o_00@8p2!H?xfB*=5Yy^^XgB~UQc<@G9zbQ5;(*{`ijrELH zi}C1~{308Z@!1oUWobEB(kptqyY;p=Q#YY6+wpF;;wJ6>cgxmIaAsSarEJN^f_xKp z?>B00AH_N|96d%ECQk%JjrezMvmX2;Hbbd1^8snRkuFUZ64PA?X{R9}t>dRhrbuF9 zmcA^P#Kktl5T154CQ2XfmQLU8tP#1qjWCuXjj50T}l|G)9H~3QTnhb zT_$r3d?7J+l+xzYeo?x$d@yaWqE3#JTFT4E(|S!s_IdGbhCT3Tf#aA|3q6?yy- zl8z_++(Ijsi*h@ym}$D{M44R}64uH^jcKYpKTh|Ukgg$Vr%g&4X~@y7C0tHgXp@uX z$(R`3I700|#BH+)al_>4JlzwbEG!KQt86PO(=r(s<9p5dEg;l82c>xqacN>t2D$}= zXOMO}GDutP8RB&RhccfXE$+<0^A63GDo*9&ZB(-bS}q@N@gmjHY&QEiJPWxU@9SiabvD7*KB<;5J&ZT$CGX#T=s>2m&XP!dPR~ZJJ6E zF>zzU9Ni-jn9oW>?Q+sgOOoylAW3PYT~eB8N>0;F0fE^?ZlGO^n`g@K>;H41zw@x~ z)7Ae!As@gmu{T(iy~NJ444Y!3Oo@Cy^4-W^M!pvL<;WY6TI92lvyoI}G7=2GAO3du zkHfzo{?+hLhdbeB_;caa@F&Bw;Xvp;QivA_fB*=900@8p2!H?xfB*=5^aLL9DQU&c z*)=!rQ4;C6P)xtllu%*0l42r*-FY~lIOh6Vb5XN12SFBMvzgCRlH;b0it?+ctzdb|m~gTBDL;&2^! zMWobeEdK{aeSxk|(}>~M{}s0Bp+CGp00ck)1V8`;KmY_l00ck)1V8`;J|uz8m=gAM z9`^*I;l)5;Iow#gR$5P~txYYJ%^l0pj}#^n$F5&%<<6eJ@WRD(`Np$PRdeZU#aC|T zj(sMPy?&)yTX}J5@s%s((&B3QOzGNcVkx!WuD$YNGn@MSpkXr|~fdB}A00@8p2!H?xfB*=900@A<{ULz$|NY?zEg%2_ zAOHd&00JNY0w4eaAOHd&@E{Yw`u{<09oPl}AOHd&00JNY0w4eaAOHd&00Q@i0M`Ha zhal=DAw))(q*y`nHo{2qw z@%*#P7q7&gUbzxmzI^Gq^Q)xj*_G8xCu7s~wX1rut@39RH*3KR;^XusOcqBGIr+N%9*FhxE}dWJ|0WN zNeOALZRl!MZ?&`y-LOKM&eQ8uomyLM-)`u_OtX5tY8O|YTDiEgdS>MXlZQQnL=umE zn#)}j+|I9_zjS{2nP;xZ$dyl1DO)YQ*{j`vXD>n9YHzAsbk}-imD}YiRjs78b$(=$ ziFZB|^+)T=O6PjHR?=^o`b8mXLvL2gtrjtE*^bFfHMzdFmR(O3x6g1y_LcLipI&*H zn#!iG+oS7Lqj*p*UeExMVw?M8!B+Z*;!4NmX#M>h&eN3&|sO|`1( zY>IrQm@O7ky0E`bc)v?C^a(i{*U)G)zAuX#{`paV^i?wc*`18%)zCvy!_| zEM%qicbTQ)BgZ|@-_MWB&dssmXeY3&+*!1GjDR8ndzY7}933^8(Q<3KbjmS)ow*O+ zWh6W0&56RYK+Jje(rg_~WOwRt4DlXKG|mqk^hYnyOqF$1Tr@|^HbX6CvxRIXK`zj{2_m|NT0b^9F6bnLuNnJ3Rzt%CD*K8;1qpiyZ+!5 z?9=J&7|sm#jvVid9`Hw>qkfxom@Zn$`h?AC$&_Bo+YIgs9Nf;#ZSp&f>x%W7bd}L0 z;w>n5W-Ob8oNcnOjVP-T`It@=OX*a0En&?deOX7P%`V>q`edzvyF)RyO7$t=RqIAl zm+b4K@jEYv{L$rQ<@Lv`5h@zY*=pPOl(5T4byXqzc>lQA>D#=}w7b#7MP(Ux`QYi} z&~H=U2d2e;cV)z%9~g?x&ns61*w*z*xvi@${rZ+(E9#C@Zx2F8;#^Iu>L)2@ymLkI zM;8~BJDSibS4-9MMpNUf&7O&8#MS&yROj*^~%n=`go)$x5^-KO}}kEdF&)mFLtu+FbXtEEgKT_6XknRcv~zZvh6 zrwiVc;I%UqPsfwlAzt=2#LZl4(mb4b?XiqMIytF)aYb-86U>9DUb<%dJ$cUB<#xsH z9dm3xXjQgo*MYG$A!~1;uB(_DXRE<`a!IY%T%2f>F1HHww_CBP@0hXG*4kSwtN5g? zc+y+0imeQWaYU_Yr+gfq6N}DZ=G2@mt-P^`Hic*hrbw=;x!glkCcG0bu?ft=tZL3kKYbAGg zX)?J}UoX~a%4<-IsY&xNc4svq`ugkF1T8bcJQ(TaV8(x3eSO=$e3!mj)!Uj{(p$x5 zxxxM7woaYv?bXJh(Aw^!$rx>}>G3-!7RA-g*UA!Tje1*KFyir7Jtbls)>xFKzAeSJFSG=ft2#;qIl@#9PqUyZKP z_Y&8ch6-ITH>vJB7#2^T9EzTu+U}EAG)?y!WVeCE@nmP;ygxcMrF5!R3yTy>p1HKK z5V@yD-k}Du?gwnVb8MP+9L@LaII=GMi9u}Jxu>SqGfO!=)4eKf$wj2u_8neU>b6>J zmvJp=UQ;;{ni~e=ofoG3(cAON9VWhJ31C*1z5-WkCj9GzG>$e<&Bpxvis|c$)gi2+9hDq(=^B>sJ@pSK=AFN+$gc_ZOtRFw zUwk&}Y)v3-^CD>F0`r@ZB4JJRl-|QmvU>RbSb?i z8r~&?&$aPg1b5clvoLa!F>T`85QJH@ZRz}@{%AU_+$o4AcPY>%YNEI4T_o{c;F=hKlb7rlm=k+zGY2$Y>&EC{}jaQU*uO|As#U?;rpIAOHd& z00JNY0w4eaAOHd&uy+Yy{l9nH7BvF_5C8!X009sH0T2KI5C8!X00Dyl*8c_xyn_G; zfB*=900@8p2!H?xfB*=9z}_W*_5a>&Tht5$KmY_l00ck)1V8`;KmY_l00ayISpOR& z@D2hX00JNY0w4eaAOHd&00JNY0(+ML|NZ|UOL^FP>>t>lvp-~Su`jbS`%(5RMdAel zAOHd&00JNY0w4eaAOHd&00Iv!fk%Qq&v=~wkUl-xMZU@B^MnFE&)67$SQ&r#T4%|1UOJ2NGx4aF{R4D5#ZRrrbL>-mp6G;l4gozgjEsy$%s>_Z49^#G literal 0 HcmV?d00001 diff --git a/news/__pycache__/admin.cpython-312.pyc b/news/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..060719e985a13c0cd1f06fd6d357511fef28db73 GIT binary patch literal 744 zcmYLHy^9k;6o0cjyUFDeKkhDvd|O3Zku7v%;ol&{cvU$Nf+V3 zRxG^MR`RFVL~O!9;6R(}lvr6gvzvGi_PyVGZ|CLgbkbZCTw3N$4Z9Two&xnwf%?pyx>ami z83=3RT2Q~RUVyY0(Iid{*m+eX2@`3i*kMK)e`4bomd_;d%S@`_il8*k%31adI0FVD zq7XV1g)VhMOx@6-4 zq!=Yx8aTpED_TYwYwojaA3Lz|wR9f42rvo)1#? zRu0OQ_Vcl*+a|5@%qRDaI)(FYZ-(3R?Sskl`PTi(v-!@w$>IFwozIVEH~QajM|9SQ z>_}JWNtBMVzFn4xUkiJex>E(olM!agir7i?svW1{M1*K(Hyx@GpD z+R|fj%eeNgJ++M)L1$58R)k*vP-n+dI_&aL$!9^%gVd+&O0$&P%7$1bV%X$KZkbJCpxs*I<1>Usqo1!#`OYnJ;9pK`t1(C=pr*nJ2p?E&JQK%Zz3a~I zwsnLmA96@~X~m@+Il77^dh8$2LnJO(Sc(}bC%EJm6cAERyxFzg6apjp&3ilV`~7D8 zqo&0WtV_RrTYexS^eY#7OYSnqp8@k2VT1(}RfK{da6V*)Dq=zOdC`3 z+Co?+!iL&&^m5;Y7zCb{Tv2- z%{NZTLBkT%n&@i|{in|)KGRI?h5{djv;K?+V8(;}Z^L+K3l+}Q&Sr+0bjx@XvVX;-yF6mWp z>2MFWr2|rkOPETDX}ARImQf*KpctNO>y~{Nc(upt2F8cHO;q7gM5@8YBE@yPTqF*Q zV6tX-rn{;7QOXsgM4ai2$f91=v<uW*f+mQEJ6)46Y||r4ue;) z*~J(-=D<0L%@Uxq7Rrb(qP$1ho5VFXdM%CrUO z^>VpF0<)M`#ogvp{-gjlY|AB<%VICb!orSgxSqpe*EvE0OW8*YQ;J|-vT4n4Ce45k zdWjRi!MqzZ(jz>8^Bl+)Itq!y;d<^!L0WoyX{sbdYXt-e!V?8Ha6FYm`kTk+BF zXWH?%>I)rheEai#ZM>z8f9E#t{BZxt{k>1x+L!gEPHO7W@P2Blm6~d}?bJ;D#xIGB zKPM*ZS5KTgooT18)Ngbk5_n0sv~*+O=~sJSw6zuRp!)ICEj8UpJi~iKZ8ZluBqz4- z>?bE$$%)2$&!X+*NA+ueNrF82I|6bX7UYqjKweueqp&F6FszcD#jC-jM!GWZCM~aK zdH1scG=ssM4-|B#|ITOE_#^KGV~}t1e}tfQueOsZv%#(SfW8mGIqDpaog+~Ygab5o afZjepSN@O&g^}&c4`+8~|3tw0+5Qc%L#g%v literal 0 HcmV?d00001 diff --git a/news/__pycache__/tests.cpython-312.pyc b/news/__pycache__/tests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70c4e219661b559f1b3220b4adead1bcedec56dd GIT binary patch literal 2268 zcmbVNO>7%Q6rT0|cx@*^jUDpWrmljTEKMAt0-+5MstTx}HAPC1>dVUUP90kt+sv#R zB??jxIphEm;o=~W;J_hKdf>#Fo?3}ZqBO)BkrU#OTNT445)$w2+Uvx)5@KY3^LF0b zH#6V&X7-oP&M<-Y_a9&76oruA@uAVwx>5T8j0&-cCD~+NN=Xvh0Xr}+r)1ZbZDn3f zsbDLXYHRbsR8S%T@;-!-O11)kiX{W8mIA0Ps)i;yG0Ux@MQxli z(@247g7C-H)qVn~5C_m}`9Q;J_W1@U%^_gL3Otmz>e0)@tnZZ&M{k6+%@t{Qn?3Tk zO!8WDq+wZ_6?~{~)#ry<{Fp^zgpxvcU}I$SqCIK4!@UU z+=(L@OW}KHkC(tc?wy&-@i_dPIU0Az<2arWp;CF)&ha^F8LCj7oMTg=aJ!rpTDt7a z6`2TTiUo%j9HDV14GS=W4A&@v#o0W~I9xf9+UMRD=Ik_mSZ^YIS>9+2N+eU+PQH}_*>JF&4J z^G~jB$HsPJpR6jMhkVz_PHd#wJybn&t{UyFTv@wPjg42ML(d|lXS7DNp75&jMW_bp z$f-iXZMlV#1fxPgrjuoa;m{mkeDK48YHeHfcPyz{>I|rs1KweC6=L%Vq(GsH^L`XZiVqt0YG)2#kJcCIw{R2|fJNEUy z9lh_{==#iecfY>-&Ctf#J^gid9^CC|*Z_}91N5ye@6*uqIR4pz&DUvY^q)yU7s}Yu zmZiLZhMF!b&?FDuYjcj4CCfjNF2PC?Y8fiWglcvUpFl2;a1H9v&SCw^S?VxUj9mZ_ z5wD!h3|tzb%M;}~lzKW>;F|$<7Grc^y;Ll4%JJ~W)Y?>KdTsiL$&IPq;mO_T8>_mn(R$y0|M*V-_y&KfUv@_acl5#a8{gY| z`sIHshBt*EA+Mo@myUOs^f$d-z0Iz|7~UEjozG-Rk`74xfSf-dmkvnZpUR8UHR;P6 Ke-Z561pffyN$%GG literal 0 HcmV?d00001 diff --git a/news/__pycache__/urls.cpython-312.pyc b/news/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87d03f2b129dcaf8a4df784a98f6a647e03c0077 GIT binary patch literal 394 zcmX@j%ge<81l&(2Wo`q~k3k$5V1qI~7XTU48B!Qh7;_kM8KW2(8B&;1m~)tNnWLB) z8JHMS8Bl(Jz%n&|U9Xn7R*hYjJ5G#cPp)Q>%imj4Ulj|i&LX+_p zOF?2u#!E&BhqWv-wY>Ni!!6E&{Nj@MoXp}9kdnN_+|()_o8p|(bSn^Hr+X;Dsb5y(kJApaGyf(UjH!3iX8aRa4+_LZa-HSWuv!2eeloq*PO4oIKTrk8ZN+9l;sY}yBja5LnY#?aAK4gK xIXi?pLi^)8;}=+8VbQ_4zF^D{Ma7x4nM0sz?gTA}~| literal 0 HcmV?d00001 diff --git a/news/__pycache__/views.cpython-312.pyc b/news/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1beb1a54ec216c6ed8ed3fa9bb6538e76b3f4976 GIT binary patch literal 898 zcmZvZ&ubGw6vt=wS5kMYiM6OGBnLqjG%2+xLJ+j+$r1#MhlqqEGv)`IY@C@@5>v^+ zizmfPFCKbS>LJJc4_;Ct6qbqy@z9%?n$rMV;tijjmzGj^tHa@eM}X zgfc%_qoU+inzRZ(R$5zGI|4OiV_$pVa#59=Xc^$B@J(NV*+C*YN6o09`3XWz{lsSG z68u@&o4weK{U@$Q(QPaiqIe&gAbKA+@SaqM^$^)c!C&Zj6{o@Llt8rx7iHn`o_W2> zXjxEFa@)P7Hhsaf9q6M3i6T=MEyvg8p4&Q;?f9{Gwe=}nM%9yS=M%_71ST+_ zj+kflaX@)SYFiQWIRHnMXno}$j(UN`;VtM>Z#@+;Bp2nrzTknd~f} z7a~&GX?d_D%{ee!YNyj_b_V{w8BhtIVasqL<@gmaz{R}$Ve6wdeEikCJIK!%tPUTI zwuXfe0ZT$+!H)R}(k!=YZid%gCaRv`fp$=eZ?s%O9Uhh>(FZ!8P;fb{BA$aB&!KnN fMTkpm{5+UnD#rLby7>)V_^l~8kEd~IgNuIw&jQ7a literal 0 HcmV?d00001 diff --git a/news/admin.py b/news/admin.py new file mode 100644 index 0000000..0455d82 --- /dev/null +++ b/news/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from django_summernote.admin import SummernoteModelAdmin +from .models import Post + +@admin.register(Post) +class PostAdmin(SummernoteModelAdmin): + summernote_fields = ('content',) + list_display = ('title', 'slug', 'status', 'created_on') + list_filter = ('status', 'created_on') + search_fields = ('title', 'content') + prepopulated_fields = {'slug': ('title',)} diff --git a/news/apps.py b/news/apps.py new file mode 100644 index 0000000..e50c454 --- /dev/null +++ b/news/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class NewsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "news" diff --git a/news/migrations/0001_initial.py b/news/migrations/0001_initial.py new file mode 100644 index 0000000..a656a72 --- /dev/null +++ b/news/migrations/0001_initial.py @@ -0,0 +1,61 @@ +# Generated by Django 4.2.28 on 2026-02-16 09:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django_summernote.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Post", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ("updated_on", models.DateTimeField(auto_now=True)), + ("content", django_summernote.fields.SummernoteTextField()), + ("created_on", models.DateTimeField(auto_now_add=True)), + ( + "status", + models.CharField( + choices=[("draft", "Draft"), ("published", "Published")], + default="draft", + max_length=10, + ), + ), + ( + "image", + models.ImageField(blank=True, null=True, upload_to="blog_images/"), + ), + ("category", models.CharField(default="General", max_length=100)), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="blog_posts", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_on"], + }, + ), + ] diff --git a/news/migrations/__init__.py b/news/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/news/migrations/__pycache__/0001_initial.cpython-312.pyc b/news/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1929fffbc8954f6bf136caa210b0e8364ecb157 GIT binary patch literal 2264 zcmaJ?O-vg{6rNq%Ywu!+?JNWv6BATOX{?*+r0P_6$&rj-`AOLTu(tZLh;cbBkzW@jzqk$4rU>GX18e8%ed?kOu@9I7+ zP~eE&rv(ck7R&+Ye*$0t`HHbN;o-RL^W*}*>C16@RLL#;I(f!AuLsh=*k>CUumpBH;RJ%HGVHcM? z7T4W@>p>@qLq}1?;iIbF>fJLl{NJd!r}YKTs15t>2e9-fUVjZ+IvA_B{xPW$qywB>59F9{gzfyh>ff0 z>b(lWLZu8Tv1I75ER03;bg%vrWk6~o-b!8xQOQ`Yt%nqF>QMH(cb8)dim+{(pq zNODszNm!61(uyh?B63)g?p0)st|D4c)m57um`Pgz7Oe+zxh$^|AVg)PL!_^5nzZNP z6Vta>=B3-qSHF=K7H6*B#Nh*kN>bL9cdKE6HawFyU>v@pD$}G+W@%uU&u_?POI#-B z?TOtpWRoju33+N17G+&AB&$*?AyYSOWKL45lgkH&74*>dz``|pEG>D~Fp;Y0H%J9? z9tmdQDh|2nB)u31^V7@u>6xooFv{di+#LM@-V%23Z)TEgdQwLZtjWXXJ$e58`3q7j zI}_zill*0X`{9XT{YaYc6Y#nx_IR$z0eLL<*Y_0g}K(XU>Hrkg%iC;Z=bR&9-zm?VKrCKMjBTB* zC&rw_*v{H+;%lTC5E% zHHJnS!`c0yp9U6m1M4NgECpsAG5c(0$A13Zu6U;z@R8XNh$U--<6C#?gX7NN_>TG_ zzdN{8i+t_UAFOJmM}N=#maC^PI_Zn`^kpY~xt`8D>3k!5p)oLAADD6mraGK{nh|@N zHyIEe*!p&Bkz}^FXMd3y/', views.post_detail, name='post_detail'), +] diff --git a/news/views.py b/news/views.py new file mode 100644 index 0000000..49bea8e --- /dev/null +++ b/news/views.py @@ -0,0 +1,10 @@ +from django.shortcuts import render, get_object_or_404 +from .models import Post + +def post_list(request): + posts = Post.objects.filter(status='published').order_by('-created_on') + return render(request, 'news/post_list.html', {'posts': posts}) + +def post_detail(request, slug): + post = get_object_or_404(Post, slug=slug, status='published') + return render(request, 'news/post_detail.html', {'post': post}) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..418abfc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +django>=4.2,<5.0 +django-summernote>=0.8.20.0 +Pillow>=10.0.0 +django-environ>=0.10.0 +bleach<5.0.0 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..2e74a5b --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python manage.py runserver 0.0.0.0:8000 diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..cc5159d --- /dev/null +++ b/templates/base.html @@ -0,0 +1,60 @@ + + + + + + {% block title %}Corporate CMS{% endblock %} + + + + {% block extra_head %}{% endblock %} + + + + + +
+ {% if messages %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + + {% block content %} + {% endblock %} +
+ +
+
+ © {% now "Y" %} Company Name. All Rights Reserved. +
+
+ + + + diff --git a/templates/contact/contact.html b/templates/contact/contact.html new file mode 100644 index 0000000..6578074 --- /dev/null +++ b/templates/contact/contact.html @@ -0,0 +1,51 @@ +{% extends 'base.html' %} + +{% block title %}Contact Us | Corporate CMS{% endblock %} + +{% block content %} +
+
+

Contact Us

+ +
+
+ {% csrf_token %} + +
+ + {{ form.name }} + {% if form.name.errors %} +
{{ form.name.errors }}
+ {% endif %} +
+ +
+ + {{ form.email }} + {% if form.email.errors %} +
{{ form.email.errors }}
+ {% endif %} +
+ +
+ + {{ form.subject }} + {% if form.subject.errors %} +
{{ form.subject.errors }}
+ {% endif %} +
+ +
+ + {{ form.message }} + {% if form.message.errors %} +
{{ form.message.errors }}
+ {% endif %} +
+ + +
+
+
+
+{% endblock %} diff --git a/templates/core/home.html b/templates/core/home.html new file mode 100644 index 0000000..143a5fd --- /dev/null +++ b/templates/core/home.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Welcome to Our Corporate CMS

+

We provide excellent services and solutions for your business needs.

+ Contact Us +
+
+ +
+
+

Latest News

+
+ {% for post in latest_posts %} +
+
+ {% if post.image %} + {{ post.title }} + {% endif %} +
+
{{ post.title }}
+

{{ post.content|striptags|truncatewords:20 }}

+ Read more +
+ +
+
+ {% empty %} +
+

No news available yet.

+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/templates/core/page_detail.html b/templates/core/page_detail.html new file mode 100644 index 0000000..1298211 --- /dev/null +++ b/templates/core/page_detail.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block title %}{{ page.title }} | Corporate CMS{% endblock %} + +{% block content %} +
+
+
+
+

{{ page.title }}

+
Last updated: {{ page.updated_at|date:"M d, Y" }}
+
+
+ {{ page.content|safe }} +
+
+
+
+{% endblock %} diff --git a/templates/news/post_detail.html b/templates/news/post_detail.html new file mode 100644 index 0000000..c7eee07 --- /dev/null +++ b/templates/news/post_detail.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block title %}{{ post.title }} | Corporate CMS{% endblock %} + +{% block content %} +
+
+
+
+

{{ post.title }}

+
Posted on {{ post.created_on|date:"F d, Y" }} by {{ post.author.username }}
+ {% if post.category %} + {{ post.category }} + {% endif %} +
+ {% if post.image %} +
{{ post.title }}
+ {% endif %} +
+ {{ post.content|safe }} +
+
+ + +
+
+{% endblock %} diff --git a/templates/news/post_list.html b/templates/news/post_list.html new file mode 100644 index 0000000..1b370a8 --- /dev/null +++ b/templates/news/post_list.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} + +{% block title %}News | Corporate CMS{% endblock %} + +{% block content %} +
+
+

Company News

+
+ {% for post in posts %} +
+
+ {% if post.image %} + {{ post.title }} + {% endif %} +
+
{{ post.title }}
+
{{ post.created_on|date:"F d, Y" }} by {{ post.author.username }}
+

{{ post.content|striptags|truncatewords:30 }}

+ Read More +
+
+
+ {% empty %} +
+

No news available.

+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/test_hello_0718.py b/test_hello_0718.py deleted file mode 100644 index bf1f44a..0000000 --- a/test_hello_0718.py +++ /dev/null @@ -1,16 +0,0 @@ -import unittest -from hello_0718 import hello_0718 - -class TestHello0718(unittest.TestCase): - def test_hello_0718(self): - result = hello_0718() - self.assertEqual(result, "hello 0718") - - def test_hello_0718_output(self): - # Test that the function returns the expected string - expected = "hello 0718" - actual = hello_0718() - self.assertEqual(actual, expected) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file