From 3a44f34cf92369ae3ba2f978364d2a312cf64594 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 16 Apr 2026 11:25:44 +0000 Subject: [PATCH] Auto commit: 2026-04-16T11:25:44.037Z --- config/__pycache__/urls.cpython-311.pyc | Bin 1557 -> 1810 bytes config/urls.py | 2 + core/__pycache__/admin.cpython-311.pyc | Bin 1283 -> 1310 bytes core/__pycache__/forms.cpython-311.pyc | Bin 3904 -> 7638 bytes core/__pycache__/models.cpython-311.pyc | Bin 3698 -> 3945 bytes core/__pycache__/urls.cpython-311.pyc | Bin 535 -> 1175 bytes core/__pycache__/views.cpython-311.pyc | Bin 9694 -> 12602 bytes core/admin.py | 5 +- core/forms.py | 38 ++++++++ core/migrations/0003_momentumentry_user.py | 21 +++++ .../0003_momentumentry_user.cpython-311.pyc | Bin 0 -> 1200 bytes core/models.py | 8 ++ core/templates/base.html | 1 + core/templates/core/entry_detail.html | 14 ++- core/templates/core/entry_list.html | 31 +++++-- core/templates/core/index.html | 79 +++++++++++++---- core/templates/core/signup.html | 58 ++++++++++++ core/templates/registration/login.html | 58 ++++++++++++ core/urls.py | 15 +++- core/views.py | 83 ++++++++++++++---- static/css/custom.css | 61 +++++++++++-- static/images/favicon.svg | 17 ++++ staticfiles/css/custom.css | 61 +++++++++++-- staticfiles/images/favicon.svg | 17 ++++ 24 files changed, 518 insertions(+), 51 deletions(-) create mode 100644 core/migrations/0003_momentumentry_user.py create mode 100644 core/migrations/__pycache__/0003_momentumentry_user.cpython-311.pyc create mode 100644 core/templates/core/signup.html create mode 100644 core/templates/registration/login.html create mode 100644 static/images/favicon.svg create mode 100644 staticfiles/images/favicon.svg diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 8cf22af743397374cabf5fa358c4f52631471db9..ac2aba6ef25c41adefb296ab79b44d6d704fb7da 100644 GIT binary patch delta 530 zcmbQrGl@@qIWI340}#X>eUN#cg@NHQhyw#0P{!w&iR!jCDU2y>Ib6BiQQV9SObn^4 zDeOR!JBtUVI+Z<(7sgKEr~)!mIM;A3V`5-f%`|a=m>MCl z7^Viho)p1g1`yU1+HA(SoykR$@fJ@|YD#8NYH~?fW@>pAcUoduW^#U>9+0RK%FInn zPc7Dmi4~WnyJ<4rVlFMpxy4zKT9liZmzq~H`7N^*qsU|pmNXN|l&r+O^nATCpi#wo z>8W|CMVZNZNr}a&MW8^w#hzFk50bmZ6&&Ii;^`b88ssy%mqmN>5f(pgd7uhLATHKt zoGi`Tex?TQ4?L5%vc|B;0xbXl5z>6v delta 289 zcmbQlH-3pae?_$HX_X^-NJbsq9(2F#Sa)sXRbQ&J<3dC?8BTg{z92fng2zGA0Iw)j$jZ zRX|-p-C*@V)%-AVAX@;&#;zxYCzt_*HF-C8Fm7j>+{|LdC^&f^OWNc}RxNI>;1I_U zPv`j1AfL%itp40mK*Jb;xcDQ(;M1& diff --git a/config/urls.py b/config/urls.py index bcfc074..1b0f05a 100644 --- a/config/urls.py +++ b/config/urls.py @@ -18,8 +18,10 @@ from django.contrib import admin from django.urls import include, path from django.conf import settings from django.conf.urls.static import static +from django.views.generic.base import RedirectView urlpatterns = [ + path("favicon.ico", RedirectView.as_view(url=settings.STATIC_URL + "images/favicon.svg", permanent=False)), path("admin/", admin.site.urls), path("", include("core.urls")), ] diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 99f725f5c15e2fade2026e51d5a25ffe9198402c..5187f36189cd67ba03dacfe8c8c964f9296b191a 100644 GIT binary patch delta 180 zcmZqXn#aYvoR^o20SGFNJ;>a@k#`#tANwuVlFX8v)LSg2#i>P;Pcyj+F&9Y!l@v*X z2q_?;$ue1%Sz728A4o-fJc!Io%uOwlnry|K#m^za*5LC&h(Xx2!TW}Y=j6@Im5dsb rby=o3@h~cVV8A41h+dJ@{0Nfx0wO*#Gcd_tVU+v8grc-a1ZXn=1TZh% delta 175 zcmbQo)y&1aoR^o20SMR*KggWAk#`#t2iq;ylFX8v)X5i_T(y{rq=1q|l0ZU}xkv&? z-Qp-o%uY=#PprJfRg{{RlbT$TnV(lAIXRR$i=SPDt-eq diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 6c9b14adbf347d8d5531350a61f703af48589919..3874769fc9af021fd203c4028f25540f3b635db3 100644 GIT binary patch literal 7638 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 Vqav)^PEi|}c#PRkx|H#y`#-&&*ysQN delta 651 zcmZ9JKWG#|6vpT6|J%#1cV~)Kh{PB}mXJ$84g!%75DlUy5kcfYmg8oXOJ;A)>|PNY z8zFTvg`I_h7B<4wieO_URz{W-c48x`Q~BN=0dd&<@xJ#S-^`o&zWV8c|JL(dhO71Y zXaBXk?+1Jn)eFWM2f-5P08p$6EzvtV#yU5Ik(eD5n8r33H=i?Z#js_O3#~UgWAE@E zua3>_dn|N*lMJCSAEh$MMm~Jez*L9&oE2a}o>RK9H+PnLY+0p@NAL+}2sJ{T&`|G< zU)6sq>Xo?wmilVGG-(uPv}0|6)ui6#(MNkq15X`QW^4aDs+v0+Hn1(M6=pVk63fDg zqW++tN0BrzUsOce>k8h@V|iIUbXzc~l)JsKMAj>ar{~*g5)blWa($4?-5WSs^VH!A z*0U4!r#_=@Rga*l<|huJe&F4Pb=CIf8`sE$2Ff(V^t4=3dtM9H)CcduCLJCx;v)6T z5H1mx3AC@IH!8d+>WOZa^#pa=i*W`VabbxFJajXNt X4;FkIf6i&7f~mVcTZQ15Q2P7@XXcJ# diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 5862b353ea8b3b9a2708034c7cb8924732a84853..c82372b525b75c5c3c3a9a8fb1c4037fe33eff71 100644 GIT binary patch delta 1789 zcmb7EO=ufe5Pom)HLmGA^$E?>YwX&lL z?#N90cCe;QUKMgI~m zv|_OQ4!d5*wC*|>lnfaMbj4!Psgq)HGmw>WT9eO24S1Ia=~hpxemM1Z#-uvQjD8nkYvD5#|}YQ-Z+T# z56|~m?qIeRxCX*rmdW3<0^Rqf-dSb4axp!teJ7O65Da0@v*Uvb_^5>wEmH;puP|d9 z-aX&3@+blVMz9+U82&wR2U4`l(!@nqIV059cVMyGSCAg&UY0K|pp6*PAVRBej2iO5 z$Zcg|FEDJ;V{Z)geOGb7CNHAPo(Njir8|iPEX(P6cjwSz$F$%tn z5r^IxbJaz=YS!wFYQ9z{O*c}ts}Zgj0ZGMlmNhT^&7ag?cjuJ4Xjs;#+V&kH@I z!Jg!OfTInz3ei9P?kC=bmAA@6(?u*%e?>&){v?Jc0OoQW< zzT<7M86P@rzwypfB5C+EeInh%=c!MAFZk-i^rHMB{)n#0=Qt%v_vNKH`}|#DeT@t; zX$>ZYE3FfI-6ZuUxy0N#2FDVAmf0AvsyNS}Yqti!wvu`< zeW;|`Qshv|w4;efQ|~4RN@>g2<5u;WKN)!l6j0UL(0SOWgdm)8cYhl|Zv&C30aNy`c zGDHs^JeZUih>7vy(Zs|UPaesh^oS==FM2S(H>KDDiN0pO&V299o40S?o9&iw@z8oO z=#xOa?>=V|-qlc3Y!OyZBq~v2N?D?ltgOof*Rr{KJra_on-X=eNYrDvXO-1lV^|fG z7bs z*TuS8n7$y6+cwO*Hi1POYv#kQmVq$LZS=M*ZG|Hb3!l^`hWkbn0age9tRA5O!2{rE zgZEM_nK4YtYI)Ihp(BQ>R)i)5ab`2l)G~PaPuI(Kp+Ig1#=dy>X5W7k{=pqzGF-D| z3KF&{7hRIH0iz_kB1>R_UW8EBrn?AVvw>eEMAw9cxB?`KFgAn+LH7%7piICjNIexE zmOuk!h8Mk34WZtPnmF)-Mkp+29ITgbDnn)1GGi|1F zbNrdYS|X@DP;jCWyFqc1pI6(<)gRNM+)^hykAtFI`M4QNcCo%Hh_0zAJI|)3PV-$g zL83gS-6QAtigqSYxtYJwy2)AoRXZn4qWrvXtR9Qjk>}@FmF1eRhjj6ez7vzBmO6f9 zOZk!(GcxJiEu$cs#!<$`Z%+&jPB`jJDsQAMR$y%y@+CY(y8&FO0eg7b-&*5F6}AZL zN4U%%`!mD)UFoRv%$he?zQC@a`WnK4#GOJ80kdkaLo+S!|F*(yuNMPP$&%<_G#X9# zoCZ3Z%B8KY8Hl{=e#*?yRNi8CDGUtA(e^Cpbm`sj@2wY@X)}>6&xl10v-C`e2&$v{ wr4D2*x?mbt**G{1VT;?(0d`!35dJPQ=QUANY3VisXEge{lsR}pqK-H7C_XXuU_!G>+(hUZXKo^&Cv1u}2tXs(zqFM5&msCD5PkxeBY$!h3GDsnu!z zU9=^1>re=Lj_-=uXbf#@R^2YuY*xuui7^xQY9g7L)wp$0`eBtv42M7#O|df_!c#l0 zF>XNIoS6EdyYFe^BSCXwnn>P=Q;WegrJ}dtVq!U8z%8W#_X`T^wc>4gmg^~a57#`` zCd#f&cATbG!%EF2G)!|SHTUnuCDTTpftSg1x|v<7^NQ+DlH<4#l>Pn@f`Z>auKHaX z4Z&;wf1|^Llyva+Fn73l%=>Vr4QB#46GD+<(?{xw;lpwpmIGLhF<&Yi8(*LMaHS1b z0=N=`p*RwXxj-yjinABuY&cyG=N7^j#f|`^mp1@P@*Pth84QnivSNzA0aV>F)yZ{_ zpE_C(^2N*i;zfS(+nS$WZ0A>l{OZp+A6DA162M9~rKzK}lSv=Wx8ZyM=R41`*~CGb JeWXAe_6J79KU@F+ delta 383 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 z zwGhS5qv*qRu=orYpNC_mHLOBb2VODKbTm+trs@GtxU2sjs-yVg79=EF+!Velu=yH+zBK8YTb=|P1agyW~c z-Y0>Bn}LHHul?@ZQsAH*7*ztJPXc3`fidaIYs!^xNP#gqa9s&pm&U&(ee+vDmB5=z zm!G!oUJGe!r@DsrtX+chtYWAe*3$i<2P5}J3dd!Cuj23B@NfDDzi2@XY`h`6PAaaG zlKJFL)qtN(R6`0Df?hS$TvPJYZvOQ*)8H;y&CN8{STa`&Vnt+8VN{n#ohpFi+(KnB zKyNDHeTaQfN%0dMz%T2ffbs4o0%S+#Z-$6%!fS-eIbP*;g}ub>C=Utzui16H%Wwq97|9$(QWof!dk* zl6Bl7YL!x`|3=onW(H-vr~Dr0fOR}a89Og&$a5Y_=1vhSscec>3?nq2K#yh^z~LpE ztYk!06mDrbM^!W0WV!A->q&s?puKam=rB&#nNPXKvLRTq072oOE@xG&wTeYYE{ayH z#m})3yUaD}{>SijsvWzAct7VQdmnacZdTj%8mmrK&6xOWsKKL{5MzS>8;tz~2`&;I zC6d33gzR_kV2l6(vIi1FgzS7J41mpR4Lk<}SharekD$|KJg9^RrSR}$tLBN7=#dHtACv7D75hbr zJRf;l-_GC3FPjm49Qn-wDKH36F&qZ~T}3lzHM2l!8egA(lz-AUve`HCp;hiXr}UkJ z%zr;7UAz9TBmXwC?XVy$0RpguF2R!C^?BKKOmQ8P%*V(s+SSe9$HDz65OApj)TJS zZK-YbrJ^_Hz^xC4u|ORE6*j_DmKWj5tiTiHN^Dk?64h1W0HP@nD^{h;IbssBh!G6; zQ~0!?*|#W{U=VA4BG)$2i>OK9-(n2cy#`9JV@yDD3kiZuqDG0h@y9XQj)Vd7nf(Ot zoJ(=T{9{Oh7*fK!K(|c>gP}+{mdIa`@+^_RBDGu6KToMclKv@D9g_YjQu`$RQ>6N& zFM4*CDNVh delta 3687 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 diff --git a/core/admin.py b/core/admin.py index c862f0a..5112d54 100644 --- a/core/admin.py +++ b/core/admin.py @@ -14,12 +14,13 @@ class CategoryAdmin(admin.ModelAdmin): class MomentumEntryAdmin(admin.ModelAdmin): list_display = ( "title", + "user", "entry_date", "category", "focus_score", "energy_score", "deep_work_minutes", ) - list_filter = ("category", "entry_date") - search_fields = ("title", "takeaway", "reflection") + list_filter = ("user", "category", "entry_date") + search_fields = ("title", "takeaway", "reflection", "user__username") date_hierarchy = "entry_date" diff --git a/core/forms.py b/core/forms.py index 7345475..f388dc1 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,4 +1,6 @@ from django import forms +from django.contrib.auth.forms import AuthenticationForm, UserCreationForm +from django.contrib.auth.models import User from django.utils import timezone from .models import MomentumEntry @@ -64,3 +66,39 @@ class MomentumEntryForm(forms.ModelForm): if len(takeaway.split()) < 3: raise forms.ValidationError("Write a short sentence with at least three words.") return takeaway + + +class LoginForm(AuthenticationForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["username"].help_text = "Use the username you chose when creating your account." + self.fields["password"].help_text = "Enter the same password you used during sign up." + for field in self.fields.values(): + field.widget.attrs["class"] = f"{field.widget.attrs.get('class', '')} form-control".strip() + field.widget.attrs.setdefault("autocomplete", "off") + +class SignUpForm(UserCreationForm): + email = forms.EmailField(required=False) + + class Meta: + model = User + fields = ("username", "email", "password1", "password2") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["username"].help_text = "Pick a simple username for your private dashboard." + self.fields["email"].help_text = "Optional, but useful if you later add notifications." + self.fields["password1"].help_text = "Use at least 8 characters so your account is harder to guess." + self.fields["password2"].help_text = "Type the same password again to confirm it." + for name, field in self.fields.items(): + field.widget.attrs["class"] = f"{field.widget.attrs.get('class', '')} form-control".strip() + if name == "username": + field.widget.attrs.setdefault("autofocus", True) + field.widget.attrs.setdefault("autocomplete", "off") + + def save(self, commit=True): + user = super().save(commit=False) + user.email = self.cleaned_data.get("email", "") + if commit: + user.save() + return user diff --git a/core/migrations/0003_momentumentry_user.py b/core/migrations/0003_momentumentry_user.py new file mode 100644 index 0000000..97234b7 --- /dev/null +++ b/core/migrations/0003_momentumentry_user.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.7 on 2026-04-16 11:22 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_seed_demo_data'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='momentumentry', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='momentum_entries', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/core/migrations/__pycache__/0003_momentumentry_user.cpython-311.pyc b/core/migrations/__pycache__/0003_momentumentry_user.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4705d5e42625f293428347bdb5c250de23212a63 GIT binary patch literal 1200 zcmZuw%}*0S6rcU&Ja{DW zz<+=kIQR#6@L)V+vWI3)CZ4=4fs-fS>~^h2y-T?aWL{GZwWPlW1L`$mQ<50xh8XnM~@H|-y$p#sgp?;IuH;# zf}XTm26G#zApc_SdV!;gM|{6+x4Z&RHLKln4ZrQ!9+fQz6Vq!9zKe&Efphj5z&;}J zd1^f7ctnJbn1;gca*7W)*d2P(T6`@N$0{jpInR9pVC$0M6?A>%cHstggFEYdYy|8{Mu^hj~o8 z#Ky#K^jubII$Ex7F6-5e$}@fK$zo-dW`_}QSgMpUUSj*XOe(4^LHg~M{h0Kqj5~(i zaww1MREaZy>p(LL<&A~%Vufl>mkh4RM|8%j@O!273M488^#EilksZBaP#1@pUCsUL;}ZgAO?5)1l>yB hehMxy`4e=>8_79Nj?n*RPYcIpPT9oyCA0CV{s7Z7JQ)B0 literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 2de2282..b84bc57 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.urls import reverse @@ -17,6 +18,13 @@ class Category(models.Model): class MomentumEntry(models.Model): + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="momentum_entries", + null=True, + blank=True, + ) category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="entries") title = models.CharField(max_length=120) entry_date = models.DateField() diff --git a/core/templates/base.html b/core/templates/base.html index fc2bed1..a039170 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -17,6 +17,7 @@ + {% block head %}{% endblock %} diff --git a/core/templates/core/entry_detail.html b/core/templates/core/entry_detail.html index ce26df7..6c664fc 100644 --- a/core/templates/core/entry_detail.html +++ b/core/templates/core/entry_detail.html @@ -6,6 +6,14 @@ {% block content %}
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + {% if created %}

