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

View file

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: m5ka <m5ka@posteo.de>\n" "Last-Translator: m5ka <m5ka@posteo.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,11 +15,11 @@ msgstr ""
msgid "english" msgid "english"
msgstr "lami english" msgstr "lami english"
#: moku/config/settings.py:144 #: moku/config/settings.py:143
msgid "lami lioa" msgid "lami lioa"
msgstr "lami lioa" msgstr "lami lioa"
#: moku/config/settings.py:145 #: moku/config/settings.py:143
msgid "toki pona" msgid "toki pona"
msgstr "lami tokipona" msgstr "lami tokipona"
@ -154,6 +154,22 @@ msgstr "noli"
msgid "about me" msgid "about me"
msgstr "oisa nai" 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 #: moku/models/post.py:37 moku/models/recipe.py:24
msgid "unique id" msgid "unique id"
msgstr "oioa mini" msgstr "oioa mini"
@ -286,23 +302,24 @@ msgstr "haoa"
msgid "log in" msgid "log in"
msgstr "mimi" 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" msgid "sign up"
msgstr "holo hono" 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" msgid "blog"
msgstr "pali oiki" 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" msgid "settings"
msgstr "oiki laia" 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" msgid "terms of use"
msgstr "pali koko" 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" msgid "privacy policy"
msgstr "pali pamo" msgstr "pali pamo"
@ -310,6 +327,10 @@ msgstr "pali pamo"
msgid "donate" msgid "donate"
msgstr "hana kipi" msgstr "hana kipi"
#: moku/templates/moku/blog.jinja:7
msgid "no blog posts yet..."
msgstr "ka koma iapa..."
#: moku/templates/moku/feed.jinja:52 #: moku/templates/moku/feed.jinja:52
msgid "post!" msgid "post!"
msgstr "si iapa!" msgstr "si iapa!"
@ -429,18 +450,19 @@ msgstr "ko oioa haino ia iapa ia koka ia '-' ia '_' ia '.'."
#: moku/validators.py:30 #: moku/validators.py:30
#, python-format #, python-format
msgid "Username must be between %(min_length)d and %(max_length)d characters." 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 #: moku/views/auth.py:28
#, python-format #, python-format
msgid "welcome back, %(username)s!" msgid "welcome back, %(username)s!"
msgstr "ka maio li kai %(username)s sa mai!" 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!" 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!" 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!" msgid "your post was made!"
msgstr "ka haika ni iapa sa hali!" 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!" msgid "step added successfully!"
msgstr "ka iaio ni sia haiki sa hali!" msgstr "ka iaio ni sia haiki sa hali!"
#: moku/views/static.py:10 #: moku/views/user.py:27
msgid "changelog"
msgstr "pali oiki"
#: moku/views/user.py:20
msgid "edit profile" msgid "edit profile"
msgstr "oiki na pali haino" msgstr "oiki na pali haino"
#: moku/views/user.py:26 #: moku/views/user.py:33
msgid "profile updated successfully!" msgid "profile updated successfully!"
msgstr "ka sioa ni pali haino sa hali!" 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." msgid "uh oh. i think something went a little bit oopsie."
msgstr "haia. ko kaki lo mia mo mia sa laka nai." msgstr "haia. ko kaki lo mia mo mia sa laka nai."
#: moku/views/user.py:49 #: moku/views/user.py:56
msgid "settings updated!" msgid "settings updated!"
msgstr "ka sioa ni maoa kai sa hali!" 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." msgid "sorry! someone else got to that username first."
msgstr "ko kami ni oioa hina mo haino oiki sa laka." 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." 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." 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 "" msgstr ""
"Project-Id-Version: 0.1.0\n" "Project-Id-Version: 0.1.0\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2024-03-26 18:28+0000\n"
"Last-Translator: m5ka <m5ka@posteo.de>\n" "Last-Translator: m5ka <m5ka@posteo.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,11 +15,11 @@ msgstr ""
msgid "english" msgid "english"
msgstr "toki inli" msgstr "toki inli"
#: moku/config/settings.py:144 #: moku/config/settings.py:143
msgid "lami lioa" msgid "lami lioa"
msgstr "toki liwa" msgstr "toki liwa"
#: moku/config/settings.py:145 #: moku/config/settings.py:143
msgid "toki pona" msgid "toki pona"
msgstr "toki pona" msgstr "toki pona"
@ -154,6 +154,22 @@ msgstr "ma"
msgid "about me" msgid "about me"
msgstr "mi sama ni" 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 #: moku/models/post.py:37 moku/models/recipe.py:24
msgid "unique id" msgid "unique id"
msgstr "nimi wan" msgstr "nimi wan"
@ -290,23 +306,24 @@ msgstr "weka"
msgid "log in" msgid "log in"
msgstr "insa" 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" msgid "sign up"
msgstr "pali e lipu jan" 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" msgid "blog"
msgstr "lipu ante" 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" msgid "settings"
msgstr "ante linluwi" 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" msgid "terms of use"
msgstr "lipu kepeken" 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" msgid "privacy policy"
msgstr "lipu len" msgstr "lipu len"
@ -314,6 +331,10 @@ msgstr "lipu len"
msgid "donate" msgid "donate"
msgstr "pana mani" 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 #: moku/templates/moku/feed.jinja:52
msgid "post!" msgid "post!"
msgstr "o pali!" msgstr "o pali!"
@ -446,11 +467,11 @@ msgstr ""
msgid "welcome back, %(username)s!" msgid "welcome back, %(username)s!"
msgstr "jan %(username)s o toki!" 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!" 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!" 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!" msgid "your post was made!"
msgstr "sitelen sina li lon!" msgstr "sitelen sina li lon!"
@ -490,30 +511,29 @@ msgstr "nasin pali ni li ken ala kama suli kin."
msgid "step added successfully!" msgid "step added successfully!"
msgstr "kipisi pi nasin pali li lon!" msgstr "kipisi pi nasin pali li lon!"
#: moku/views/static.py:10 #: moku/views/user.py:27
msgid "changelog"
msgstr "lipu ante"
#: moku/views/user.py:20
msgid "edit profile" msgid "edit profile"
msgstr "ante pi lipu jan" msgstr "ante pi lipu jan"
#: moku/views/user.py:26 #: moku/views/user.py:33
msgid "profile updated successfully!" msgid "profile updated successfully!"
msgstr "lipu jan li ante!" 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." msgid "uh oh. i think something went a little bit oopsie."
msgstr "a! ike li kama." msgstr "a! ike li kama."
#: moku/views/user.py:49 #: moku/views/user.py:56
msgid "settings updated!" msgid "settings updated!"
msgstr "wile sina li ante!" msgstr "wile sina li ante!"
#: moku/views/user.py:94 #: moku/views/user.py:101
msgid "sorry! someone else got to that username first." msgid "sorry! someone else got to that username first."
msgstr "jan ante li jo e nimi ni." 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." 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." 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.post import Post
from moku.models.recipe import Recipe, RecipeStep from moku.models.recipe import Recipe, RecipeStep
from moku.models.user import User, UserSettings 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; 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 { .grid-content {
display: grid; display: grid;
row-gap: 1.2rem; row-gap: 1.2rem;

View file

@ -52,7 +52,7 @@
<footer> <footer>
<nav> <nav>
<ul> <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 %} {% 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('terms') }}">{% trans %}terms of use{% endtrans %}</a></li>
<li><a href="{{ url('privacy') }}">{% trans %}privacy policy{% 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 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): class PrivacyView(View):
"""Displays the static privacy policy page.""" """Displays the static privacy policy page."""

