From 27aac4da8ca4bf072039fef290c878f58d585a11 Mon Sep 17 00:00:00 2001 From: Irene Sheen Date: Wed, 10 Dec 2025 18:28:07 +0530 Subject: [PATCH] Add login and home page --- appunti/settings.py | 5 + notes/api_views.py | 2 +- notes/models.py | 11 ++ .../static/notes/images/icons/post-a-note.png | Bin 0 -> 3083 bytes notes/static/notes/images/icons/profile.png | Bin 0 -> 2557 bytes notes/static/notes/stylesheets/style.css | 107 ++++++++++++++++++ notes/templates/notes/base.html | 25 ++++ notes/templates/notes/home.html | 22 ++++ notes/templates/registration/login.html | 19 ++++ notes/urls.py | 5 +- notes/views.py | 11 +- pyproject.toml | 1 + uv.lock | 34 ++++++ 13 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 notes/static/notes/images/icons/post-a-note.png create mode 100644 notes/static/notes/images/icons/profile.png create mode 100644 notes/static/notes/stylesheets/style.css create mode 100644 notes/templates/notes/base.html create mode 100644 notes/templates/notes/home.html create mode 100644 notes/templates/registration/login.html diff --git a/appunti/settings.py b/appunti/settings.py index c5ab158..cb01340 100644 --- a/appunti/settings.py +++ b/appunti/settings.py @@ -39,6 +39,7 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", "rest_framework", "notes", + "livereload", ] MIDDLEWARE = [ @@ -49,6 +50,7 @@ MIDDLEWARE = [ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "livereload.middleware.LiveReloadScript", ] ROOT_URLCONF = "appunti.urls" @@ -131,3 +133,6 @@ REST_FRAMEWORK = { 'rest_framework.permissions.IsAuthenticated', ], } + +LOGIN_URL = "login" +LOGIN_REDIRECT_URL = "/" diff --git a/notes/api_views.py b/notes/api_views.py index b44287b..92ce883 100644 --- a/notes/api_views.py +++ b/notes/api_views.py @@ -9,4 +9,4 @@ class NoteListView(generics.ListCreateAPIView): serializer_class = NoteSerializer def get_queryset(self): - return Note.objects.filter(to_user=self.request.user, expiry__gt=timezone.now()).select_related("from_user", "to_user") + return self.request.user.alive_received_notes diff --git a/notes/models.py b/notes/models.py index e216698..b9f1669 100644 --- a/notes/models.py +++ b/notes/models.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import AbstractUser from django.db import models +from django.utils import timezone # Create your models here. @@ -7,6 +8,16 @@ class User(AbstractUser): allow_notes_from = models.ManyToManyField('User', related_name='allowed_notes_to', blank=True) expiry_seconds = models.PositiveIntegerField(default=86400) + @property + def alive_received_notes(self) -> models.QuerySet["Note"]: + return self.received_notes.filter(expiry__gt=timezone.now()).select_related("from_user", "to_user") + + @property + def visible_name(self) -> str: + if self.first_name or self.last_name: + return self.get_full_name() + return self.username + class Note(models.Model): from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='created_notes') diff --git a/notes/static/notes/images/icons/post-a-note.png b/notes/static/notes/images/icons/post-a-note.png new file mode 100644 index 0000000000000000000000000000000000000000..dabcf6a6517f8f7ee334144272a4988fbfc49185 GIT binary patch literal 3083 zcmV+m4D|DfP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk3&n>i_@? zVM#VUbxalqv0 z(UX<~F9FMa!e^kB9!Q;!$?q*+Xf!#PwGB8oA9Y)qVUc&prUCcF05X6Y;CGpHdY(nJ(t_*^;CA5HnAx@&xHGEV znhZ3*kbN8YF)%*HAM@d8RN80QEOq7wvf04RF^3G`EZ}Pv(fmL*1Gt;;@=+4(`7y9K z6$DEyq7@IaDZniWf(+nGz*U)a`dbECaUdHDTn?O^z|3$sa7`wio^GHOr(XGHf^B#- zurQNO*BEGpi)M!d*8;;6Y=#P8P88W{479>%F>siffhxl3PLmC^{718kqm*eQ_E2Mi zMbYeF11(RG)d4pFL(Cd`F?2dGe}5^|K6tF2HLrgX6exsU!wr|=T1I#o_I)|oqwIfh+J zmY6|uYdi2_*R*M%^+J*=2`{4@UshGcfwN|X`N3ezrT+qt>^TUq)Y&s&)slWj*tk9Z zbAv^&4Y}e9Gt%B_C2Y_(bI9yCc;0XOIUA3H}` zRTbsq#!*&Rhsw(0G+5XGTm?MQTgVa(nxyXmcNXjb&QUgO80DizQ8s1_q*7$JZ5zzd ztt}cTT?)L`Q%$yyIT8ril&F1iP|*&W&C;=TEA359>}qJBeZ>lBX&E#o1J)Bx0u8$B zlUYRD8?x&NZ$8Nr=#8Zhy}Kf4H{oQ@2P43m2K-%12xK#XKT{glb_M*( zId=hkz7qJ=z;}RAgd1x3@=Gh>q{U}|yYc~8VnCC0W>mzM3S^Hu=jL^NA(Ku|1WpF# z0f!J?0;?*Lk1~Xl2ObCR7)XjtBxsVZ1#T&k&zS`taL(2D==Bh~3xLyrafG{%30UaYjhs1QS@3%I6<+YP;kW_v?yIZO=(Q*%mU{)=bp`LuG<^HiCv!e z)H@MeozLqE8M%_w!k8_oU8SK8Nf(({;s@C!l6I8{WNnguAgRhc6EDc7OWIl@khMy> zNzxb1Gx36~PSTnZfviQ+0!bsxGyhjy?t-cZjw^xl$N(1ufpc!B;T5lFc9f*;B@oFr zOG-a6XBHobINqW~KWR20f9Hs$iFf7DZ4;EP&j-HX=@d2si=1;UhDp3r=WyUWPsiR3T;rU}7$WfpZ4TkBWo$oo8IX;V^q8k*3^q!dWL}6rWYv=H_5|53Ntc=zk^$L`p892Nl5RCG#2>Os zNsFUJQG68te~>iRJP~imY9#%_6J(t?{Z2Gb#2d0YNlQFJwocMi^F+KM8!72tkBj7O zk@OAoMEnc?-+PQ?pGvySrv1blvQ(5VeMPcelIjhzctbWq(tREwYm;=Pc_QAB9U|!w zk88?iCEahHh&N=llAiFqK;Bd4iFiYHXteyfPsmnVO%`v+CQEwR^F|U4w#I*a0jrcW zLDKV{A^W?n!WeJRj*j+T^0{Nl+mennZ^XM#_PEDptR($Y(lql%yi?{<&)eesTei7Z z>_J-?UEcAf^^%S^PsAItYDt?s7yhT3CkD4FdP6Kq(oRp2 z>=jAl%nR{{tVYsGPmul1nz6|NZIP#bS)(n!k{rnDBsF;&U7l$kh@ZM!O=uo4zR2gY z8Mw|lx6Isz1 zWUfe7+ANp!RuNKVM|5+sO9(T^IFteHZ6VWCB2Ln$7wZL}ZNnmI6*yx;l)=Xu}$6#QJS;16D Z{SV@(oiM8Wtw8_)002ovPDHLkV1i-}p9KH_ literal 0 HcmV?d00001 diff --git a/notes/static/notes/images/icons/profile.png b/notes/static/notes/images/icons/profile.png new file mode 100644 index 0000000000000000000000000000000000000000..2069253ad2fb3e1178076bfad644bec50f240f33 GIT binary patch literal 2557 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 TP*fpu00000NkvXXu0mjffLhXo literal 0 HcmV?d00001 diff --git a/notes/static/notes/stylesheets/style.css b/notes/static/notes/stylesheets/style.css new file mode 100644 index 0000000..6d18786 --- /dev/null +++ b/notes/static/notes/stylesheets/style.css @@ -0,0 +1,107 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + background-color: #f4f0f0; + font-family: cursive; +} + +header { + width: 100%; + display: flex; + background-color: #e7bbe0; +} + +header > .home { + flex-grow: 1; + font-size: 3em; + text-decoration: none; + color: #000; +} + +header img { + height: 40px; + margin: 20px; +} + +main { + padding: 0 30px; +} + +.notes { + width: 100%; + columns: auto 300px; + column-gap: 80px; + justify-content: center; + align-content: center; + align-items: center; + margin: auto; +} + +.notes.notes-grid { + display: grid; +} + +.note { + display: inline-block; + width: 100%; + max-width: 300px; + padding: 30px; + border: 2px solid rgba(30, 30, 30, 0.4); + font-weight: 400; + font-style: normal; + box-shadow: 15px 28px 25px -18px rgba(0, 0, 0, 0.2); + text-align: center; + margin-bottom: 40px; +} + +.note:nth-child(3n) { + border-radius: 155px 25px 15px 25px / 15px 225px 230px 150px; +} + +.note:nth-child(3n + 1) { + border-radius: 25px 155px 15px 25px / 115px 25px 225px 150px; +} + +.note:nth-child(3n + 2) { + border-radius: 25px 150px 25px 155px / 115px 25px 225px 50px; +} + +.note:nth-child(4n) { + background-color: #d4f2d8; +} + +.note:nth-child(4n + 1) { + background-color: #fac9dc; +} + +.note:nth-child(4n + 2) { + background-color: #fff372; +} + +.note:nth-child(4n + 3) { + background-color: #a4d6f7; +} + +.note-from, .note-at { + text-align: right; +} + + +.card { + display: inline-block; + width: 100%; + padding: 30px; + border: 2px solid rgba(30, 30, 30, 0.4); + font-weight: 400; + font-style: normal; + box-shadow: 15px 28px 25px -18px rgba(0, 0, 0, 0.2); + text-align: center; + margin-bottom: 40px; + border-radius: 25px 155px 15px 25px / 115px 25px 225px 150px; + background-color: #fac9dc; +} + diff --git a/notes/templates/notes/base.html b/notes/templates/notes/base.html new file mode 100644 index 0000000..279b75d --- /dev/null +++ b/notes/templates/notes/base.html @@ -0,0 +1,25 @@ +{% load static %} + + + + + + + {% block title %}{% endblock %} | Appunti + + +
+ Appunti + + Post a note + + + Profile + +
+
+ {% block body %} + {% endblock %} +
+ + diff --git a/notes/templates/notes/home.html b/notes/templates/notes/home.html new file mode 100644 index 0000000..235b1dd --- /dev/null +++ b/notes/templates/notes/home.html @@ -0,0 +1,22 @@ +{% extends "notes/base.html" %} + +{% block title %}Dashboard{% endblock %} + +{% block body %} +