More in {{ entry.category.name }}

diff --git a/core/templates/core/entry_list.html b/core/templates/core/entry_list.html index ae3a103..9e171ff 100644 --- a/core/templates/core/entry_list.html +++ b/core/templates/core/entry_list.html @@ -6,15 +6,32 @@ {% block content %}
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} +
History -

All momentum entries

-

Filter by category and open any check-in for its confirmation/detail view.

+

{% if is_demo_mode %}Demo momentum entries{% else %}Your momentum entries{% endif %}

+

{% if is_demo_mode %}Browse the seeded sample history, then create an account when you want private tracking.{% else %}Filter your private check-ins by category and open any one for its detail view.{% endif %}

-
+
Back to dashboard + {% if request.user.is_authenticated %} New check-in +
+ {% csrf_token %} + +
+ {% else %} + Log in + Create account + {% endif %}
@@ -47,9 +64,13 @@
{% else %}
-

No entries for this filter

-

Try a different category or create a new check-in from the dashboard.

+

{% if is_demo_mode %}No demo entries for this filter{% else %}No private entries for this filter{% endif %}

+

{% if is_demo_mode %}Try a different category or create an account to start tracking your own days.{% else %}Try a different category or create a new check-in from the dashboard.{% endif %}

+ {% if request.user.is_authenticated %} Create an entry + {% else %} + Create your account + {% endif %}
{% endif %}
diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 22e4b66..81b349f 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -14,9 +14,21 @@ @@ -29,12 +41,23 @@
- Interesting Python project · daily tracker MVP + {% if request.user.is_authenticated %}Private momentum tracker{% else %}Interesting Python project · try the demo{% endif %}

