From 0bf6f721d60cef5bcac29f2d82f152f7b6036b9f Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Wed, 20 Jul 2022 15:15:48 -0600 Subject: [PATCH] Add accounts, login, and authentication checks --- src/accounts/forms.py | 12 +++ .../templates/accounts/account_create.html | 14 ++++ .../templates/accounts/account_detail.html | 83 +++++++++++++++++++ .../templates/accounts/account_form.html | 18 ++++ .../templates/accounts/account_list.html | 23 +++++ src/accounts/templates/accounts/profile.html | 72 ++++++++++++++++ .../templates/accounts/profile_form.html | 10 +++ src/accounts/urls.py | 11 +++ src/accounts/views.py | 54 +++++++++++- src/core/feeds.py | 25 ------ src/core/forms.py | 22 ++++- src/core/migrations/0005_post_recipients.py | 20 +++++ src/core/models.py | 11 +++ src/core/templates/core/comment_detail.html | 15 +++- src/core/templates/core/comment_form.html | 19 +++-- src/core/templates/core/post_detail.html | 6 +- src/core/templates/core/post_form.html | 17 +--- src/core/templates/core/topic_detail.html | 9 +- src/core/templates/core/topic_list.html | 4 +- src/core/views.py | 58 ++++++++----- src/forum/middleware.py | 17 ++++ src/forum/settings.py | 3 + src/forum/urls.py | 13 +-- src/static/scripts/cookie.js | 24 ++++++ src/static/scripts/timezone.js | 4 + src/static/styles/main.css | 55 +++++++++++- src/templates/base.html | 29 ++++--- src/templates/registration/logged_out.html | 9 ++ src/templates/registration/login.html | 33 ++++++++ .../registration/password_change_done.html | 12 +++ .../registration/password_change_form.html | 14 ++++ .../registration/password_reset_complete.html | 7 ++ .../registration/password_reset_confirm.html | 24 ++++++ .../registration/password_reset_done.html | 7 ++ .../registration/password_reset_email.html | 10 +++ .../registration/password_reset_form.html | 14 ++++ 36 files changed, 673 insertions(+), 105 deletions(-) create mode 100644 src/accounts/forms.py create mode 100755 src/accounts/templates/accounts/account_create.html create mode 100755 src/accounts/templates/accounts/account_detail.html create mode 100755 src/accounts/templates/accounts/account_form.html create mode 100755 src/accounts/templates/accounts/account_list.html create mode 100644 src/accounts/templates/accounts/profile.html create mode 100755 src/accounts/templates/accounts/profile_form.html create mode 100644 src/accounts/urls.py delete mode 100644 src/core/feeds.py create mode 100644 src/core/migrations/0005_post_recipients.py create mode 100644 src/forum/middleware.py create mode 100644 src/static/scripts/cookie.js create mode 100644 src/static/scripts/timezone.js create mode 100755 src/templates/registration/logged_out.html create mode 100755 src/templates/registration/login.html create mode 100755 src/templates/registration/password_change_done.html create mode 100755 src/templates/registration/password_change_form.html create mode 100755 src/templates/registration/password_reset_complete.html create mode 100755 src/templates/registration/password_reset_confirm.html create mode 100755 src/templates/registration/password_reset_done.html create mode 100755 src/templates/registration/password_reset_email.html create mode 100755 src/templates/registration/password_reset_form.html diff --git a/src/accounts/forms.py b/src/accounts/forms.py new file mode 100644 index 0000000..2f0b44f --- /dev/null +++ b/src/accounts/forms.py @@ -0,0 +1,12 @@ +from django import forms +from .models import User + + +class AccountUpdateForm(forms.ModelForm): + class Meta: + model = User + fields = [ + 'first_name', + 'last_name', + 'email', + ] diff --git a/src/accounts/templates/accounts/account_create.html b/src/accounts/templates/accounts/account_create.html new file mode 100755 index 0000000..8bee1d3 --- /dev/null +++ b/src/accounts/templates/accounts/account_create.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Sign up