Dashboard

+ +

Notes for you

+ {% if notes %} +
+ {% for note in notes %} +
+

{{ note.note }}

+

From {{ note.from_user.visible_name }}

+

{{ note.created_at|date:"j M, H:i"}}

+
+ {% endfor %} +
+ {% else %} +
No Notes found
+ {% endif %} +{% endblock %} diff --git a/notes/templates/registration/login.html b/notes/templates/registration/login.html new file mode 100644 index 0000000..27c5cc2 --- /dev/null +++ b/notes/templates/registration/login.html @@ -0,0 +1,19 @@ +{% extends "notes/base.html" %} +{% block title %}Login{% endblock %} +{% block body %} + + +
+ +
+{% endblock %} diff --git a/notes/urls.py b/notes/urls.py index 7a0d127..9c583da 100644 --- a/notes/urls.py +++ b/notes/urls.py @@ -1,9 +1,12 @@ +from django.contrib.auth.views import LoginView from django.urls import include, path -from notes import api_views +from notes import views, api_views urlpatterns = [ + path("", views.HomePage.as_view(), name="home"), + path("login/", LoginView.as_view(), name='login'), path("api/notes/", api_views.NoteListView.as_view(), name="api-notes-list"), path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), ] diff --git a/notes/views.py b/notes/views.py index 91ea44a..198f1ca 100644 --- a/notes/views.py +++ b/notes/views.py @@ -1,3 +1,12 @@ -from django.shortcuts import render +from typing import Any +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic import TemplateView # Create your views here. +class HomePage(LoginRequiredMixin, TemplateView): + template_name = "notes/home.html" + + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: + ctx = super().get_context_data(**kwargs) + ctx['notes'] = self.request.user.alive_received_notes + return ctx diff --git a/pyproject.toml b/pyproject.toml index 1c517ed..1297437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ [dependency-groups] dev = [ + "django-livereload-server>=0.5.1", "django-stubs>=5.2.8", "ipdb>=0.13.13", ] diff --git a/uv.lock b/uv.lock index 5242695..e4c14c8 100644 --- a/uv.lock +++ b/uv.lock @@ -13,6 +13,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "django-livereload-server" }, { name = "django-stubs" }, { name = "ipdb" }, ] @@ -25,6 +26,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "django-livereload-server", specifier = ">=0.5.1" }, { name = "django-stubs", specifier = ">=5.2.8" }, { name = "ipdb", specifier = ">=0.13.13" }, ] @@ -79,6 +81,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/3d/a035a4ee9b1d4d4beee2ae6e8e12fe6dee5514b21f62504e22efcbd9fb46/django-5.2.8-py3-none-any.whl", hash = "sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f", size = 8289692, upload-time = "2025-11-05T14:07:28.761Z" }, ] +[[package]] +name = "django-livereload-server" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/56/313c43b871d4cc167260faf2924a72ffa9105cdd78faf028341338115800/django-livereload-server-0.5.1.tar.gz", hash = "sha256:e1fecca2a74ec87235bbfdf8b63365499324d9a87017427e5d297711d1cd532d", size = 23111, upload-time = "2023-12-19T23:22:02.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/cd/77566526193cb49e805bd33a6b982ba5a39f3a7f828dd6647a76bf977f3c/django_livereload_server-0.5.1-py2.py3-none-any.whl", hash = "sha256:e03bd65d1679ef1b4a5e22e2a77d11d3cfb0e3d21ae25afba49e280924ba6f58", size = 25920, upload-time = "2023-12-19T23:22:00.494Z" }, +] + [[package]] name = "django-stubs" version = "5.2.8" @@ -281,6 +296,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + [[package]] name = "traitlets" version = "5.14.3"