Track your momentum like a product, not a spreadsheet.

-

Log a single daily check-in, watch your focus and energy trend over time, and turn a simple Django app into a polished personal dashboard you can actually share.

+

+ {% if request.user.is_authenticated %} + Welcome back, {{ request.user.username }}. Your entries are now private to your account, so this dashboard reflects your own focus, energy, and small wins. + {% else %} + Explore the tracker with demo data first, then create a free account when you are ready to save private daily check-ins. + {% endif %} +

+ {% if request.user.is_authenticated %} Log today’s momentum - Browse history + Browse your history + {% else %} + Create free account + Browse demo history + {% endif %}
@@ -49,7 +72,7 @@
-
30-day snapshot
+
{% if is_demo_mode %}Demo 30-day snapshot{% else %}Your 30-day snapshot{% endif %}
@@ -105,10 +128,17 @@
Workflow widget -

Capture one meaningful check-in.

-

Each entry becomes a usable artifact: you log the day, land on a confirmation detail view, and keep building a real momentum history.

+

{% if request.user.is_authenticated %}Capture one meaningful check-in.{% else %}Create your account to start saving check-ins.{% endif %}

+

+ {% if request.user.is_authenticated %} + Each entry becomes a private artifact: you log the day, land on a confirmation detail view, and build your own momentum history. + {% else %} + You can already browse the demo dashboard. Once you sign up, new entries will belong only to your account. + {% endif %} +

