initial commit
This commit is contained in:
commit
c9f8c48664
38 changed files with 2017 additions and 0 deletions
24
.env.example
Normal file
24
.env.example
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
DEBUG=true
|
||||
DATABASE_URL="postgres://localhost:5432/moku"
|
||||
SECRET_KEY="moku-keyboard-cat"
|
||||
RECAPTCHA_PUBLIC_KEY=""
|
||||
RECAPTCHA_PRIVATE_KEY=""
|
||||
|
||||
# The following are defaults.
|
||||
# Uncomment and set a value to use a different value.
|
||||
# DEBUG_TOOLBAR=true
|
||||
# ALLOWED_HOSTS=host.example.com,host.example.org
|
||||
# CSRF_TRUSTED_ORIGINS=
|
||||
# STATIC_URL="static/"
|
||||
# MEDIA_URL="media/"
|
||||
# EMAIL_FROM='"Sender" <sender@example.com>'
|
||||
# EMAIL_HOST="localhost"
|
||||
# EMAIL_PORT=25
|
||||
# EMAIL_HOST_USER=""
|
||||
# EMAIL_HOST_PASSWORD=""
|
||||
# EMAIL_USE_TLS=false
|
||||
# EMAIL_USE_SSL=false
|
||||
# EMAIL_TIMEOUT=3
|
||||
# USERNAME_MIN_LENGTH=3
|
||||
# USERNAME_MAX_LENGTH=36
|
||||
# SITE_ROOT_URL="https://moku.blog"
|
||||
78
.gitignore
vendored
Normal file
78
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
*.manifest
|
||||
*.spec
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
*.mo
|
||||
*.pot
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
instance/
|
||||
.webassets-cache
|
||||
.scrapy
|
||||
docs/_build/
|
||||
.pybuilder/
|
||||
target/
|
||||
.ipynb_checkpoints
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
.python-version
|
||||
.pdm.toml
|
||||
__pypackages__/
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
*.sage.py
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
.spyderproject
|
||||
.spyproject
|
||||
.ropeproject
|
||||
/site
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
.pyre/
|
||||
.pytype/
|
||||
cython_debug/
|
||||
.idea/
|
||||
23
manage.py
Executable file
23
manage.py
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "moku.config.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
moku/__init__.py
Normal file
1
moku/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
default_app_config = "moku.config.apps.MokuConfig"
|
||||
0
moku/config/__init__.py
Normal file
0
moku/config/__init__.py
Normal file
7
moku/config/apps.py
Normal file
7
moku/config/apps.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MokuConfig(AppConfig):
|
||||
name = "moku"
|
||||
label = "moku"
|
||||
verbose_name = "moku.blog"
|
||||
16
moku/config/asgi.py
Normal file
16
moku/config/asgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
ASGI config for moku project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "moku.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
179
moku/config/settings.py
Normal file
179
moku/config/settings.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import environ
|
||||
|
||||
# Initial environment
|
||||
env = environ.Env()
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
env.read_env(BASE_DIR / ".env")
|
||||
|
||||
# Debug
|
||||
DEBUG = env.bool("DEBUG", default=False)
|
||||
|
||||
# Secret key
|
||||
SECRET_KEY = env.str("SECRET_KEY", default="insecure-keyboard-cat-abcd1234")
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django_jinja",
|
||||
"django_jinja.contrib._humanize",
|
||||
"django_recaptcha",
|
||||
"moku",
|
||||
]
|
||||
|
||||
# Middleware
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
# Debug toolbar
|
||||
# https://django-debug-toolbar.readthedocs.io/en/latest/
|
||||
if DEBUG and env.bool("DEBUG_TOOLBAR", default=True):
|
||||
try:
|
||||
import debug_toolbar # noqa: F401
|
||||
|
||||
INSTALLED_APPS += ["debug_toolbar"]
|
||||
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
|
||||
DEBUG_TOOLBAR = True
|
||||
except ImportError:
|
||||
DEBUG_TOOLBAR = False
|
||||
else:
|
||||
DEBUG_TOOLBAR = False
|
||||
|
||||
ROOT_URLCONF = "moku.config.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django_jinja.backend.Jinja2",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
"filters": {
|
||||
"unemoji": "moku.utils.unemoji",
|
||||
},
|
||||
"policies": {"ext.i18n.trimmed": True},
|
||||
},
|
||||
},
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
]
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "moku.config.wsgi.application"
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||
DATABASES = {
|
||||
"default": env.db(
|
||||
"DATABASE_URL", engine="postgres", default="postgres://localhost:5432/moku"
|
||||
)
|
||||
}
|
||||
|
||||
# Authentication models
|
||||
# https://docs.djangoproject.com/en/5.0/topics/auth/customizing/
|
||||
AUTH_USER_MODEL = "moku.User"
|
||||
LOGIN_URL = "login"
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": (
|
||||
"django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
|
||||
)
|
||||
},
|
||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||
]
|
||||
|
||||
# Internal IP addresses
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
INTERNAL_IPS = ["localhost", "127.0.0.1"]
|
||||
|
||||
# Allowed hosts
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", cast=str, default=[])
|
||||
|
||||
# CSRF trusted origins
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#csrf-trusted-origins
|
||||
CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", cast=str, default=[])
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||
LANGUAGE_CODE = "en-gb"
|
||||
TIME_ZONE = "UTC"
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||
STATIC_URL = env.str("STATIC_URL", default="static/")
|
||||
STATIC_ROOT = BASE_DIR / "moku/static"
|
||||
|
||||
# User-uploaded content
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#media-root
|
||||
MEDIA_URL = env.str("MEDIA_URL", default="media/")
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
# Email settings
|
||||
# https://docs.djangoproject.com/en/5.0/topics/email/
|
||||
EMAIL_FROM = env.str("EMAIL_FROM", default='"Sender" <sender@example.com>')
|
||||
EMAIL_HOST = env.str("EMAIL_HOST", default="localhost")
|
||||
EMAIL_PORT = env.int("EMAIL_PORT", default=25)
|
||||
EMAIL_HOST_USER = env.str("EMAIL_HOST_USER", default="")
|
||||
EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD", default="")
|
||||
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", default=False)
|
||||
EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", default=False)
|
||||
EMAIL_TIMEOUT = env.int("EMAIL_TIMEOUT", default=3)
|
||||
|
||||
# Username length
|
||||
USERNAME_MIN_LENGTH = 3
|
||||
USERNAME_MAX_LENGTH = 24
|
||||
|
||||
# URL configuration
|
||||
SITE_ROOT_URL = env.str("SITE_ROOT_URL", default="https://moku.blog")
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# Recaptcha settings
|
||||
# https://pypi.org/project/django-recaptcha/#installation
|
||||
if DEBUG:
|
||||
SILENCED_SYSTEM_CHECKS = ["django_recaptcha.recaptcha_test_key_error"]
|
||||
else:
|
||||
RECAPTCHA_PUBLIC_KEY = env.str("RECAPTCHA_PUBLIC_KEY")
|
||||
RECAPTCHA_PRIVATE_KEY = env.str("RECAPTCHA_PRIVATE_KEY")
|
||||
40
moku/config/urls.py
Normal file
40
moku/config/urls.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
URL configuration for moku project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
from moku.views.auth import LoginView, LogoutView
|
||||
from moku.views.post import FeedView
|
||||
from moku.views.user import EditProfileView, ProfileView, SignupView
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("", FeedView.as_view(), name="feed"),
|
||||
path("login", LoginView.as_view(), name="login"),
|
||||
path("logout", LogoutView.as_view(), name="logout"),
|
||||
path("signup", SignupView.as_view(), name="signup"),
|
||||
path("profile", EditProfileView.as_view(), name="profile.edit"),
|
||||
path("user/<str:username>", ProfileView.as_view(), name="profile"),
|
||||
]
|
||||
|
||||
if settings.DEBUG_TOOLBAR:
|
||||
urlpatterns += [path("__debug__/", include("debug_toolbar.urls"))]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
16
moku/config/wsgi.py
Normal file
16
moku/config/wsgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for moku project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "moku.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
72
moku/constants.py
Normal file
72
moku/constants.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class Verbs:
|
||||
ATE = "ate"
|
||||
MADE = "made"
|
||||
COOKED = "cooked"
|
||||
BAKED = "baked"
|
||||
ORDERED = "ordered"
|
||||
CHOICES = (
|
||||
(ATE, _("%(user)s ate %(food)s")),
|
||||
(MADE, _("%(user)s made %(food)s")),
|
||||
(COOKED, _("%(user)s cooked %(food)s")),
|
||||
(BAKED, _("%(user)s baked %(food)s")),
|
||||
(ORDERED, _("%(user)s ordered %(food)s")),
|
||||
)
|
||||
|
||||
|
||||
EMOJI_CATEGORIES = [
|
||||
(
|
||||
_("fruit & veg"),
|
||||
(
|
||||
"🍏", "🍎", "🍐", "🍊", "🍋", "🍋🟩", "🍌", "🍉", "🍇", "🍓", "🫐", "🍈", "🍒", "🍑", "🥭",
|
||||
"🍍", "🥥", "🥝", "🍅", "🍆", "🥑", "🫛", "🥦", "🥬", "🥒", "🌶️", "🫑", "🌽", "🥕", "🫒",
|
||||
"🧄", "🧅", "🥔", "🍠", "🫚",
|
||||
),
|
||||
),
|
||||
(
|
||||
_("savoury dishes"),
|
||||
(
|
||||
"🥯", "🍞", "🥖", "🥨", "🧀", "🥚", "🍳", "🧈", "🥞", "🧇", "🥓", "🥩", "🍗", "🍖", "🦴",
|
||||
"🌭", "🍔", "🍟", "🍕", "🫓", "🥪", "🥙", "🧆", "🌮", "🌯", "🫔", "🥗", "🥘", "🫕", "🥫",
|
||||
"🫙", "🍝", "🍜", "🍲", "🍛", "🍣", "🍱", "🥟", "🦪", "🍤", "🍙", "🍚", "🍘", "🍥", "🌰",
|
||||
"🥜", "🫘", "🧊",
|
||||
),
|
||||
),
|
||||
(
|
||||
_("sweet treats"),
|
||||
(
|
||||
"🥠", "🥮", "🍢", "🍡", "🍧", "🍨", "🍦", "🥧", "🧁", "🍰", "🎂", "🍮", "🍭", "🍬", "🍫",
|
||||
"🥐", "🍿", "🍩", "🍪", "🍯",
|
||||
),
|
||||
),
|
||||
(
|
||||
_("drinks"),
|
||||
(
|
||||
"🥛", "🫗", "🍼", "🫖", "☕️", "🍵", "🧃", "🥤", "🧋", "🍶", "🍺", "🍻", "🥂", "🍷", "🥃",
|
||||
"🍸", "🍹", "🧉", "🍾",
|
||||
),
|
||||
),
|
||||
(
|
||||
_("people"),
|
||||
(
|
||||
"😀", "😃", "😄", "😁", "😆", "🥹", "😅", "😂", "🤣", "🥲", "😊", "😇", "🙂", "🙃", "😉",
|
||||
"😌", "😌", "😍", "🥰", "😘", "😗", "😙", "😚", "😋", "😛", "😝", "😜", "🤪", "🤨", "🧐",
|
||||
),
|
||||
),
|
||||
(
|
||||
_("animals"),
|
||||
(
|
||||
"🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼", "🐨", "🐯", "🦁", "🐮", "🐷", "🐽", "🐸",
|
||||
"🐵", "🙈", "🙉", "🙊", "🐒", "🐔", "🐧", "🐦", "🐤", "🐣", "🐥", "🪿", "🦆", "🐦⬛", "🦅",
|
||||
"🦉", "🦇", "🐺", "🐗", "🐴", "🦄", "🫎", "🐝", "🪱", "🐛", "🦋", "🐌", "🐞", "🐜", "🪰",
|
||||
),
|
||||
),
|
||||
(
|
||||
_("tools & things"),
|
||||
(
|
||||
"🥄", "🍴", "🍽️", "🥣", "🥡", "🥢", "🧂", "🔪", "🪓",
|
||||
)
|
||||
)
|
||||
]
|
||||
0
moku/forms/__init__.py
Normal file
0
moku/forms/__init__.py
Normal file
16
moku/forms/post.py
Normal file
16
moku/forms/post.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from django.forms import ModelForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from moku.models import Post
|
||||
|
||||
|
||||
class PostForm(ModelForm):
|
||||
class Meta:
|
||||
model = Post
|
||||
fields = ("emoji", "verb", "food", "image")
|
||||
labels = {
|
||||
"emoji": _("emoji"),
|
||||
"verb": _("verb"),
|
||||
"food": _("food"),
|
||||
"image": _("image"),
|
||||
}
|
||||
51
moku/forms/user.py
Normal file
51
moku/forms/user.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
from django import forms
|
||||
from django.contrib.auth import password_validation
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_recaptcha.fields import ReCaptchaField
|
||||
|
||||
from moku.models.user import User
|
||||
|
||||
|
||||
class UserForm(UserCreationForm):
|
||||
password1 = forms.CharField(
|
||||
label=_("password"),
|
||||
strip=False,
|
||||
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
|
||||
help_text=_("make a secure password that you've never used before!"),
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label=_("password (again)"),
|
||||
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
|
||||
strip=False,
|
||||
help_text=_("just type the password again to confirm."),
|
||||
)
|
||||
captcha = ReCaptchaField(required=True)
|
||||
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = User
|
||||
fields = ("username", "email")
|
||||
labels = {
|
||||
"username": _("username"),
|
||||
"email": _("email address"),
|
||||
}
|
||||
help_texts = {
|
||||
"username": User._meta.get_field("username").help_text,
|
||||
"email": User._meta.get_field("email").help_text,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["captcha"].error_messages = {"required": _("make sure you've ticked the captcha.")}
|
||||
|
||||
|
||||
class ProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("pronouns", "location", "bio")
|
||||
labels = {
|
||||
"pronouns": _("pronouns"),
|
||||
"location": _("location"),
|
||||
"bio": _("about me"),
|
||||
}
|
||||
49
moku/migrations/0001_initial.py
Normal file
49
moku/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Generated by Django 5.0.3 on 2024-03-24 17:24
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.core.validators
|
||||
import django.utils.timezone
|
||||
import moku.validators
|
||||
import re
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('username', models.CharField(db_index=True, help_text="this is the unique identifier you'll use to log in. it may only contain letters, numbers, hyphens, dashes and dots.", max_length=64, unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[a-zA-Z0-9-_.]+\\Z'), 'Username may only contain letters, numbers, hyphens, underscores and dots.', 'invalid'), moku.validators.validate_username_length], verbose_name='username')),
|
||||
('email', models.EmailField(help_text="this should be your email address. make sure it's valid and that you have access to it.", max_length=128, unique=True, verbose_name='email address')),
|
||||
('email_confirmed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('pronouns', models.CharField(blank=True, help_text='what pronouns should people use when referring to you?', max_length=64, verbose_name='pronouns')),
|
||||
('location', models.CharField(blank=True, help_text='where in the world are you?', max_length=64, verbose_name='location')),
|
||||
('bio', models.TextField(blank=True, help_text='write something about yourself!', verbose_name='about me')),
|
||||
('last_seen_at', models.DateTimeField(blank=True, help_text='the last time this user accessed the site.', null=True, verbose_name='last seen at')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
'abstract': False,
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
32
moku/migrations/0002_post.py
Normal file
32
moku/migrations/0002_post.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Generated by Django 5.0.3 on 2024-03-25 10:04
|
||||
|
||||
import django.db.models.deletion
|
||||
import moku.models.post
|
||||
import moku.validators
|
||||
import shortuuid.django_fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('moku', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Post',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', shortuuid.django_fields.ShortUUIDField(alphabet=None, help_text='the unique id that identifies this post.', length=22, max_length=22, prefix='', verbose_name='unique id')),
|
||||
('emoji', models.CharField(help_text='an emoji to accompany your post!', max_length=8, validators=[moku.validators.validate_emoji], verbose_name='emoji')),
|
||||
('verb', models.CharField(choices=[('ate', '%(user)s ate %(food)s'), ('made', '%(user)s made %(food)s'), ('cooked', '%(user)s cooked %(food)s'), ('baked', '%(user)s baked %(food)s'), ('ordered', '%(user)s ordered %(food)s')], help_text='how should we best phrase this entry?', max_length=32, verbose_name='verb')),
|
||||
('food', models.CharField(help_text='what did you eat?', max_length=128, verbose_name='food')),
|
||||
('image', models.ImageField(blank=True, help_text='here you can upload a picture of what you ate!', upload_to=moku.models.post.post_image_filename, verbose_name='image')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='when this post was created.')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='when this post was last updated.')),
|
||||
('created_by', models.ForeignKey(db_column='created_by_user_id', on_delete=django.db.models.deletion.CASCADE, related_name='posts', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
moku/migrations/__init__.py
Normal file
0
moku/migrations/__init__.py
Normal file
8
moku/models/__init__.py
Normal file
8
moku/models/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from moku.models.post import Post
|
||||
from moku.models.user import User
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Post",
|
||||
"User",
|
||||
]
|
||||
76
moku/models/post.py
Normal file
76
moku/models/post.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
from django.db import models
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from shortuuid.django_fields import ShortUUIDField
|
||||
|
||||
from moku.constants import Verbs
|
||||
from moku.utils import process_image
|
||||
from moku.validators import validate_emoji
|
||||
|
||||
|
||||
def post_image_filename(instance, filename):
|
||||
fn = filename.split(".")
|
||||
ext = "png" if len(fn) < 2 else fn[-1]
|
||||
return f"posts/{instance.created_by.username}__{instance.uuid}.{ext}"
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
uuid = ShortUUIDField(
|
||||
verbose_name=_("unique id"),
|
||||
max_length=22,
|
||||
length=22,
|
||||
help_text=_("the unique id that identifies this post."),
|
||||
)
|
||||
emoji = models.CharField(
|
||||
verbose_name=_("emoji"),
|
||||
max_length=8,
|
||||
validators=[validate_emoji],
|
||||
help_text=_("an emoji to accompany your post!"),
|
||||
)
|
||||
verb = models.CharField(
|
||||
verbose_name=_("verb"),
|
||||
max_length=32,
|
||||
choices=Verbs.CHOICES,
|
||||
help_text=_("how should we best phrase this entry?"),
|
||||
)
|
||||
food = models.CharField(
|
||||
verbose_name=_("food"),
|
||||
max_length=128,
|
||||
help_text=_("what did you eat?"),
|
||||
)
|
||||
image = models.ImageField(
|
||||
verbose_name=_("image"),
|
||||
blank=True,
|
||||
upload_to=post_image_filename,
|
||||
help_text=_("here you can upload a picture of what you ate!"),
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
"User",
|
||||
related_name="posts",
|
||||
db_index=True,
|
||||
db_column="created_by_user_id",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
created_at = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
help_text=_("when this post was created."),
|
||||
)
|
||||
updated_at = models.DateTimeField(
|
||||
auto_now=True,
|
||||
help_text=_("when this post was last updated."),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.uuid
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.id and self.image:
|
||||
self.image = process_image(self.image)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.get_verb_display() % {
|
||||
"user": f"<a href=\"{self.created_by.get_absolute_url()}\">@{self.created_by.username}</a>",
|
||||
"food": escape(self.food),
|
||||
}
|
||||
69
moku/models/user.py
Normal file
69
moku/models/user.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse
|
||||
|
||||
from moku.validators import validate_username_regex, validate_username_length
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
username = models.CharField(
|
||||
verbose_name=_("username"),
|
||||
max_length=64,
|
||||
unique=True,
|
||||
db_index=True,
|
||||
validators=[validate_username_regex, validate_username_length],
|
||||
help_text=_(
|
||||
"this is the unique identifier you'll use to log in. it may only contain "
|
||||
"letters, numbers, hyphens, dashes and dots."
|
||||
)
|
||||
)
|
||||
email = models.EmailField(
|
||||
verbose_name=_("email address"),
|
||||
max_length=128,
|
||||
unique=True,
|
||||
help_text=_(
|
||||
"this should be your email address. make sure it's valid and that you have "
|
||||
"access to it."
|
||||
)
|
||||
)
|
||||
email_confirmed_at = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
pronouns = models.CharField(
|
||||
verbose_name=_("pronouns"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
help_text=_("what pronouns should people use when referring to you?"),
|
||||
)
|
||||
location = models.CharField(
|
||||
verbose_name=_("location"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
help_text=_("where in the world are you?"),
|
||||
)
|
||||
bio = models.TextField(
|
||||
verbose_name=_("about me"),
|
||||
blank=True,
|
||||
help_text=_("write something about yourself!"),
|
||||
)
|
||||
last_seen_at = models.DateTimeField(
|
||||
verbose_name=_("last seen at"),
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("the last time this user accessed the site."),
|
||||
)
|
||||
|
||||
first_name = None
|
||||
last_name = None
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("profile", kwargs={"username": self.username})
|
||||
|
||||
@property
|
||||
def email_confirmed(self):
|
||||
return self.email_confirmed_at is not None
|
||||
298
moku/static/css/moku.css
Normal file
298
moku/static/css/moku.css
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
:root {
|
||||
--tangerine: #F7A278;
|
||||
--orange: #c94c10;
|
||||
--champagne: #FDE4D8;
|
||||
--dusty-champagne: #EFC6B8;
|
||||
--charcoal: #626262;
|
||||
|
||||
--emoji-picker-rounding: 6px;
|
||||
--image-max-size: 324px;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
line-height: 1.5;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--champagne);
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.4;
|
||||
max-width: 768px;
|
||||
margin-inline: auto;
|
||||
padding: 4rem 1.2rem 2.4rem 1.2rem;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.mt {
|
||||
margin-block-start: 1.6rem;
|
||||
}
|
||||
|
||||
.mb {
|
||||
margin-block-end: 1.6rem;
|
||||
}
|
||||
|
||||
button.logout {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
display: inline;
|
||||
font-size: 1.6rem;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a, button.logout {
|
||||
color: var(--orange);
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-decoration-skip-ink: none;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
a:hover, button.logout:hover {
|
||||
text-decoration: wavy underline;
|
||||
}
|
||||
|
||||
.profile h2 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
margin-block-end: 1.2rem;
|
||||
color: var(--orange);
|
||||
}
|
||||
|
||||
dl {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
row-gap: 1.2rem;
|
||||
column-gap: 1.8rem;
|
||||
}
|
||||
|
||||
dl > div {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
font-size: 1.4rem;
|
||||
font-weight: bold;
|
||||
color: var(--charcoal);
|
||||
}
|
||||
|
||||
dl > .double {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
form .errors {
|
||||
font-size: 1.5rem;
|
||||
display: grid;
|
||||
margin-block-end: .8rem;
|
||||
}
|
||||
|
||||
form .errors::before {
|
||||
content: "fix these and we're good to go:";
|
||||
font-size: 1.4rem;
|
||||
color: var(--charcoal);
|
||||
margin-block-end: .4rem;
|
||||
}
|
||||
|
||||
form .errors li::before {
|
||||
content: "❌ ";
|
||||
}
|
||||
|
||||
form .errors li {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
form:not(.logout) {
|
||||
display: grid;
|
||||
row-gap: .8rem;
|
||||
}
|
||||
|
||||
form.auth {
|
||||
max-width: 378px;
|
||||
}
|
||||
|
||||
form .field {
|
||||
display: grid;
|
||||
row-gap: .4rem;
|
||||
}
|
||||
|
||||
form .field .help {
|
||||
color: var(--charcoal);
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
form input,
|
||||
form select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form textarea {
|
||||
height: 6ch;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
form input[type=text],
|
||||
form input[type=password],
|
||||
form input[type=email],
|
||||
form textarea,
|
||||
form select {
|
||||
padding: .2rem;
|
||||
font-size: 1.6rem;
|
||||
border: 1px solid var(--charcoal);
|
||||
border-radius: 3px;
|
||||
background: var(--champagne);
|
||||
}
|
||||
|
||||
form input[type=text]:focus,
|
||||
form input[type=password]:focus,
|
||||
form input[type=email]:focus,
|
||||
form textarea:focus,
|
||||
form select:focus {
|
||||
background: white;
|
||||
outline: 2px solid var(--tangerine);
|
||||
}
|
||||
|
||||
form .emoji-picker {
|
||||
display: grid;
|
||||
row-gap: .2rem;
|
||||
}
|
||||
|
||||
form .emoji-picker details:first-of-type summary {
|
||||
border-top-right-radius: var(--emoji-picker-rounding);
|
||||
border-top-left-radius: var(--emoji-picker-rounding);
|
||||
}
|
||||
|
||||
form .emoji-picker details:last-of-type:not([open]) summary {
|
||||
border-bottom-right-radius: var(--emoji-picker-rounding);
|
||||
border-bottom-left-radius: var(--emoji-picker-rounding);
|
||||
}
|
||||
|
||||
form .emoji-picker details summary {
|
||||
background: var(--dusty-champagne);
|
||||
font-size: 1.4rem;
|
||||
padding: .4rem .6rem;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
form .emoji-picker ul {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
column-gap: .2rem;
|
||||
row-gap: .2rem;
|
||||
}
|
||||
|
||||
form .emoji-picker ul li label {
|
||||
padding: .3rem;
|
||||
}
|
||||
|
||||
form .emoji-picker ul li input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form .emoji-picker ul li input[type=radio]:checked + label {
|
||||
outline: 3px solid var(--tangerine);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
form button[type=submit]:not(.logout) {
|
||||
padding: .4rem .6rem;
|
||||
font-size: 1.5rem;
|
||||
background: var(--tangerine);
|
||||
border: 1px solid var(--orange);
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
header {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
column-gap: 2.4rem;
|
||||
align-items: center;
|
||||
margin-block-end: 2.4rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-weight: bold;
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
header nav ul {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
column-gap: 1.2rem;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
display: grid;
|
||||
grid-template-columns: 214px 1fr;
|
||||
column-gap: 2.4rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.grid-content main {
|
||||
display: grid;
|
||||
row-gap: 1.8rem;
|
||||
}
|
||||
|
||||
.grid-content main article {
|
||||
padding: .8rem 1.8rem;
|
||||
border-left: 4px solid var(--tangerine);
|
||||
word-break: break-all;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
column-gap: 1.2rem;
|
||||
}
|
||||
|
||||
.grid-content main article .image {
|
||||
grid-column: 1 / span 2;
|
||||
margin-block-end: 1rem;
|
||||
}
|
||||
|
||||
.grid-content main article .image img {
|
||||
max-width: var(--image-max-size);
|
||||
max-height: var(--image-max-size);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.grid-content main article .emoji {
|
||||
font-size: 2rem;
|
||||
padding-top: .8rem;
|
||||
}
|
||||
|
||||
.grid-content main article .body {
|
||||
display: grid;
|
||||
row-gap: .2rem;
|
||||
}
|
||||
|
||||
.grid-content main article .body .metadata {
|
||||
font-size: 1.4rem;
|
||||
color: var(--charcoal);
|
||||
}
|
||||
|
||||
.grid-content main article .body .recipe {
|
||||
margin-block-start: .4rem;
|
||||
}
|
||||
|
||||
.grid-content main article .body .recipe summary {
|
||||
font-size: 1.4rem;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.grid-content main article .body .recipe ol {
|
||||
font-size: 1.5rem;
|
||||
list-style: decimal;
|
||||
margin-block-start: 1rem;
|
||||
padding-inline-start: 3.4rem;
|
||||
border-left: 2px solid var(--tangerine);
|
||||
}
|
||||
48
moku/static/css/reset.css
Normal file
48
moku/static/css/reset.css
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
33
moku/templates/moku/base.jinja
Normal file
33
moku/templates/moku/base.jinja
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="utf-8">
|
||||
<title>moku.blog</title>
|
||||
<link rel="stylesheet" href="{{ static('css/reset.css') }}" type="text/css">
|
||||
<link rel="stylesheet" href="{{ static('css/moku.css') }}" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>moku.blog</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
{% if request.user.is_authenticated %}
|
||||
<li><a href="{{ url('feed') }}">{% trans %}feed{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('profile', username=request.user.username) }}">{% trans %}my profile{% endtrans %}</a></li>
|
||||
<li>
|
||||
<form action="{{ url('logout') }}" method="POST" class="logout">
|
||||
{% csrf_token %}
|
||||
<button class="logout" type="submit">{% trans %}log out{% endtrans %}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="{{ url('login') }}">{% trans %}log in{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('signup') }}">{% trans %}sign up{% endtrans %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
{% block content %}{% endblock content %}
|
||||
</body>
|
||||
</html>
|
||||
68
moku/templates/moku/feed.jinja
Normal file
68
moku/templates/moku/feed.jinja
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{% extends "moku/base.jinja" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid-content">
|
||||
<aside>
|
||||
{% if request.user.is_authenticated %}
|
||||
<form action="" method="POST" enctype="{% if form.is_multipart %}multipart/form-data{% else %}application/x-www-form-urlencoded{% endif %}">
|
||||
{% include "moku/snippets/form_errors.jinja" %}
|
||||
{% csrf_token %}
|
||||
<div class="emoji-picker">
|
||||
{% for emoji_category in emoji %}
|
||||
{% set outer_loop = loop %}
|
||||
<details{% if loop.index0 == 0 %} open{% endif %}>
|
||||
<summary>{{ emoji_category[0] }}</summary>
|
||||
<ul>
|
||||
{% for emoji_choice in emoji_category[1] %}
|
||||
{% set emoji_label = emoji_choice|unemoji %}
|
||||
<li>
|
||||
<input type="radio" value="{{ emoji_choice }}" name="emoji" id="id_emoji_{{ emoji_label }}" required{% if loop.index0 == 0 and outer_loop.index0 == 0 %} checked{% endif %}>
|
||||
<label for="id_emoji_{{ emoji_label }}">{{ emoji_choice }}</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</details>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_food">{{ form.food.label }}</label>
|
||||
<input type="text" name="food" id="id_food" required aria-describedby="help_food">
|
||||
<span class="help" id="help_food">{{ form.food.help_text }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_verb">{{ form.verb.label }}</label>
|
||||
<select name="verb" id="id_verb">
|
||||
{% for verb, verb_label in verbs %}
|
||||
<option value="{{ verb }}">{{ verb_label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span class="help" id="help_verb">{{ form.verb.help_text }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_image">{{ form.image.label }}</label>
|
||||
{{ form.image }}
|
||||
<span class="help" id="help_image">{{ form.image.help_text }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button type="submit">{% trans %}post!{% endtrans %}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{% trans %}want to post?{% endtrans %}</p>
|
||||
<p>
|
||||
{% with login_url=url('login'), signup_url=url('signup') %}
|
||||
{% trans %}<a href="{{ login_url }}">log in</a> or <a href="{{ signup_url }}">make an account</a>!{% endtrans %}
|
||||
{% endwith %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</aside>
|
||||
<main>
|
||||
{% if not posts %}
|
||||
<p>{% trans %}no posts yet... 🥱{% endtrans %}</p>
|
||||
{% endif %}
|
||||
{% for post in posts %}
|
||||
{% include "moku/snippets/post.jinja" %}
|
||||
{% endfor %}
|
||||
</main>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
19
moku/templates/moku/login.jinja
Normal file
19
moku/templates/moku/login.jinja
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "moku/base.jinja" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
<form action="" method="POST" class="auth">
|
||||
{% include "moku/snippets/form_errors.jinja" %}
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label for="id_username">{% trans %}username{% endtrans %}</label>
|
||||
<input type="text" name="username" id="id_username">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_password">{% trans %}password{% endtrans %}</label>
|
||||
<input type="password" name="password" id="id_password">
|
||||
</div>
|
||||
<button type="submit">{% trans %}log in{% endtrans %}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
24
moku/templates/moku/profile/edit.jinja
Normal file
24
moku/templates/moku/profile/edit.jinja
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{% extends "moku/base.jinja" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
<p class="mb"><a href="{{ url('profile', username=request.user.username) }}">👈 {% trans %}back to my profile{% endtrans %}</a></p>
|
||||
<form action="" method="POST" class="auth">
|
||||
{% include "moku/snippets/form_errors.jinja" %}
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label for="id_pronouns">{{ form.pronouns.label }}</label>
|
||||
<input type="text" name="pronouns" id="id_pronouns" value="{{ form.pronouns.value() or "" }}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_location">{{ form.location.label }}</label>
|
||||
<input type="text" name="location" id="id_location" value="{{ form.location.value() or "" }}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_bio">{{ form.bio.label }}</label>
|
||||
<textarea name="bio" id="id_bio">{{ form.bio.value() or "" }}</textarea>
|
||||
</div>
|
||||
<button type="submit">update!</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
34
moku/templates/moku/profile/show.jinja
Normal file
34
moku/templates/moku/profile/show.jinja
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{% extends "moku/base.jinja" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid-content">
|
||||
<aside class="profile">
|
||||
<h2>@{{ user.username }}</h2>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>{% trans %}pronouns{% endtrans %}</dt>
|
||||
<dd>{{ user.pronouns or "not set" }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{% trans %}location{% endtrans %}</dt>
|
||||
<dd>{{ user.location or "not set" }}</dd>
|
||||
</div>
|
||||
<div class="double">
|
||||
<dt>{% trans %}about me{% endtrans %}</dt>
|
||||
<dd>{{ user.bio or "not set" }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{% if user.id == request.user.id %}
|
||||
<p class="mt"><a href="{{ url('profile.edit') }}">{% trans %}edit{% endtrans %}</a></p>
|
||||
{% endif %}
|
||||
</aside>
|
||||
<main>
|
||||
{% if not posts %}
|
||||
<p>{% trans %}no posts yet... 🥱{% endtrans %}</p>
|
||||
{% endif %}
|
||||
{% for post in posts %}
|
||||
{% include "moku/snippets/post.jinja" %}
|
||||
{% endfor %}
|
||||
</main>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
36
moku/templates/moku/signup.jinja
Normal file
36
moku/templates/moku/signup.jinja
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{% extends "moku/base.jinja" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
<form action="" method="POST" class="auth">
|
||||
{% include "moku/snippets/form_errors.jinja" %}
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label for="id_username">{{ form.username.label }}</label>
|
||||
<input type="text" name="username" id="id_username" value="{{ form.username.value() or "" }}" required>
|
||||
<span class="help">{{ form.username.help_text }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_email">{{ form.email.label }}</label>
|
||||
<input type="email" name="email" id="id_email" value="{{ form.username.value() or "" }}" required>
|
||||
<span class="help">{{ form.email.help_text }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_password1">{{ form.password1.label }}</label>
|
||||
<input type="password" name="password1" id="id_password1" value="{{ form.username.value() or "" }}" required>
|
||||
<span class="help">{{ form.password1.help_text|safe }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_password2">{{ form.password2.label }}</label>
|
||||
<input type="password" name="password2" id="id_password2" value="{{ form.username.value() or "" }}" required>
|
||||
<span class="help">{{ form.password2.help_text }}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_captcha">captcha</label>
|
||||
{{ form.captcha }}
|
||||
<span class="help">{% trans %}please let us know you're not a robot. i'm scared of robots!{% endtrans %}</span>
|
||||
</div>
|
||||
<button type="submit">{% trans %}sign up!{% endtrans %}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
9
moku/templates/moku/snippets/form_errors.jinja
Normal file
9
moku/templates/moku/snippets/form_errors.jinja
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{% if form.errors %}
|
||||
<ul class="errors">
|
||||
{% for err_errors in form.errors.values() %}
|
||||
{% for error in err_errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
13
moku/templates/moku/snippets/post.jinja
Normal file
13
moku/templates/moku/snippets/post.jinja
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<article>
|
||||
{% if post.image %}
|
||||
<a href="{{ post.image.url }}" target="_blank" rel="noreferrer noopener" class="image">
|
||||
<img src="{{ post.image.url }}">
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="emoji">{{ post.emoji }}</div>
|
||||
<div class="body">
|
||||
<p class="food">{{ post.text|safe }}</p>
|
||||
<p class="metadata">{{ post.created_at|naturaltime }}</p>
|
||||
<!-- <details class="recipe"><summary>recipe</summary><ol>...</ol></details> -->
|
||||
</div>
|
||||
</article>
|
||||
18
moku/utils.py
Normal file
18
moku/utils.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from io import BytesIO
|
||||
|
||||
from django.core.files import File
|
||||
from emoji import demojize
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def process_image(image_file):
|
||||
image = Image.open(image_file)
|
||||
image.convert("RGB")
|
||||
image.thumbnail((486, 486))
|
||||
thumb_io = BytesIO()
|
||||
image.save(thumb_io, "WEBP")
|
||||
return File(thumb_io, name=".".join(image_file.name.split(".")[:-1] + ["webp"]))
|
||||
|
||||
|
||||
def unemoji(txt: str):
|
||||
return demojize(txt, delimiters=("", ""))
|
||||
32
moku/validators.py
Normal file
32
moku/validators.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.validators import RegexValidator, ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from emoji import is_emoji
|
||||
|
||||
|
||||
def validate_emoji(value):
|
||||
if not is_emoji(value):
|
||||
raise ValidationError(_("Must be an emoji."))
|
||||
|
||||
|
||||
validate_username_regex = RegexValidator(
|
||||
re.compile(r"^[a-zA-Z0-9-_.]+\Z"),
|
||||
_("Username may only contain letters, numbers, hyphens, underscores and dots."),
|
||||
"invalid",
|
||||
)
|
||||
|
||||
|
||||
def validate_username_length(value):
|
||||
if (
|
||||
len(value) < settings.USERNAME_MIN_LENGTH
|
||||
or len(value) > settings.USERNAME_MAX_LENGTH
|
||||
):
|
||||
raise ValidationError(
|
||||
_("Username must be between %(min_length)d and %(max_length)d characters.")
|
||||
% {
|
||||
"min_length": settings.USERNAME_MIN_LENGTH,
|
||||
"max_length": settings.USERNAME_MAX_LENGTH,
|
||||
}
|
||||
)
|
||||
0
moku/views/__init__.py
Normal file
0
moku/views/__init__.py
Normal file
19
moku/views/auth.py
Normal file
19
moku/views/auth.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from django.contrib.auth.views import LoginView as BaseLoginView, LogoutView as BaseLogoutView
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
class LoginView(BaseLoginView):
|
||||
template_name = "moku/login.jinja"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if self.request.user.is_authenticated:
|
||||
return redirect(self.get_success_url())
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.GET.get("next", reverse_lazy("feed"))
|
||||
|
||||
|
||||
class LogoutView(BaseLogoutView):
|
||||
next_page = "feed"
|
||||
38
moku/views/post.py
Normal file
38
moku/views/post.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic import FormView
|
||||
|
||||
from moku.constants import EMOJI_CATEGORIES, Verbs
|
||||
from moku.models.post import Post
|
||||
from moku.forms.post import PostForm
|
||||
|
||||
|
||||
class FeedView(FormView):
|
||||
template_name = "moku/feed.jinja"
|
||||
form_class = PostForm
|
||||
|
||||
def form_valid(self, form):
|
||||
if not self.request.user.is_authenticated:
|
||||
raise PermissionDenied
|
||||
form.instance.created_by = self.request.user
|
||||
form.instance.save()
|
||||
return redirect("feed")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
**super().get_context_data(**kwargs),
|
||||
"posts": Post.objects.order_by("-created_at").all()[:128]
|
||||
}
|
||||
if self.request.user.is_authenticated:
|
||||
return self.get_authenticated_context_data(context)
|
||||
return context
|
||||
|
||||
def get_authenticated_context_data(self, context):
|
||||
return {
|
||||
**context,
|
||||
"emoji": EMOJI_CATEGORIES,
|
||||
"verbs": (
|
||||
(verb[0], verb[1] % {"user": f"@{self.request.user.username}", "food": "..."})
|
||||
for verb in Verbs.CHOICES
|
||||
)
|
||||
}
|
||||
34
moku/views/user.py
Normal file
34
moku/views/user.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from django.shortcuts import redirect
|
||||
from django.views.generic import FormView, TemplateView
|
||||
|
||||
from moku.forms.user import ProfileForm, UserForm
|
||||
from moku.models.user import User
|
||||
|
||||
|
||||
class EditProfileView(FormView):
|
||||
template_name = "moku/profile/edit.jinja"
|
||||
form_class = ProfileForm
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
return redirect("profile", username=form.instance.username)
|
||||
|
||||
def get_form(self):
|
||||
return self.form_class(instance=self.request.user, **self.get_form_kwargs())
|
||||
|
||||
|
||||
class ProfileView(TemplateView):
|
||||
template_name = "moku/profile/show.jinja"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
user = User.objects.get(username=self.kwargs.get("username"))
|
||||
return {
|
||||
**super().get_context_data(**kwargs),
|
||||
"user": user,
|
||||
"posts": user.posts.order_by("-created_at").all(),
|
||||
}
|
||||
|
||||
|
||||
class SignupView(FormView):
|
||||
template_name = "moku/signup.jinja"
|
||||
form_class = UserForm
|
||||
477
poetry.lock
generated
Normal file
477
poetry.lock
generated
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.8.1"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
||||
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.0.3"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "Django-5.0.3-py3-none-any.whl", hash = "sha256:5c7d748ad113a81b2d44750ccc41edc14e933f56581683db548c9257e078cc83"},
|
||||
{file = "Django-5.0.3.tar.gz", hash = "sha256:5fb37580dcf4a262f9258c1f4373819aacca906431f505e4688e37f3a99195df"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.7.0,<4"
|
||||
sqlparse = ">=0.3.1"
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||
bcrypt = ["bcrypt"]
|
||||
|
||||
[[package]]
|
||||
name = "django-debug-toolbar"
|
||||
version = "4.3.0"
|
||||
description = "A configurable set of panels that display various debug information about the current request/response."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "django_debug_toolbar-4.3.0-py3-none-any.whl", hash = "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6"},
|
||||
{file = "django_debug_toolbar-4.3.0.tar.gz", hash = "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=3.2.4"
|
||||
sqlparse = ">=0.2"
|
||||
|
||||
[[package]]
|
||||
name = "django-environ"
|
||||
version = "0.11.2"
|
||||
description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application."
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4"
|
||||
files = [
|
||||
{file = "django-environ-0.11.2.tar.gz", hash = "sha256:f32a87aa0899894c27d4e1776fa6b477e8164ed7f6b3e410a62a6d72caaf64be"},
|
||||
{file = "django_environ-0.11.2-py2.py3-none-any.whl", hash = "sha256:0ff95ab4344bfeff693836aa978e6840abef2e2f1145adff7735892711590c05"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
develop = ["coverage[toml] (>=5.0a4)", "furo (>=2021.8.17b43,<2021.9.dev0)", "pytest (>=4.6.11)", "sphinx (>=3.5.0)", "sphinx-notfound-page"]
|
||||
docs = ["furo (>=2021.8.17b43,<2021.9.dev0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"]
|
||||
testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"]
|
||||
|
||||
[[package]]
|
||||
name = "django-jinja"
|
||||
version = "2.11.0"
|
||||
description = "Jinja2 templating language integrated in Django."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "django-jinja-2.11.0.tar.gz", hash = "sha256:47c06d3271e6b2f27d3596278af517bfe2e19c1eb36ae1c0b1cc302d7f0259af"},
|
||||
{file = "django_jinja-2.11.0-py3-none-any.whl", hash = "sha256:cc4c72246a6e346aa0574e0c56c3e534c1a20ef47b8476f05d7287781f69a0a9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=3.2"
|
||||
jinja2 = ">=3"
|
||||
|
||||
[[package]]
|
||||
name = "django-recaptcha"
|
||||
version = "4.0.0"
|
||||
description = "Django recaptcha form field/widget app."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "django-recaptcha-4.0.0.tar.gz", hash = "sha256:5316438f97700c431d65351470d1255047e3f2cd9af0f2f13592b637dad9213e"},
|
||||
{file = "django_recaptcha-4.0.0-py3-none-any.whl", hash = "sha256:0d912d5c7c009df4e47accd25029133d47a74342dbd2a8edc2877b6bffa971a3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = "*"
|
||||
|
||||
[[package]]
|
||||
name = "emoji"
|
||||
version = "2.10.1"
|
||||
description = "Emoji for Python"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "emoji-2.10.1-py2.py3-none-any.whl", hash = "sha256:11fb369ea79d20c14efa4362c732d67126df294a7959a2c98bfd7447c12a218e"},
|
||||
{file = "emoji-2.10.1.tar.gz", hash = "sha256:16287283518fb7141bde00198f9ffff4e1c1cb570efb68b2f1ec50975c3a581d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage", "coveralls", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "gunicorn"
|
||||
version = "21.2.0"
|
||||
description = "WSGI HTTP Server for UNIX"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"},
|
||||
{file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
packaging = "*"
|
||||
|
||||
[package.extras]
|
||||
eventlet = ["eventlet (>=0.24.1)"]
|
||||
gevent = ["gevent (>=1.4.0)"]
|
||||
setproctitle = ["setproctitle"]
|
||||
tornado = ["tornado (>=0.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
description = "brain-dead simple config-ini parsing"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.3"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
|
||||
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
|
||||
{file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
|
||||
{file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"},
|
||||
{file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"},
|
||||
{file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"},
|
||||
{file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"},
|
||||
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.0"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
|
||||
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.2.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"},
|
||||
{file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"},
|
||||
{file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"},
|
||||
{file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"},
|
||||
{file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"},
|
||||
{file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"},
|
||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"},
|
||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"},
|
||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"},
|
||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"},
|
||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"},
|
||||
{file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"},
|
||||
{file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"},
|
||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"},
|
||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"},
|
||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"},
|
||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"},
|
||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"},
|
||||
{file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"},
|
||||
{file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
|
||||
fpx = ["olefile"]
|
||||
mic = ["olefile"]
|
||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||
typing = ["typing-extensions"]
|
||||
xmp = ["defusedxml"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.4.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
|
||||
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg2"
|
||||
version = "2.9.9"
|
||||
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "psycopg2-2.9.9-cp310-cp310-win32.whl", hash = "sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516"},
|
||||
{file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"},
|
||||
{file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"},
|
||||
{file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"},
|
||||
{file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"},
|
||||
{file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"},
|
||||
{file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"},
|
||||
{file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"},
|
||||
{file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"},
|
||||
{file = "psycopg2-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e"},
|
||||
{file = "psycopg2-2.9.9-cp39-cp39-win32.whl", hash = "sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59"},
|
||||
{file = "psycopg2-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913"},
|
||||
{file = "psycopg2-2.9.9.tar.gz", hash = "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.1.1"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
|
||||
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=1.4,<2.0"
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-django"
|
||||
version = "4.8.0"
|
||||
description = "A Django plugin for pytest."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-django-4.8.0.tar.gz", hash = "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90"},
|
||||
{file = "pytest_django-4.8.0-py3-none-any.whl", hash = "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "sphinx-rtd-theme"]
|
||||
testing = ["Django", "django-configurations (>=2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.3.4"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"},
|
||||
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"},
|
||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"},
|
||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"},
|
||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"},
|
||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"},
|
||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"},
|
||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"},
|
||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"},
|
||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"},
|
||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"},
|
||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"},
|
||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"},
|
||||
{file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"},
|
||||
{file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"},
|
||||
{file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"},
|
||||
{file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shortuuid"
|
||||
version = "1.0.13"
|
||||
description = "A generator library for concise, unambiguous and URL-safe UUIDs."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "shortuuid-1.0.13-py3-none-any.whl", hash = "sha256:a482a497300b49b4953e15108a7913244e1bb0d41f9d332f5e9925dba33a3c5a"},
|
||||
{file = "shortuuid-1.0.13.tar.gz", hash = "sha256:3bb9cf07f606260584b1df46399c0b87dd84773e7b25912b7e391e30797c5e72"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.4.4"
|
||||
description = "A non-validating SQL parser."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
|
||||
{file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build", "flake8"]
|
||||
doc = ["sphinx"]
|
||||
test = ["pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.1"
|
||||
description = "Provider of IANA time zone data"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
files = [
|
||||
{file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
|
||||
{file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "a2d30838ebf562b336e3c1b80401aadbfd231979f3619645d94a45484bc04f40"
|
||||
60
pyproject.toml
Normal file
60
pyproject.toml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
[tool.poetry]
|
||||
name = "moku"
|
||||
version = "0.1.0"
|
||||
description = "a lightweight food blogging site"
|
||||
authors = ["m5ka <m5ka@posteo.de>"]
|
||||
license = "BSD 2-Clause"
|
||||
readme = "README.md"
|
||||
package-mode = false
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
django = "^5.0.3"
|
||||
django-environ = "^0.11.2"
|
||||
django-jinja = "^2.11.0"
|
||||
django-recaptcha = "^4.0.0"
|
||||
emoji = "^2.10.1"
|
||||
gunicorn = "^21.2.0"
|
||||
pillow = "^10.2.0"
|
||||
psycopg2 = "^2.9.9"
|
||||
python = "^3.12"
|
||||
shortuuid = "^1.0.13"
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
django-debug-toolbar = "^4.3.0"
|
||||
ruff = "^0.3.4"
|
||||
|
||||
[tool.poetry.group.test]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^8.1.1"
|
||||
pytest-django = "^4.8.0"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "moku.config.settings"
|
||||
|
||||
[tool.ruff]
|
||||
include = ["pyproject.toml", "moku/**/*.py", "manage.py"]
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
target-version = "py312"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["F", "E", "W", "I"]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
split-on-trailing-comma = false
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
skip-magic-trailing-comma = true
|
||||
docstring-code-format = true
|
||||
docstring-code-line-length = "dynamic"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
Loading…
Reference in a new issue