+ +
+ {% csrf_token %} + {{ form.as_p }} + + +
+
+{% endblock %} diff --git a/src/accounts/templates/accounts/account_detail.html b/src/accounts/templates/accounts/account_detail.html new file mode 100755 index 0000000..55df003 --- /dev/null +++ b/src/accounts/templates/accounts/account_detail.html @@ -0,0 +1,83 @@ +{% extends 'base.html' %} +{% load static %} +{% load helpers %} + +{% block content %} +
+
+

{{ object }}

+ {% if request.user == object %} +

Update account

+ {% endif %} +
+
+

Posts

+ + + + + + + + + {% for post in posts %} + + + + {% if post.comments.last is not None %} + {% with comment=post.comments.last %} + + {% endwith %} + {% else %} + + {% endif %} + + {% empty %} + + + + {% endfor %} + + + +
PostCommentsLast comment
No posts
+
+
+

Comments

+ + + + + + + + + + {% for comment in comments %} + + + {% with post=comment.content_object %} + + {% endwith %} + + + {% empty %} + + + + {% endfor %} + + + +
ContentPostDate
{{ comment.content|markdown|safe|truncatechars_html:64 }} +

{{ post.title }}

+ by {{ post.author }} » +
No comments
+
+
+{% endblock %} diff --git a/src/accounts/templates/accounts/account_form.html b/src/accounts/templates/accounts/account_form.html new file mode 100755 index 0000000..cbd2cf9 --- /dev/null +++ b/src/accounts/templates/accounts/account_form.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} + +{% block content %} +
+ +
+

Update Profile

+

Change password

+
+ {% csrf_token %} + {{ form.as_p }} + + or + Cancel +
+
+
+{% endblock %} diff --git a/src/accounts/templates/accounts/account_list.html b/src/accounts/templates/accounts/account_list.html new file mode 100755 index 0000000..e3d9893 --- /dev/null +++ b/src/accounts/templates/accounts/account_list.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Users

+ + + + + + + {% for user in user_list %} + + + + + {% empty %} + + {% endfor %} + +
UsernameName
{{ user.username }}{{user.first_name}} {{user.last_name}}
No users yet.
+
+{% endblock %} diff --git a/src/accounts/templates/accounts/profile.html b/src/accounts/templates/accounts/profile.html new file mode 100644 index 0000000..6a4349d --- /dev/null +++ b/src/accounts/templates/accounts/profile.html @@ -0,0 +1,72 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
+
+

Welcome {{profile.user.first_name}} {{profile.user.last_name }} +
+ Here's what's going on today +

+
+
+

Birthdays

+
    + {% for student in birthdays %} +
  • {{student}} is turning {{student.age|add:1}} on {{student.dob|date:"M j"}}
  • + {% empty %} +

    No Birthdays this next week.

    + {% endfor %} +
+
+
+

Today's Assignments

+
    + {% for component in components %} +
  • + {{component.subject}}, {{component}} +
  • + {% empty %} +

    Nothing for today.

    + {% endfor %} +
+
+
+

Today's Attendance

+ {% for day in attendance %} +

{{day.date}}

+ + + + + + + + + {% for entry in day.entry_set.all %} + + + + + + {% endfor %} + +
StudentStatus
{{entry.student}}{{entry.get_status_display}}Update
+ {% empty %} +

No attendance taken yet: Take attendance

+ {% endfor %} +
+
+

Assignments to be graded

+
    + {% for component in ungraded_components %} +
  • + {{component.subject}}, {{component}} +
  • + {% empty %} +

    Everything is graded to far.

    + {% endfor %} +
+
+
+{% endblock %} diff --git a/src/accounts/templates/accounts/profile_form.html b/src/accounts/templates/accounts/profile_form.html new file mode 100755 index 0000000..9b1dd9e --- /dev/null +++ b/src/accounts/templates/accounts/profile_form.html @@ -0,0 +1,10 @@ +
+

Settings

+
+ {% csrf_token %} + {{form.as_p}} +

+ +