+ {% if request.user.is_authenticated %}
{% csrf_token %}
@@ -132,6 +162,19 @@ Default categories are ready, and you can manage them later from Django Admin.
+ {% else %} +
+
+ Private entries + Login + signup ready +
+

This prevents your personal notes from mixing with the demo sample data. It is a common first step when an app starts storing user-specific information.

+ +
+ {% endif %}
@@ -142,7 +185,7 @@

Create

-

Submit a structured daily entry with validation, categories, and tangible metrics.

+

{% if request.user.is_authenticated %}Submit a structured daily entry with validation, categories, and personal ownership.{% else %}Create an account in a few seconds so the app can keep your data separate from demo content.{% endif %}

Confirm

@@ -163,9 +206,9 @@
7-day insight -

Focus and energy trend

+

{% if is_demo_mode %}Demo focus and energy trend{% else %}Your focus and energy trend{% endif %}

- Open full history + {% if is_demo_mode %}Open demo history{% else %}Open your history{% endif %}
@@ -190,9 +233,9 @@
Recent check-ins -

Momentum history preview

+

{% if is_demo_mode %}Demo momentum history preview{% else %}Your momentum history preview{% endif %}

- See every entry + {% if is_demo_mode %}See every demo entry{% else %}See every entry{% endif %}
{% if recent_entries %}
@@ -216,8 +259,14 @@
{% else %}
-