21
poetry.lock generated
View file

@ -107,13 +107,13 @@ django = "*"
[[package]] [[package]]
name = "emoji" name = "emoji"
version = "2.10.1" version = "2.11.0"
description = "Emoji for Python" description = "Emoji for Python"
optional = false 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 = [ files = [
{file = "emoji-2.10.1-py2.py3-none-any.whl", hash = "sha256:11fb369ea79d20c14efa4362c732d67126df294a7959a2c98bfd7447c12a218e"}, {file = "emoji-2.11.0-py2.py3-none-any.whl", hash = "sha256:63fc9107f06c6c2e48e5078ce9575cef98518f5ac09474f6148a43e989989582"},
{file = "emoji-2.10.1.tar.gz", hash = "sha256:16287283518fb7141bde00198f9ffff4e1c1cb570efb68b2f1ec50975c3a581d"}, {file = "emoji-2.11.0.tar.gz", hash = "sha256:772eaa30f4e0b1ce95148a092df4c7dc97644532c03225326b0fd05e8a9f72a3"},
] ]
[package.extras] [package.extras]
@ -236,6 +236,17 @@ files = [
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, {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]] [[package]]
name = "packaging" name = "packaging"
version = "24.0" version = "24.0"
@ -474,4 +485,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" 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" django-recaptcha = "^4.0.0"
emoji = "^2.10.1" emoji = "^2.10.1"
gunicorn = "^21.2.0" gunicorn = "^21.2.0"
mistune = "^3.0.2"
pillow = "^10.2.0" pillow = "^10.2.0"
psycopg2 = "^2.9.9" psycopg2 = "^2.9.9"
python = "^3.12" python = "^3.12"