+
+
diff --git a/src/accounts/urls.py b/src/accounts/urls.py new file mode 100644 index 0000000..4f89f4f --- /dev/null +++ b/src/accounts/urls.py @@ -0,0 +1,11 @@ +from django.urls import path, include +from . import views + +urlpatterns = [ + path('', views.AccountListView.as_view(), name='account-list'), + path('/', include([ + path('', views.AccountDetailView.as_view(), name='account-detail'), + path('update/', views.AccountUpdateView.as_view(), name='account-update'), + path('delete/', views.AccountDeleteView.as_view(), name='account-delete'), + ])), +] diff --git a/src/accounts/views.py b/src/accounts/views.py index 91ea44a..083aa79 100644 --- a/src/accounts/views.py +++ b/src/accounts/views.py @@ -1,3 +1,53 @@ -from django.shortcuts import render +from django.shortcuts import render, reverse, redirect, get_object_or_404 +from django.urls import reverse, reverse_lazy +from django.views.generic.base import TemplateView +from django.views.generic.detail import DetailView +from django.views.generic.list import ListView +from django.views.generic.edit import ( + FormView, CreateView, UpdateView, DeleteView, FormMixin +) +from django.contrib import messages +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.contrib.messages.views import SuccessMessageMixin +from django.contrib.auth.forms import PasswordChangeForm -# Create your views here. +from core.models import Comment, Post + +from .models import User +from .forms import AccountUpdateForm + + +class AccountListView(LoginRequiredMixin, ListView): + model = User + template_name = 'accounts/account_list.html' + + +class AccountDetailView(LoginRequiredMixin, DetailView): + model = User + template_name = 'accounts/account_detail.html' + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['posts'] = Post.objects.filter(author=self.object).order_by('-created_at') + context['comments'] = Comment.objects.filter(author=self.object).order_by('-created_at') + + return context + + +class AccountUpdateView(LoginRequiredMixin, UserPassesTestMixin, SuccessMessageMixin, UpdateView): + model = User + form_class = AccountUpdateForm + template_name = 'accounts/account_form.html' + success_message = 'Account updated.' + success_url = reverse_lazy('core:topic-list') + + def test_func(self): + return self.request.user.pk == self.get_object().pk + + +class AccountDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): + model = User + success_url = reverse_lazy('core:topic-list') + + def test_func(self): + return self.request.user.pk == self.get_object().pk diff --git a/src/core/feeds.py b/src/core/feeds.py deleted file mode 100644 index 422d1d7..0000000 --- a/src/core/feeds.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.urls import reverse -from django.contrib.syndication.views import Feed -from django.utils.feedgenerator import Atom1Feed - -from .models import Post - - -class RssLatestPostFeed(Feed): - title = 'Latest posts' - link = '/sitenews/' - description = 'A list of the latest posts.' - - def items(self): - return Post.objects.order_by('-created_at')[:5] - - def item_title(self, item): - return item.title - - def item_description(self, item): - return item.subtitle - - -class AtomLatestPostFeed(RssLatestPostFeed): - feed_type = Atom1Feed - subtitle = RssLatestPostFeed.description diff --git a/src/core/forms.py b/src/core/forms.py index d007cce..d5466c9 100644 --- a/src/core/forms.py +++ b/src/core/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.core.mail import send_mail from .models import Post, Comment @@ -21,16 +22,33 @@ class CommentCreateForm(forms.ModelForm): }) } + def send_notification(self): + # subject = self.cleaned_data['content_object'] + author = self.instance.author + message = self.cleaned_data.get('content') + post = Post.objects.get(pk=self.cleaned_data['object_id']) + recipients = list(post.recipients.all().values_list('email', flat=True)) + recipients.remove(author.email) + + send_mail( + post.title, + message, + author.email, + recipients + ) + class PostForm(forms.ModelForm): class Meta: model = Post fields = [ - 'author', 'title', 'content', - 'topic' + 'recipients' ] labels = { 'content': '' } + widgets = { + 'recipients': forms.CheckboxSelectMultiple() + } diff --git a/src/core/migrations/0005_post_recipients.py b/src/core/migrations/0005_post_recipients.py new file mode 100644 index 0000000..72dfc8b --- /dev/null +++ b/src/core/migrations/0005_post_recipients.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0.6 on 2022-07-20 20:35 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0004_alter_post_options_remove_post_is_published_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='post', + name='recipients', + field=models.ManyToManyField(related_name='subscriptions', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 7711054..6ce3379 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -76,6 +76,13 @@ class Comment(models.Model): objects = CommentManager() + @property + def edited(self): + if self.created_at.strftime('%Y%m%d%H%M%S') != self.updated_at.strftime('%Y%m%d%H%M%S'): + return True + else: + return False + def get_absolute_url(self): return reverse('core:comment-detail', kwargs={'pk': self.pk}) @@ -120,6 +127,10 @@ class Post(models.Model): ) comments = GenericRelation(Comment, related_query_name='parent') + recipients = models.ManyToManyField( + User, + related_name='subscriptions' + ) tags = GenericRelation(Tag) created_at = models.DateTimeField(auto_now_add=True) diff --git a/src/core/templates/core/comment_detail.html b/src/core/templates/core/comment_detail.html index 975da5e..62a26c2 100644 --- a/src/core/templates/core/comment_detail.html +++ b/src/core/templates/core/comment_detail.html @@ -2,9 +2,18 @@
-
- {{ comment.author }} » -
+
+
+ {{ comment.author }} » +
+
+ {% if comment.edited %} + Edited: + {% endif %} + {% if request.user == comment.author %}Edit{% endif %} +
+
+
{{ comment.content|markdown|safe }} diff --git a/src/core/templates/core/comment_form.html b/src/core/templates/core/comment_form.html index b4698c1..112c075 100644 --- a/src/core/templates/core/comment_form.html +++ b/src/core/templates/core/comment_form.html @@ -1,13 +1,14 @@ {% extends 'base.html' %} {% block content %} -

Update comment

-

Delete

-
- {% csrf_token %} - {{ form.as_p }} -

- or cancel -

-
+
+

Update comment

+
+ {% csrf_token %} + {{ form.as_p }} +

+ or cancel +

+
+
{% endblock %} diff --git a/src/core/templates/core/post_detail.html b/src/core/templates/core/post_detail.html index fd0da7f..35feb38 100644 --- a/src/core/templates/core/post_detail.html +++ b/src/core/templates/core/post_detail.html @@ -22,19 +22,21 @@

{{ post.title }}

- » + »

+ {% if request.user == post.author %}
Edit Delete
+ {% endif %}
{{ post.content|markdown|safe }}
-

Discussion

+

Discussion

{% for comment in post.comments.all %} diff --git a/src/core/templates/core/post_form.html b/src/core/templates/core/post_form.html index 723e338..03fa4e1 100644 --- a/src/core/templates/core/post_form.html +++ b/src/core/templates/core/post_form.html @@ -1,27 +1,14 @@ {% extends 'base.html' %} {% load static %} -{% block head %} - - -{% endblock %} - {% block content %}

Update Post

-
+ {% csrf_token %} {{ form.as_p }}

- or cancel + or cancel

diff --git a/src/core/templates/core/topic_detail.html b/src/core/templates/core/topic_detail.html index fc3a3d8..747591e 100644 --- a/src/core/templates/core/topic_detail.html +++ b/src/core/templates/core/topic_detail.html @@ -12,9 +12,10 @@

{{ topic.name }}

-

{{ topic.description }} » {{ topic.created_at }}

+

{{ topic.description }}

+

Created:

- + New post + + New post

@@ -30,13 +31,13 @@

{{ post.title }}

- by {{ post.author }} » {{ post.created_at }} + by {{ post.author }} » {{ post.comments__count }} {% if post.comments.last is not None %} {% with comment=post.comments.last %} - by {{ comment.author }} » {{ comment.created_at }} + by {{ comment.author }} » {% endwith %} {% else %} diff --git a/src/core/templates/core/topic_list.html b/src/core/templates/core/topic_list.html index cfdbc50..3704141 100644 --- a/src/core/templates/core/topic_list.html +++ b/src/core/templates/core/topic_list.html @@ -5,7 +5,7 @@

Topics

- + New topic + + New topic

@@ -28,7 +28,7 @@ {% with post=topic.post_set.last %} {{ post.title }}
- by {{ post.author }} » {{ post.created_at }} + by {{ post.author }} » {% endwith %} diff --git a/src/core/views.py b/src/core/views.py index 620ec33..5a15197 100644 --- a/src/core/views.py +++ b/src/core/views.py @@ -8,7 +8,7 @@ from django.views.generic.edit import ( FormView, CreateView, UpdateView, DeleteView, FormMixin ) from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.messages.views import SuccessMessageMixin from django.contrib.contenttypes.models import ContentType @@ -16,15 +16,15 @@ from .models import Topic, Post, Comment from .forms import PostForm, CommentCreateForm -class CommentListView(ListView): +class CommentListView(LoginRequiredMixin, ListView): model = Comment -class CommentDetailView(DetailView): +class CommentDetailView(LoginRequiredMixin, DetailView): model = Comment -class CommentCreateView(CreateView): +class CommentCreateView(LoginRequiredMixin, CreateView): model = Comment form_class = CommentCreateForm template_name_suffix = '_create_form' @@ -34,22 +34,31 @@ class CommentCreateView(CreateView): def form_valid(self, form): form.instance.author = self.request.user + form.send_notification() return super().form_valid(form) -class CommentUpdateView(SuccessMessageMixin, UpdateView): +class CommentUpdateView( + LoginRequiredMixin, UserPassesTestMixin, SuccessMessageMixin, UpdateView +): model = Comment success_message = 'Comment saved.' - fields = '__all__' + fields = ['content'] + + def test_func(self): + return self.request.user.pk == self.get_object().author.pk + + def get_success_url(self): + return self.object.content_object.get_absolute_url() -class CommentDeleteView(SuccessMessageMixin, DeleteView): +class CommentDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Comment success_message = 'Comment deleted.' success_url = reverse_lazy('core:comment-list') -class TopicListView(ListView): +class TopicListView(LoginRequiredMixin, ListView): model = Topic def get_queryset(self): @@ -59,26 +68,29 @@ class TopicListView(ListView): return queryset -class TopicCreateView(SuccessMessageMixin, CreateView): +class TopicCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = Topic success_message = 'Topic created.' template_name_suffix = '_create_form' fields = '__all__' -class TopicDetailView(DetailView): +class TopicDetailView(LoginRequiredMixin, DetailView): model = Topic pk_url_kwarg = 'topic_pk' -class TopicUpdateView(SuccessMessageMixin, UpdateView): +class TopicUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = Topic pk_url_kwarg = 'topic_pk' success_message = 'Topic saved.' - fields = '__all__' + fields = [ + 'name', + 'description' + ] -class TopicDeleteView(SuccessMessageMixin, DeleteView): +class TopicDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Topic pk_url_kwarg = 'topic_pk' success_message = 'Topic deleted.' @@ -89,14 +101,11 @@ class PostListView(ListView): model = Post -class PostCreateView(SuccessMessageMixin, CreateView): +class PostCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = Post success_message = 'Post created.' template_name_suffix = '_create_form' - fields = [ - 'title', - 'content', - ] + form_class = PostForm def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) @@ -111,7 +120,7 @@ class PostCreateView(SuccessMessageMixin, CreateView): return super().form_valid(form) -class PostDetailView(DetailView): +class PostDetailView(LoginRequiredMixin, DetailView): model = Post pk_url_kwarg = 'post_pk' @@ -126,14 +135,19 @@ class PostDetailView(DetailView): return context -class PostUpdateView(SuccessMessageMixin, UpdateView): +class PostUpdateView( + LoginRequiredMixin, UserPassesTestMixin, SuccessMessageMixin, UpdateView +): model = Post pk_url_kwarg = 'post_pk' success_message = 'Post saved.' - fields = '__all__' + form_class = PostForm + + def test_func(self): + return self.request.user.pk == self.get_object().author.pk -class PostDeleteView(SuccessMessageMixin, DeleteView): +class PostDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Post pk_url_kwarg = 'post_pk' success_message = 'Post deleted.' diff --git a/src/forum/middleware.py b/src/forum/middleware.py new file mode 100644 index 0000000..a64cb54 --- /dev/null +++ b/src/forum/middleware.py @@ -0,0 +1,17 @@ +import zoneinfo +from urllib import parse +from django.utils import timezone + + +class TimezoneMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + tzname = request.COOKIES.get('timezone') + if tzname: + tzname = parse.unquote(tzname) + timezone.activate(zoneinfo.ZoneInfo(tzname)) + else: + timezone.deactivate() + return self.get_response(request) diff --git a/src/forum/settings.py b/src/forum/settings.py index a484570..ced4ecf 100644 --- a/src/forum/settings.py +++ b/src/forum/settings.py @@ -1,5 +1,6 @@ import os from pathlib import Path +from django.urls import reverse_lazy from .config import * @@ -40,6 +41,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'forum.middleware.TimezoneMiddleware', ] @@ -137,3 +139,4 @@ STATICFILES_FINDERS = ( DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' AUTH_USER_MODEL = 'accounts.User' +LOGIN_REDIRECT_URL = reverse_lazy('core:topic-list') diff --git a/src/forum/urls.py b/src/forum/urls.py index a0e83de..ada9bbc 100644 --- a/src/forum/urls.py +++ b/src/forum/urls.py @@ -2,13 +2,14 @@ from django.conf import settings from django.contrib import admin from django.urls import include, path from django.conf.urls.static import static -from core.feeds import RssLatestPostFeed, AtomLatestPostFeed urlpatterns = [ -path('', include(('core.urls', 'core'), namespace='core')), - path('sitenews/rss/', RssLatestPostFeed()), - path('sitenews/atom/', AtomLatestPostFeed()), + path('', include(('core.urls', 'core'), namespace='core')), + path('accounts/', include('accounts.urls'), name='accounts'), + path('accounts/', include('django.contrib.auth.urls')), path('admin/', admin.site.urls), ] -urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + +if settings.DEBUG: + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/src/static/scripts/cookie.js b/src/static/scripts/cookie.js new file mode 100644 index 0000000..8e2378d --- /dev/null +++ b/src/static/scripts/cookie.js @@ -0,0 +1,24 @@ +export function getCookie(name) { + let cookieValue = null + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';') + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim() + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)) + break + } + } + } + return cookieValue +} + +const twentyYears = 20 * 365 * 24 * 60 * 60 * 1000 + +export function setCookie(name, value, expiration=twentyYears) { + const body = [ name, value ].map(encodeURIComponent).join("=") + const expires = new Date(Date.now() + expiration).toUTCString() + const cookie = `${body}; domain=; path=/; SameSite=Lax; expires=${expires}` + document.cookie = cookie +} diff --git a/src/static/scripts/timezone.js b/src/static/scripts/timezone.js new file mode 100644 index 0000000..2f9652d --- /dev/null +++ b/src/static/scripts/timezone.js @@ -0,0 +1,4 @@ +import { setCookie } from './cookie.js' + +const { timeZone } = new Intl.DateTimeFormat().resolvedOptions() +setCookie('timezone', timeZone) diff --git a/src/static/styles/main.css b/src/static/styles/main.css index 97b08c0..671085f 100644 --- a/src/static/styles/main.css +++ b/src/static/styles/main.css @@ -22,6 +22,7 @@ body { color: #000000; } p { + margin-top: 0; margin-bottom: 1rem; } a { @@ -155,7 +156,7 @@ button, Block Elements ========================================================================== */ main { - max-width: 1200px; + max-width: 1024px; margin: 0 auto; } @@ -173,6 +174,12 @@ main { align-items: center; color: var(--color-white); } +.site__logo { + text-decoration: none; +} +.site__logo a:hover { + color: var(--color-white); +} .site__header h1 { margin: 0; } @@ -219,12 +226,25 @@ main { } +/* ========================================================================== + Account + ========================================================================== */ +.account header, +.account section { + margin-bottom: 4rem; +} + + /* ========================================================================== Post ========================================================================== */ +.post { + margin-bottom: 2rem; +} .post__header { display: flex; justify-content: space-between; + margin-bottom: 2rem; } .post__content { font-family: 'STIX Two Text'; @@ -243,10 +263,41 @@ main { padding: 1rem; } +.comment__header { + display: flex; + justify-content: space-between; +} + .comment__content { font-family: 'STIX Two Text'; font-size: 200%; - max-width: 64ch; + /*max-width: 64ch;*/ +} + +.comment__content h1, +.comment__content h2, +.comment__content h3, +.comment__content h4, +.comment__content h5 { + font-family: 'STIX Two Text'; + text-transform: unset; + margin: 2em 0 1em; +} + +.comment__content h1 { + font-size: 1.802em; +} +.comment__content h2 { + font-size: 1.602em; +} +.comment__content h3 { + font-size: 1.424em; +} +.comment__content h4 { + font-size: 1.266em; +} +.comment__content h5 { + font-size: 1.125em; } .comment p { diff --git a/src/templates/base.html b/src/templates/base.html index 0097383..16aaff6 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -17,29 +17,36 @@ {% endcompress %} + + {% block head %} {% endblock %}
- {% if messages %} -
- {% for message in messages %} -

{{ message }}

- {% endfor %} -
- {% endif %} - {% block breadcrumbs %} {% endblock breadcrumbs %} diff --git a/src/templates/registration/logged_out.html b/src/templates/registration/logged_out.html new file mode 100755 index 0000000..0b464dc --- /dev/null +++ b/src/templates/registration/logged_out.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

You have been logged out. Log in

+
+
+{% endblock %} diff --git a/src/templates/registration/login.html b/src/templates/registration/login.html new file mode 100755 index 0000000..b7d4b80 --- /dev/null +++ b/src/templates/registration/login.html @@ -0,0 +1,33 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Log in

+ {% if form.errors %} +

Your username and password didn't match. Please try again.

+ {% endif %} + +
+ {% csrf_token %} +

+ {{ form.username.label_tag }} + {{ form.username }} +

+ +

+ {{ form.password.label_tag }} + {{ form.password }} +
+ + Forgot your password? + Reset your password here. + +

+ +

+ + +

+
+
+{% endblock %} diff --git a/src/templates/registration/password_change_done.html b/src/templates/registration/password_change_done.html new file mode 100755 index 0000000..fb611b7 --- /dev/null +++ b/src/templates/registration/password_change_done.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

+ Password has been changed. + Log in +

+
+
+{% endblock %} diff --git a/src/templates/registration/password_change_form.html b/src/templates/registration/password_change_form.html new file mode 100755 index 0000000..cad7d69 --- /dev/null +++ b/src/templates/registration/password_change_form.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Change password

+
+ {% csrf_token %} + {{ form.as_p }} + + or + Cancel +
+
+{% endblock %} diff --git a/src/templates/registration/password_reset_complete.html b/src/templates/registration/password_reset_complete.html new file mode 100755 index 0000000..ad0c905 --- /dev/null +++ b/src/templates/registration/password_reset_complete.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Password was reset successfully.

+
+{% endblock %} diff --git a/src/templates/registration/password_reset_confirm.html b/src/templates/registration/password_reset_confirm.html new file mode 100755 index 0000000..1aaf4a8 --- /dev/null +++ b/src/templates/registration/password_reset_confirm.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} + +{% block content %} + +{% if validlink %} +
+