No entries yet

-

Start with one check-in above and this dashboard will instantly become your first useful Python product demo.

+

{% if request.user.is_authenticated %}No private entries yet{% else %}No demo entries yet{% endif %}

+

+ {% if request.user.is_authenticated %} + Start with one check-in above and this dashboard will instantly become your own useful Python product demo. + {% else %} + Create an account to start filling this dashboard with your own progress history. + {% endif %} +

{% endif %}
diff --git a/core/templates/core/signup.html b/core/templates/core/signup.html new file mode 100644 index 0000000..121f2eb --- /dev/null +++ b/core/templates/core/signup.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% block title %}{{ page_title }}{% endblock %} +{% block meta_description %}{{ meta_description }}{% endblock %} + +{% block content %} +
+
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + +
+
+
+ Create account +

Start your private tracker

+

This is the simple user system behind the app: once you sign up, each new entry is stored under your account instead of the public demo sample.

+ +
+ {% csrf_token %} + +
+ {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %}{{ error }}{% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+ {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %}{{ error }}{% endfor %} +
+ {% endif %} +
+ + I already have an account +
+
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/registration/login.html b/core/templates/registration/login.html new file mode 100644 index 0000000..a3fab8a --- /dev/null +++ b/core/templates/registration/login.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% block title %}{{ page_title|default:"Log in | Momentum Atlas" }}{% endblock %} +{% block meta_description %}Log in to Momentum Atlas and continue your private daily tracking workflow.{% endblock %} + +{% block content %} +
+
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + +
+
+
+ Account access +

