feat: 📜 flesh out blog system for site updates

This commit is contained in:
m5ka 2024-03-27 12:14:32 +00:00
parent 83ce6e887e
commit 4a3bb0ec56
15 changed files with 273 additions and 68 deletions

View file

@ -4,6 +4,7 @@ from django.contrib import admin
from django.urls import include, path
from moku.views.auth import LoginView, LogoutView
from moku.views.blog import IndexBlogView
from moku.views.post import FeedView
from moku.views.recipe import (
DeleteRecipeView,
@ -13,7 +14,7 @@ from moku.views.recipe import (
NewRecipeView,
ShowRecipeView,
)
from moku.views.static import ChangelogView, PrivacyView, TermsView
from moku.views.static import PrivacyView, TermsView
from moku.views.user import (
EditProfileView,
EditSettingsView,
@ -30,7 +31,7 @@ urlpatterns = [
path("signup", SignupView.as_view(), name="signup"),
path("profile", EditProfileView.as_view(), name="profile.edit"),
path("settings", EditSettingsView.as_view(), name="settings"),
path("changelog", ChangelogView.as_view(), name="changelog"),
path("blog", IndexBlogView.as_view(), name="blog.index"),
path("privacy", PrivacyView.as_view(), name="privacy"),
path("terms", TermsView.as_view(), name="terms"),
path("user/<str:username>", ProfileView.as_view(), name="profile"),

View file

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 18:45+0000\n"
"POT-Creation-Date: 2024-03-27 12:13+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: m5ka <m5ka@posteo.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,11 +15,11 @@ msgstr ""
msgid "english"
msgstr "lami english"
#: moku/config/settings.py:144
#: moku/config/settings.py:143
msgid "lami lioa"
msgstr "lami lioa"
#: moku/config/settings.py:145
#: moku/config/settings.py:143
msgid "toki pona"
msgstr "lami tokipona"
@ -154,6 +154,22 @@ msgstr "noli"
msgid "about me"
msgstr "oisa nai"
#: moku/models/blog.py:9
msgid "title"
msgstr ""
#: moku/models/blog.py:11
msgid "the title of the blog post."
msgstr ""
#: moku/models/blog.py:14
msgid "content"
msgstr ""
#: moku/models/blog.py:15
msgid "the content of the blog post. markdown is allowed here."
msgstr ""
#: moku/models/post.py:37 moku/models/recipe.py:24
msgid "unique id"
msgstr "oioa mini"
@ -286,23 +302,24 @@ msgstr "haoa"
msgid "log in"
msgstr "mimi"
#: moku/templates/moku/base.jinja:37 moku/views/user.py:86
#: moku/templates/moku/base.jinja:37 moku/views/user.py:93
msgid "sign up"
msgstr "holo hono"
#: moku/templates/moku/base.jinja:55
#: moku/templates/moku/base.jinja:55 moku/templates/moku/blog.jinja:5
#: moku/views/blog.py:9
msgid "blog"
msgstr "pali oiki"
#: moku/templates/moku/base.jinja:56 moku/views/user.py:38
#: moku/templates/moku/base.jinja:56 moku/views/user.py:45
msgid "settings"
msgstr "oiki laia"
#: moku/templates/moku/base.jinja:57 moku/views/static.py:24
#: moku/templates/moku/base.jinja:57 moku/views/static.py:17
msgid "terms of use"
msgstr "pali koko"
#: moku/templates/moku/base.jinja:58 moku/views/static.py:17
#: moku/templates/moku/base.jinja:58 moku/views/static.py:10
msgid "privacy policy"
msgstr "pali pamo"
@ -310,6 +327,10 @@ msgstr "pali pamo"
msgid "donate"
msgstr "hana kipi"
#: moku/templates/moku/blog.jinja:7
msgid "no blog posts yet..."
msgstr "ka koma iapa..."
#: moku/templates/moku/feed.jinja:52
msgid "post!"
msgstr "si iapa!"
@ -429,18 +450,19 @@ msgstr "ko oioa haino ia iapa ia koka ia '-' ia '_' ia '.'."
#: moku/validators.py:30
#, python-format
msgid "Username must be between %(min_length)d and %(max_length)d characters."
msgstr "ko homa nao na kino %(min_length)d. ko oioi nao na kino %(max_length)d."
msgstr ""
"ko homa nao na kino %(min_length)d. ko oioi nao na kino %(max_length)d."
#: moku/views/auth.py:28
#, python-format
msgid "welcome back, %(username)s!"
msgstr "ka maio li kai %(username)s sa mai!"
#: moku/views/post.py:33
#: moku/views/post.py:28
msgid "you can't add someone else's recipe to your post!"
msgstr "ko lisa sai io iapa ia haiki na haino oiki mo kai!"
#: moku/views/post.py:40
#: moku/views/post.py:35
msgid "your post was made!"
msgstr "ka haika ni iapa sa hali!"
@ -480,30 +502,29 @@ msgstr "ko poli sai io iaoa li haiki hina ni sia sioa sa laka."
msgid "step added successfully!"
msgstr "ka iaio ni sia haiki sa hali!"
#: moku/views/static.py:10
msgid "changelog"
msgstr "pali oiki"
#: moku/views/user.py:20
#: moku/views/user.py:27
msgid "edit profile"
msgstr "oiki na pali haino"
#: moku/views/user.py:26
#: moku/views/user.py:33
msgid "profile updated successfully!"
msgstr "ka sioa ni pali haino sa hali!"
#: moku/views/user.py:46
#: moku/views/user.py:53
msgid "uh oh. i think something went a little bit oopsie."
msgstr "haia. ko kaki lo mia mo mia sa laka nai."
#: moku/views/user.py:49
#: moku/views/user.py:56
msgid "settings updated!"
msgstr "ka sioa ni maoa kai sa hali!"
#: moku/views/user.py:94
#: moku/views/user.py:101
msgid "sorry! someone else got to that username first."
msgstr "ko kami ni oioa hina mo haino oiki sa laka."
#: moku/views/user.py:98
#: moku/views/user.py:105
msgid "that's it! just log in, and you're ready to go."
msgstr "poi! si iapa ni oioa ni oioa pamo li io oima ni lia kai ha."
#~ msgid "changelog"
#~ msgstr "pali oiki"

View file

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 18:45+0000\n"
"POT-Creation-Date: 2024-03-27 12:13+0000\n"
"PO-Revision-Date: 2024-03-26 18:28+0000\n"
"Last-Translator: m5ka <m5ka@posteo.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,11 +15,11 @@ msgstr ""
msgid "english"
msgstr "toki inli"
#: moku/config/settings.py:144
#: moku/config/settings.py:143
msgid "lami lioa"
msgstr "toki liwa"
#: moku/config/settings.py:145
#: moku/config/settings.py:143
msgid "toki pona"
msgstr "toki pona"
@ -154,6 +154,22 @@ msgstr "ma"
msgid "about me"
msgstr "mi sama ni"
#: moku/models/blog.py:9
msgid "title"
msgstr ""
#: moku/models/blog.py:11
msgid "the title of the blog post."
msgstr ""
#: moku/models/blog.py:14
msgid "content"
msgstr ""
#: moku/models/blog.py:15
msgid "the content of the blog post. markdown is allowed here."
msgstr ""
#: moku/models/post.py:37 moku/models/recipe.py:24
msgid "unique id"
msgstr "nimi wan"
@ -290,23 +306,24 @@ msgstr "weka"
msgid "log in"
msgstr "insa"
#: moku/templates/moku/base.jinja:37 moku/views/user.py:86
#: moku/templates/moku/base.jinja:37 moku/views/user.py:93
msgid "sign up"
msgstr "pali e lipu jan"
#: moku/templates/moku/base.jinja:55
#: moku/templates/moku/base.jinja:55 moku/templates/moku/blog.jinja:5
#: moku/views/blog.py:9
msgid "blog"
msgstr "lipu ante"
#: moku/templates/moku/base.jinja:56 moku/views/user.py:38
#: moku/templates/moku/base.jinja:56 moku/views/user.py:45
msgid "settings"
msgstr "ante linluwi"
#: moku/templates/moku/base.jinja:57 moku/views/static.py:24
#: moku/templates/moku/base.jinja:57 moku/views/static.py:17
msgid "terms of use"
msgstr "lipu kepeken"
#: moku/templates/moku/base.jinja:58 moku/views/static.py:17
#: moku/templates/moku/base.jinja:58 moku/views/static.py:10
msgid "privacy policy"
msgstr "lipu len"
@ -314,6 +331,10 @@ msgstr "lipu len"
msgid "donate"
msgstr "pana mani"
#: moku/templates/moku/blog.jinja:7
msgid "no blog posts yet..."
msgstr "sitelen li lon ala..."
#: moku/templates/moku/feed.jinja:52
msgid "post!"
msgstr "o pali!"
@ -446,11 +467,11 @@ msgstr ""
msgid "welcome back, %(username)s!"
msgstr "jan %(username)s o toki!"
#: moku/views/post.py:33
#: moku/views/post.py:28
msgid "you can't add someone else's recipe to your post!"
msgstr "sina ken ala pana e sitelen kepeken nasin pali pi jan ante!"
#: moku/views/post.py:40
#: moku/views/post.py:35
msgid "your post was made!"
msgstr "sitelen sina li lon!"
@ -490,30 +511,29 @@ msgstr "nasin pali ni li ken ala kama suli kin."
msgid "step added successfully!"
msgstr "kipisi pi nasin pali li lon!"
#: moku/views/static.py:10
msgid "changelog"
msgstr "lipu ante"
#: moku/views/user.py:20
#: moku/views/user.py:27
msgid "edit profile"
msgstr "ante pi lipu jan"
#: moku/views/user.py:26
#: moku/views/user.py:33
msgid "profile updated successfully!"
msgstr "lipu jan li ante!"
#: moku/views/user.py:46
#: moku/views/user.py:53
msgid "uh oh. i think something went a little bit oopsie."
msgstr "a! ike li kama."
#: moku/views/user.py:49
#: moku/views/user.py:56
msgid "settings updated!"
msgstr "wile sina li ante!"
#: moku/views/user.py:94
#: moku/views/user.py:101
msgid "sorry! someone else got to that username first."
msgstr "jan ante li jo e nimi ni."
#: moku/views/user.py:98
#: moku/views/user.py:105
msgid "that's it! just log in, and you're ready to go."
msgstr "pona a! o sitelen e nimi sin ni la sina ken lon insa."
#~ msgid "changelog"
#~ msgstr "lipu ante"

52
moku/markdown.py Normal file
View file

@ -0,0 +1,52 @@
from django.apps import apps
from django.conf import settings
from mistune import HTMLRenderer, Markdown
from mistune.plugins.formatting import strikethrough, subscript, superscript
from mistune.plugins.url import url
USERNAME_PATTERN = (
r"@[a-z0-9-_.]{"
+ str(settings.USERNAME_MIN_LENGTH)
+ r","
+ str(settings.USERNAME_MAX_LENGTH)
+ r"}"
)
def _parse_username_link(inline, m, state):
User = apps.get_model("moku", "User")
text = m.group(0)
pos = m.end()
if state.in_link:
inline.process_text(text, state)
return pos
try:
user = User.objects.get(username=text[1:])
except User.DoesNotExist:
inline.process_text(text, state)
return pos
state.append_token(
{
"type": "link",
"children": [{"type": "text", "raw": f"@{user.username}"}],
"attrs": {"url": user.get_absolute_url()},
}
)
return pos
def _username(md):
md.inline.register("username", USERNAME_PATTERN, _parse_username_link)
full_markdown = Markdown(
renderer=HTMLRenderer(),
plugins=[strikethrough, subscript, superscript, url, _username],
)
basic_markdown = Markdown(
renderer=HTMLRenderer(), plugins=[strikethrough, subscript, superscript, url]
)

View file

@ -0,0 +1,26 @@
# Generated by Django 5.0.3 on 2024-03-27 11:35
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('moku', '0007_add_lami_lioa_to_languages'),
]
operations = [
migrations.CreateModel(
name='BlogPost',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(help_text='the title of the blog post.', max_length=128, verbose_name='title')),
('text', models.TextField(help_text='the content of the blog post. markdown is allowed here.', verbose_name='content')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(db_column='created_by_user_id', on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -1,5 +1,6 @@
from moku.models.blog import BlogPost
from moku.models.post import Post
from moku.models.recipe import Recipe, RecipeStep
from moku.models.user import User, UserSettings
__all__ = ["Post", "Recipe", "RecipeStep", "User", "UserSettings"]
__all__ = ["BlogPost", "Post", "Recipe", "RecipeStep", "User", "UserSettings"]

30
moku/models/blog.py Normal file
View file

@ -0,0 +1,30 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from moku.markdown import full_markdown
class BlogPost(models.Model):
title = models.CharField(
verbose_name=_("title"),
max_length=128,
help_text=_("the title of the blog post."),
)
text = models.TextField(
verbose_name=_("content"),
help_text=_("the content of the blog post. markdown is allowed here."),
)
created_by = models.ForeignKey(
"User",
related_name="+",
db_column="created_by_user_id",
on_delete=models.PROTECT,
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def as_html(self):
return full_markdown(self.text)

View file

@ -308,6 +308,32 @@ header nav ul {
row-gap: .8rem;
}
.blog {
display: grid;
row-gap: 2.4rem;
}
.blog > h2 {
font-size: 2.2rem;
font-weight: bold;
}
.blog-post {
display: grid;
row-gap: .4rem;
}
.blog-post > h3 {
font-size: 2rem;
font-weight: bold;
}
.blog-post .post-metadata {
font-size: 1.4rem;
color: var(--charcoal);
margin-block-end: 1.2rem;
}
.grid-content {
display: grid;
row-gap: 1.2rem;

View file

@ -52,7 +52,7 @@
<footer>
<nav>
<ul>
<li><a href="{{ url('changelog') }}">{% trans %}blog{% endtrans %}</a></li>
<li><a href="{{ url('blog.index') }}">{% trans %}blog{% endtrans %}</a></li>
{% if request.user.is_authenticated %}<li><a href="{{ url('settings') }}">{% trans %}settings{% endtrans %}</a></li>{% endif %}
<li><a href="{{ url('terms') }}">{% trans %}terms of use{% endtrans %}</a></li>
<li><a href="{{ url('privacy') }}">{% trans %}privacy policy{% endtrans %}</a></li>

View file

@ -0,0 +1,19 @@
{% extends "moku/base.jinja" %}
{% block content %}
<div class="content blog">
<h2>{% trans %}blog{% endtrans %} 📜</h2>
{% if not posts %}
<p>{% trans %}no blog posts yet...{% endtrans %} 🥱</p>
{% endif %}
{% for post in posts %}
<article class="blog-post">
<h3>{{ post.title }}</h3>
<span class="post-metadata">{{ post.created_at|naturaltime }} · <a href="{{ post.created_by.get_absolute_url() }}">@{{ post.created_by.username }}</a></span>
<div class="block">
{{ post.as_html()|safe }}
</div>
</article>
{% endfor %}
</div>
{% endblock content %}

View file

@ -1,11 +0,0 @@
{% extends "moku/base.jinja" %}
{% block content %}
<div class="content block">
<h2>changelog 📜</h2>
<h3>2024-03-25</h3>
<ul>
<li>moku.blog launched! 🎉</li>
</ul>
</div>
{% endblock content %}

15
moku/views/blog.py Normal file
View file

@ -0,0 +1,15 @@
from django.utils.translation import gettext_lazy
from moku.models.blog import BlogPost
from moku.views.base import View
class IndexBlogView(View):
template_name = "moku/blog.jinja"
page_title = gettext_lazy("blog")
def get_context_data(self, **kwargs):
return {
**super().get_context_data(**kwargs),
"posts": BlogPost.objects.order_by("-created_at").all(),
}

View file

@ -3,13 +3,6 @@ from django.utils.translation import gettext_lazy
from moku.views.base import View
class ChangelogView(View):
"""Displays the static changelog page."""
template_name = "moku/changelog.jinja"
page_title = gettext_lazy("changelog")
class PrivacyView(View):
"""Displays the static privacy policy page."""

21
poetry.lock generated
View file

@ -107,13 +107,13 @@ django = "*"
[[package]]
name = "emoji"
version = "2.10.1"
version = "2.11.0"
description = "Emoji for Python"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
files = [
{file = "emoji-2.10.1-py2.py3-none-any.whl", hash = "sha256:11fb369ea79d20c14efa4362c732d67126df294a7959a2c98bfd7447c12a218e"},
{file = "emoji-2.10.1.tar.gz", hash = "sha256:16287283518fb7141bde00198f9ffff4e1c1cb570efb68b2f1ec50975c3a581d"},
{file = "emoji-2.11.0-py2.py3-none-any.whl", hash = "sha256:63fc9107f06c6c2e48e5078ce9575cef98518f5ac09474f6148a43e989989582"},
{file = "emoji-2.11.0.tar.gz", hash = "sha256:772eaa30f4e0b1ce95148a092df4c7dc97644532c03225326b0fd05e8a9f72a3"},
]
[package.extras]
@ -236,6 +236,17 @@ files = [
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
name = "mistune"
version = "3.0.2"
description = "A sane and fast Markdown parser with useful plugins and renderers"
optional = false
python-versions = ">=3.7"
files = [
{file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"},
{file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"},
]
[[package]]
name = "packaging"
version = "24.0"
@ -474,4 +485,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "a2d30838ebf562b336e3c1b80401aadbfd231979f3619645d94a45484bc04f40"
content-hash = "e759f693fe3fafe547634271da1f0ba2c3de523581edd40dd78299cf7fb97c00"

View file

@ -14,6 +14,7 @@ django-jinja = "^2.11.0"
django-recaptcha = "^4.0.0"
emoji = "^2.10.1"
gunicorn = "^21.2.0"
mistune = "^3.0.2"
pillow = "^10.2.0"
psycopg2 = "^2.9.9"
python = "^3.12"