Reset password

+

Enter a new password below.

+
+ {% csrf_token %} + {{ form.as_p }} + + +
+
+{% else %} + +
+

Password reset failed

+
+ +{% endif %} + +{% endblock %} diff --git a/src/templates/registration/password_reset_done.html b/src/templates/registration/password_reset_done.html new file mode 100755 index 0000000..8f2b394 --- /dev/null +++ b/src/templates/registration/password_reset_done.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} + +{% block content %} +
+

An email with password reset instructions has been sent.

+
+{% endblock %} diff --git a/src/templates/registration/password_reset_email.html b/src/templates/registration/password_reset_email.html new file mode 100755 index 0000000..8c37094 --- /dev/null +++ b/src/templates/registration/password_reset_email.html @@ -0,0 +1,10 @@ +{% load i18n %}{% autoescape off %} +{% blocktranslate %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktranslate %} + +{% translate "Please go to the following page and choose a new password:" %} +{% block reset_link %} +{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} +{% endblock %} +{% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }} + +{% endautoescape %} diff --git a/src/templates/registration/password_reset_form.html b/src/templates/registration/password_reset_form.html new file mode 100755 index 0000000..1a21366 --- /dev/null +++ b/src/templates/registration/password_reset_form.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} {% block content %} +
+

Reset your password

+

Enter your email address below and we'll send you instructions on how to reset your password.

+
+ {% csrf_token %} + {{ form.as_p }} + +

+ +

+
+
+{% endblock %}