Log in to your dashboard

+

Once logged in, new check-ins belong to your account and only you will see them.

+ +
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %}{{ error }}{% endfor %} +
+ {% endif %} +
+ {% endfor %} + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %}{{ error }}{% endfor %} +
+ {% endif %} + {% if next %}{% endif %} + +
+ + +
+
+
+
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 2bbe4bc..1f47f61 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,9 +1,22 @@ +from django.contrib.auth import views as auth_views from django.urls import path -from .views import entry_detail, entry_list, home +from .forms import LoginForm +from .views import entry_detail, entry_list, home, signup urlpatterns = [ path("", home, name="home"), path("entries/", entry_list, name="entry_list"), path("entries//", entry_detail, name="entry_detail"), + path( + "login/", + auth_views.LoginView.as_view( + template_name="registration/login.html", + redirect_authenticated_user=True, + authentication_form=LoginForm, + ), + name="login", + ), + path("logout/", auth_views.LogoutView.as_view(next_page="home"), name="logout"), + path("signup/", signup, name="signup"), ] diff --git a/core/views.py b/core/views.py index cb3cbf6..992803c 100644 --- a/core/views.py +++ b/core/views.py @@ -4,12 +4,14 @@ from datetime import timedelta from django import get_version as django_version from django.contrib import messages -from django.db.models import Avg, Count, Sum +from django.contrib.auth import login +from django.db.models import Avg, Count, Q, Sum from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse from django.utils import timezone -from .forms import MomentumEntryForm +from .forms import MomentumEntryForm, SignUpForm from .models import Category, MomentumEntry @@ -17,6 +19,21 @@ APP_NAME = "Momentum Atlas" APP_TAGLINE = "A polished personal dashboard for tracking focus, energy, and small wins." +def _entries_for_request(request): + entries = MomentumEntry.objects.select_related("category") + if request.user.is_authenticated: + return entries.filter(user=request.user) + return entries.filter(user__isnull=True) + + +def _categories_for_request(request): + if request.user.is_authenticated: + entry_filter = Q(entries__user=request.user) + else: + entry_filter = Q(entries__user__isnull=True) + return Category.objects.annotate(entry_total=Count("entries", filter=entry_filter)) + + def _build_weekly_trend(entries): today = timezone.localdate() start_date = today - timedelta(days=6) @@ -54,8 +71,8 @@ def _build_weekly_trend(entries): return trend -def _dashboard_context(): - entries = MomentumEntry.objects.select_related("category") +def _dashboard_context(request): + entries = _entries_for_request(request) recent_entries = entries[:6] last_30_days = timezone.localdate() - timedelta(days=29) stats_window = entries.filter(entry_date__gte=last_30_days) @@ -80,13 +97,16 @@ def _dashboard_context(): spotlight = "Your recent focus trend is excellent—keep protecting that deep-work time." elif focus_average >= 6: spotlight = "Momentum is building. A little more consistency could turn this into a real streak." - else: + elif totals["total_entries"]: spotlight = "A reset week might help—shrink the task list and aim for one clear win each day." + else: + spotlight = "Your dashboard will start filling in as soon as you save the first check-in." return { "recent_entries": recent_entries, - "categories": Category.objects.all(), + "categories": _categories_for_request(request), "weekly_trend": weekly_trend, + "is_demo_mode": not request.user.is_authenticated, "stats": { "total_entries": totals["total_entries"], "avg_focus": focus_average, @@ -105,10 +125,16 @@ def home(request): now = timezone.now() if request.method == "POST": + if not request.user.is_authenticated: + messages.info(request, "Create a free account or log in to save personal check-ins.") + return redirect(f"{reverse('login')}?next={reverse('home')}") + form = MomentumEntryForm(request.POST) if form.is_valid(): - entry = form.save() - messages.success(request, "Momentum captured. Your new check-in is ready.") + entry = form.save(commit=False) + entry.user = request.user + entry.save() + messages.success(request, "Momentum captured. Your new private check-in is ready.") return redirect(f"{entry.get_absolute_url()}?created=1") messages.error(request, "Please fix the form errors and try again.") else: @@ -126,14 +152,14 @@ def home(request): "page_title": f"{APP_NAME} | Daily focus dashboard", "meta_description": "Track daily focus, energy, and deep-work minutes in a polished Python dashboard.", "form": form, - **_dashboard_context(), + **_dashboard_context(request), } return render(request, "core/index.html", context) def entry_list(request): selected_slug = request.GET.get("category", "") - entries = MomentumEntry.objects.select_related("category") + entries = _entries_for_request(request) if selected_slug: entries = entries.filter(category__slug=selected_slug) @@ -141,24 +167,47 @@ def entry_list(request): "page_title": f"All check-ins | {APP_NAME}", "meta_description": "Browse recent check-ins and filter your momentum history by category.", "entries": entries, - "categories": Category.objects.annotate(entry_total=Count("entries")), + "categories": _categories_for_request(request), "selected_slug": selected_slug, + "is_demo_mode": not request.user.is_authenticated, } return render(request, "core/entry_list.html", context) def entry_detail(request, pk): - entry = get_object_or_404(MomentumEntry.objects.select_related("category"), pk=pk) - related_entries = ( - MomentumEntry.objects.select_related("category") - .filter(category=entry.category) - .exclude(pk=entry.pk)[:3] - ) + scoped_entries = _entries_for_request(request) + entry = get_object_or_404(scoped_entries, pk=pk) + related_entries = scoped_entries.filter(category=entry.category).exclude(pk=entry.pk)[:3] context = { "page_title": f"{entry.title} | {APP_NAME}", "meta_description": entry.takeaway, "entry": entry, "related_entries": related_entries, "created": request.GET.get("created") == "1", + "is_demo_mode": not request.user.is_authenticated, } return render(request, "core/entry_detail.html", context) + + +def signup(request): + if request.user.is_authenticated: + return redirect("home") + + if request.method == "POST": + form = SignUpForm(request.POST) + if form.is_valid(): + user = form.save() + login(request, user) + messages.success(request, "Your account is ready. You can now save private momentum entries.") + return redirect(request.POST.get("next") or "home") + messages.error(request, "Please fix the sign-up form and try again.") + else: + form = SignUpForm() + + context = { + "page_title": f"Create account | {APP_NAME}", + "meta_description": "Create a private Momentum Atlas account to save personal check-ins.", + "form": form, + "next_url": request.GET.get("next") or request.POST.get("next") or reverse("home"), + } + return render(request, "core/signup.html", context) diff --git a/static/css/custom.css b/static/css/custom.css index ecc15bc..951a829 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -87,6 +87,27 @@ p { padding: 0.55rem 1rem !important; } +.nav-user { + display: inline-flex; + align-items: center; + min-height: 2.75rem; + padding: 0.55rem 0.9rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + color: #ffffff; + font-weight: 700; +} + +.nav-form, +.inline-form { + margin: 0; +} + +.nav-button { + background: transparent; + cursor: pointer; +} + .hero-section { position: relative; padding: 1rem 0 4rem; @@ -206,7 +227,8 @@ p { } .chip-row, -.filter-row { +.filter-row, +.message-stack { display: flex; flex-wrap: wrap; gap: 0.75rem; @@ -255,7 +277,8 @@ p { .section-heading h2, .subpage-header h1, -.detail-panel h1 { +.detail-panel h1, +.auth-card h1 { font-size: clamp(2rem, 3vw, 3rem); margin-bottom: 0.8rem; } @@ -269,12 +292,14 @@ p { .detail-panel, .sidebar-panel, .subpage-header, -.empty-state { +.empty-state, +.auth-card { padding: 2rem; } .form-panel .form-control, -.form-panel .form-select { +.form-panel .form-select, +.auth-card .form-control { border-radius: 16px; border: 1px solid rgba(148, 163, 184, 0.35); padding: 0.9rem 1rem; @@ -345,6 +370,27 @@ p { margin-bottom: 0.6rem; } +.auth-callout { + display: grid; + gap: 0.75rem; +} + +.auth-shell { + padding: 1rem 0 3rem; +} + +.auth-card { + max-width: 100%; +} + +.auth-footer { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + margin-top: 1.25rem; +} + .trend-chart { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); @@ -534,7 +580,12 @@ p { .sidebar-panel, .subpage-header, .empty-state, - .insight-panel { + .insight-panel, + .auth-card { padding: 1.4rem; } + + .nav-user { + width: fit-content; + } } diff --git a/static/images/favicon.svg b/static/images/favicon.svg new file mode 100644 index 0000000..90f19b9 --- /dev/null +++ b/static/images/favicon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index ecc15bc..951a829 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -87,6 +87,27 @@ p { padding: 0.55rem 1rem !important; } +.nav-user { + display: inline-flex; + align-items: center; + min-height: 2.75rem; + padding: 0.55rem 0.9rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + color: #ffffff; + font-weight: 700; +} + +.nav-form, +.inline-form { + margin: 0; +} + +.nav-button { + background: transparent; + cursor: pointer; +} + .hero-section { position: relative; padding: 1rem 0 4rem; @@ -206,7 +227,8 @@ p { } .chip-row, -.filter-row { +.filter-row, +.message-stack { display: flex; flex-wrap: wrap; gap: 0.75rem; @@ -255,7 +277,8 @@ p { .section-heading h2, .subpage-header h1, -.detail-panel h1 { +.detail-panel h1, +.auth-card h1 { font-size: clamp(2rem, 3vw, 3rem); margin-bottom: 0.8rem; } @@ -269,12 +292,14 @@ p { .detail-panel, .sidebar-panel, .subpage-header, -.empty-state { +.empty-state, +.auth-card { padding: 2rem; } .form-panel .form-control, -.form-panel .form-select { +.form-panel .form-select, +.auth-card .form-control { border-radius: 16px; border: 1px solid rgba(148, 163, 184, 0.35); padding: 0.9rem 1rem; @@ -345,6 +370,27 @@ p { margin-bottom: 0.6rem; } +.auth-callout { + display: grid; + gap: 0.75rem; +} + +.auth-shell { + padding: 1rem 0 3rem; +} + +.auth-card { + max-width: 100%; +} + +.auth-footer { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + margin-top: 1.25rem; +} + .trend-chart { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); @@ -534,7 +580,12 @@ p { .sidebar-panel, .subpage-header, .empty-state, - .insight-panel { + .insight-panel, + .auth-card { padding: 1.4rem; } + + .nav-user { + width: fit-content; + } } diff --git a/staticfiles/images/favicon.svg b/staticfiles/images/favicon.svg new file mode 100644 index 0000000..90f19b9 --- /dev/null +++ b/staticfiles/images/favicon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + +