Compare commits
4 Commits
df595e4f19
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c0289e715 | |||
| 07dcb72deb | |||
| 495dfe7b8e | |||
| 27aac4da8c |
@@ -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,7 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
],
|
||||
}
|
||||
|
||||
LOGIN_URL = "login"
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
LOGOUT_REDIRECT_URL = "login"
|
||||
|
||||
@@ -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
|
||||
|
||||
8
notes/forms.py
Normal file
8
notes/forms.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django import forms
|
||||
|
||||
from notes.models import Note
|
||||
|
||||
class PostNoteForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Note
|
||||
fields = ["to_user", "note"]
|
||||
@@ -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')
|
||||
|
||||
BIN
notes/static/notes/images/icons/logout.png
Normal file
BIN
notes/static/notes/images/icons/logout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
notes/static/notes/images/icons/post-a-note.png
Normal file
BIN
notes/static/notes/images/icons/post-a-note.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
notes/static/notes/images/icons/profile.png
Normal file
BIN
notes/static/notes/images/icons/profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
131
notes/static/notes/stylesheets/style.css
Normal file
131
notes/static/notes/stylesheets/style.css
Normal file
@@ -0,0 +1,131 @@
|
||||
* {
|
||||
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;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
header img {
|
||||
height: 40px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.logout {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
padding: 5px;
|
||||
min-width: 200px;
|
||||
display: block;
|
||||
box-shadow: 15px 28px 25px -18px rgba(0, 0, 0, 0.1);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
select:nth-child(3n), textarea:nth-child(3n), input:nth-child(3n), .note:nth-child(3n) {
|
||||
border-radius: 155px 25px 15px 25px / 15px 225px 230px 150px;
|
||||
}
|
||||
|
||||
select:nth-child(3n + 1), textarea:nth-child(3n + 1), input:nth-child(3n + 1), .note:nth-child(3n + 1) {
|
||||
border-radius: 25px 155px 15px 25px / 115px 25px 225px 150px;
|
||||
}
|
||||
|
||||
select:nth-child(3n + 2), textarea:nth-child(3n + 2), input:nth-child(3n + 2), .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;
|
||||
}
|
||||
29
notes/templates/notes/base.html
Normal file
29
notes/templates/notes/base.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="stylesheet" href="{% static "notes/stylesheets/style.css" %}" type="text/css">
|
||||
<title>{% block title %}{% endblock %} | Appunti</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="{% url "home" %}" class="home">Appunti</a>
|
||||
<a href="{% url "post-a-note" %}">
|
||||
<img src="{% static "notes/images/icons/post-a-note.png" %}" alt="Post a note" title="Post a note" />
|
||||
</a>
|
||||
<a href="{% url "profile" %}">
|
||||
<img src="{% static "notes/images/icons/profile.png" %}" alt="Profile" title="Profile" />
|
||||
</a>
|
||||
<form method="post" action="{% url "logout" %}">
|
||||
{% csrf_token %}
|
||||
<button class="logout"><img src="{% static "notes/images/icons/logout.png" %}" alt="Logout" title="Logout" /></button>
|
||||
</form>
|
||||
</header>
|
||||
<main>
|
||||
{% block body %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
22
notes/templates/notes/home.html
Normal file
22
notes/templates/notes/home.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "notes/base.html" %}
|
||||
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Dashboard</h1>
|
||||
|
||||
<h2>Notes for you</h2>
|
||||
{% if notes %}
|
||||
<div class="notes">
|
||||
{% for note in notes %}
|
||||
<div class="note">
|
||||
<p>{{ note.note }}</p>
|
||||
<p class="note-from">From {{ note.from_user.visible_name }}</p>
|
||||
<p class="note-at">{{ note.created_at|date:"j M, H:i"}}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card">No Notes found</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
11
notes/templates/notes/note_form.html
Normal file
11
notes/templates/notes/note_form.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% extends "notes/base.html" %}
|
||||
|
||||
{% block title %}Post a Note{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Post a Note</h1>
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Post!</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
12
notes/templates/notes/user_form.html
Normal file
12
notes/templates/notes/user_form.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "notes/base.html" %}
|
||||
|
||||
{% block title %}Profile{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Profile</h1>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Post!</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
19
notes/templates/registration/login.html
Normal file
19
notes/templates/registration/login.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% extends "notes/base.html" %}
|
||||
{% block title %}Login{% endblock %}
|
||||
{% block body %}
|
||||
<div id="header">
|
||||
<h1>Appunti Login Screen</h1>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<div class="login">
|
||||
<form action="{% url 'login' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<h2>Welcome!</h2>
|
||||
<p>Please login to continue</p>
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,9 +1,15 @@
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
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("post-a-note/", views.PostNoteView.as_view(), name="post-a-note"),
|
||||
path("profile/", views.ProfileView.as_view(), name="profile"),
|
||||
path("login/", LoginView.as_view(), name='login'),
|
||||
path("logout/", LogoutView.as_view(), name='logout'),
|
||||
path("api/notes/", api_views.NoteListView.as_view(), name="api-notes-list"),
|
||||
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
||||
]
|
||||
|
||||
@@ -1,3 +1,44 @@
|
||||
from django.shortcuts import render
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.views.generic import CreateView, TemplateView, UpdateView
|
||||
from django.views.generic.edit import FormMixin
|
||||
from notes.models import Note, User
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class PostNoteView(LoginRequiredMixin, CreateView):
|
||||
model = Note
|
||||
fields = ["to_user", "note"]
|
||||
success_url = reverse_lazy("post-a-note")
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.fields['to_user'].queryset = self.request.user.allowed_notes_to.all()
|
||||
return form
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = note = form.save(commit=False)
|
||||
note.expiry = timezone.now() + timedelta(seconds=note.to_user.expiry_seconds)
|
||||
note.from_user = self.request.user
|
||||
note.save()
|
||||
return FormMixin.form_valid(self, form)
|
||||
|
||||
|
||||
class ProfileView(LoginRequiredMixin, UpdateView):
|
||||
model = User
|
||||
fields = ["first_name", "last_name", "allow_notes_from", "expiry_seconds"]
|
||||
success_url = reverse_lazy("profile")
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
@@ -11,6 +11,7 @@ dependencies = [
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"django-livereload-server>=0.5.1",
|
||||
"django-stubs>=5.2.8",
|
||||
"ipdb>=0.13.13",
|
||||
]
|
||||
|
||||
34
uv.lock
generated
34
uv.lock
generated
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user