style: 💭 update docstring comments across project
This commit is contained in:
parent
a4f8275383
commit
2187af7b10
22 changed files with 104 additions and 35 deletions
|
|
@ -11,6 +11,8 @@ for model_name in models.__all__:
|
|||
|
||||
@admin.register(models.User)
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
"""Admin class override for the User model."""
|
||||
|
||||
fieldsets = (
|
||||
(None, {"fields": ("username", "email", "password")}),
|
||||
("Profile", {"fields": ("pronouns", "location", "bio")}),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class MokuConfig(AppConfig):
|
||||
"""Django application configuration for moku.blog."""
|
||||
|
||||
name = "moku"
|
||||
label = "moku"
|
||||
verbose_name = "moku.blog"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,3 @@
|
|||
"""
|
||||
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
|
||||
|
|
@ -14,3 +5,7 @@ from django.core.asgi import get_asgi_application
|
|||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "moku.config.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
"""
|
||||
ASGI application for moku.blog.
|
||||
More information: https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,20 +1,3 @@
|
|||
"""
|
||||
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
|
||||
|
|
@ -57,6 +40,10 @@ urlpatterns = [
|
|||
name="step.delete",
|
||||
),
|
||||
]
|
||||
"""
|
||||
URL patterns, defining the routes available in moku.blog.
|
||||
More information: https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||
"""
|
||||
|
||||
if settings.DEBUG_TOOLBAR:
|
||||
urlpatterns += [path("__debug__/", include("debug_toolbar.urls"))]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,3 @@
|
|||
"""
|
||||
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
|
||||
|
|
@ -14,3 +5,7 @@ from django.core.wsgi import get_wsgi_application
|
|||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "moku.config.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
"""
|
||||
WSGI application for moku.blog.
|
||||
More information: https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
|
||||
class Verbs:
|
||||
"""Defines choices of possible verbs in posts."""
|
||||
|
||||
ATE = "ate"
|
||||
DRANK = "drank"
|
||||
MADE = "made"
|
||||
|
|
@ -247,3 +249,4 @@ EMOJI_CATEGORIES = [
|
|||
),
|
||||
(_("tools & things"), ("🥄", "🍴", "🍽️", "🥣", "🥡", "🥢", "🧂", "🔪", "🪓")),
|
||||
]
|
||||
"""Defines emoji that are available in the emoji picker widget."""
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ from emoji import demojize
|
|||
|
||||
|
||||
def unemoji(txt: str):
|
||||
"""Turn emoji in the given string into plain text."""
|
||||
return demojize(txt, delimiters=("", ""))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ from moku.models import Post
|
|||
|
||||
|
||||
class PostForm(ModelForm):
|
||||
"""Form for creating and updating posts."""
|
||||
|
||||
class Meta:
|
||||
model = Post
|
||||
fields = ("emoji", "verb", "food", "recipe", "image")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ from moku.models.recipe import Recipe, RecipeStep
|
|||
|
||||
|
||||
class RecipeForm(ModelForm):
|
||||
"""Form for creating and updating recipes."""
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = ("title",)
|
||||
|
|
@ -12,6 +14,8 @@ class RecipeForm(ModelForm):
|
|||
|
||||
|
||||
class RecipeStepForm(ModelForm):
|
||||
"""Form for creating and updating steps of a recipe."""
|
||||
|
||||
class Meta:
|
||||
model = RecipeStep
|
||||
fields = ("instructions",)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ from moku.models.user import User, UserSettings
|
|||
|
||||
|
||||
class UserForm(UserCreationForm):
|
||||
"""Form for creating a new user account on the site."""
|
||||
|
||||
captcha = ReCaptchaField(required=True)
|
||||
check = forms.BooleanField(required=True)
|
||||
|
||||
|
|
@ -30,6 +32,8 @@ class UserForm(UserCreationForm):
|
|||
|
||||
|
||||
class UserSettingsForm(forms.ModelForm):
|
||||
"""Form for creating or updating user settings."""
|
||||
|
||||
class Meta:
|
||||
model = UserSettings
|
||||
fields = ("language",)
|
||||
|
|
@ -37,6 +41,8 @@ class UserSettingsForm(forms.ModelForm):
|
|||
|
||||
|
||||
class ProfileForm(forms.ModelForm):
|
||||
"""Form for updating user profile information."""
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("avatar", "pronouns", "location", "bio")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from PIL import Image, ImageOps
|
|||
|
||||
|
||||
def _convert_image_to_webp(image_file):
|
||||
"""Private helper function for image conversion."""
|
||||
image = Image.open(image_file)
|
||||
ImageOps.exif_transpose(image, in_place=True)
|
||||
image.convert("RGB")
|
||||
|
|
@ -15,8 +16,10 @@ def _convert_image_to_webp(image_file):
|
|||
|
||||
|
||||
def process_avatar_image(image_file):
|
||||
"""Image conversion function for user avatars."""
|
||||
return _convert_image_to_webp(image_file)
|
||||
|
||||
|
||||
def process_post_image(image_file):
|
||||
"""Image conversion function for post images."""
|
||||
return _convert_image_to_webp(image_file)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ from django.utils import translation
|
|||
|
||||
|
||||
class MokuLanguageMiddleware:
|
||||
"""Activates the chosen language of an authenticated user if set."""
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ from moku.validators import validate_emoji
|
|||
|
||||
|
||||
def post_image_filename(instance, _):
|
||||
"""Returns the filename that post images should be saved to."""
|
||||
return f"posts/{instance.created_by.username}__{instance.uuid}.webp"
|
||||
|
||||
|
||||
class PostManager(models.Manager):
|
||||
"""Manages post objects more efficiently by pre-fetching recipes and their steps."""
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super()
|
||||
|
|
@ -28,6 +31,8 @@ class PostManager(models.Manager):
|
|||
|
||||
|
||||
class Post(models.Model):
|
||||
"""Represents a single post on the site."""
|
||||
|
||||
uuid = ShortUUIDField(
|
||||
verbose_name=_("unique id"),
|
||||
max_length=22,
|
||||
|
|
@ -85,6 +90,10 @@ class Post(models.Model):
|
|||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
The text of the post, with the post's chosen verb hydrated with food and user
|
||||
information.
|
||||
"""
|
||||
return self.get_verb_display() % {
|
||||
"user": (
|
||||
f'<a href="{self.created_by.get_absolute_url()}">'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ from shortuuid.django_fields import ShortUUIDField
|
|||
|
||||
|
||||
class RecipeManager(models.Manager):
|
||||
"""Manages recipe objects more efficiently by pre-fetching steps."""
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super()
|
||||
|
|
@ -16,6 +18,8 @@ class RecipeManager(models.Manager):
|
|||
|
||||
|
||||
class Recipe(models.Model):
|
||||
"""Represents a single recipe on the site."""
|
||||
|
||||
uuid = ShortUUIDField(
|
||||
verbose_name=_("unique id"),
|
||||
max_length=22,
|
||||
|
|
@ -47,6 +51,8 @@ class Recipe(models.Model):
|
|||
|
||||
|
||||
class RecipeStep(models.Model):
|
||||
"""Represents a single step belonging to a recipe."""
|
||||
|
||||
uuid = ShortUUIDField(
|
||||
verbose_name=_("step id"),
|
||||
max_length=22,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ from moku.validators import validate_username_length, validate_username_regex
|
|||
|
||||
|
||||
def user_avatar_filename(instance, _):
|
||||
"""Returns the filename that user avatar images should be saved to."""
|
||||
return f"avatars/{instance.username}.webp"
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
"""Represents a single authenticated user on the site."""
|
||||
|
||||
username = models.CharField(
|
||||
verbose_name=_("username"),
|
||||
max_length=64,
|
||||
|
|
@ -74,10 +77,13 @@ class User(AbstractUser):
|
|||
|
||||
@property
|
||||
def email_confirmed(self):
|
||||
"""Whether the user has confirmed their email address."""
|
||||
return self.email_confirmed_at is not None
|
||||
|
||||
|
||||
class UserSettings(models.Model):
|
||||
"""Represents settings for a single user."""
|
||||
|
||||
user = models.OneToOneField(
|
||||
"User", related_name="settings", on_delete=models.CASCADE
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from emoji import is_emoji
|
|||
|
||||
|
||||
def validate_emoji(value):
|
||||
"""Validates that a given string is a single emoji."""
|
||||
if not is_emoji(value):
|
||||
raise ValidationError(_("Must be an emoji."))
|
||||
|
||||
|
|
@ -16,9 +17,11 @@ validate_username_regex = RegexValidator(
|
|||
_("Username may only contain letters, numbers, hyphens, underscores and dots."),
|
||||
"invalid",
|
||||
)
|
||||
"""Validates that a given string is a valid username."""
|
||||
|
||||
|
||||
def validate_username_length(value):
|
||||
"""Validates the length of a given username string according to Django settings."""
|
||||
if (
|
||||
len(value) < settings.USERNAME_MIN_LENGTH
|
||||
or len(value) > settings.USERNAME_MAX_LENGTH
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from moku.views.base import View
|
|||
|
||||
|
||||
class LoginView(View, BaseLoginView):
|
||||
"""Allows users to log in by username and password."""
|
||||
|
||||
template_name = "moku/login.jinja"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
@ -27,4 +29,6 @@ class LoginView(View, BaseLoginView):
|
|||
|
||||
|
||||
class LogoutView(BaseLogoutView):
|
||||
"""Logs the user out and redirect them to the feed."""
|
||||
|
||||
next_page = "feed"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ from django.views import generic
|
|||
|
||||
|
||||
class View(generic.TemplateView):
|
||||
"""Defines a common set of data to be passed to the template context."""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return {
|
||||
**super().get_context_data(**kwargs),
|
||||
|
|
@ -14,4 +16,6 @@ class View(generic.TemplateView):
|
|||
|
||||
|
||||
class FormView(View, generic.FormView):
|
||||
"""Functions the same as `moku.views.base.View` but for rendering forms."""
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ from moku.views.base import FormView
|
|||
|
||||
|
||||
class FeedView(FormView):
|
||||
"""Allows users to see recent posts and create a new post."""
|
||||
|
||||
template_name = "moku/feed.jinja"
|
||||
form_class = PostForm
|
||||
|
||||
|
|
@ -70,6 +72,8 @@ class FeedView(FormView):
|
|||
|
||||
|
||||
class LatestPostJSONView(BaseView):
|
||||
"""Renders the latest post from a specific user as JSON."""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
post = (
|
||||
Post.objects.prefetch_related("recipe__steps")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ from moku.views.base import FormView, View
|
|||
|
||||
|
||||
class DeleteRecipeView(LoginRequiredMixin, UserPassesTestMixin, View):
|
||||
"""Deletes a recipe from the database if it belongs to the authenticated user."""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.recipe.delete()
|
||||
messages.success(self.request, _("recipe deleted successfully!"))
|
||||
|
|
@ -24,6 +26,10 @@ class DeleteRecipeView(LoginRequiredMixin, UserPassesTestMixin, View):
|
|||
|
||||
|
||||
class DeleteStepView(LoginRequiredMixin, UserPassesTestMixin, View):
|
||||
"""
|
||||
Deletes a recipe step from the database if it belongs to the authenticated user.
|
||||
"""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if self.step.order != (self.step.recipe.steps.count() - 1):
|
||||
messages.error(self.request, _("sorry! you can only delete the last step."))
|
||||
|
|
@ -45,6 +51,8 @@ class DeleteStepView(LoginRequiredMixin, UserPassesTestMixin, View):
|
|||
|
||||
|
||||
class EditStepView(LoginRequiredMixin, UserPassesTestMixin, FormView):
|
||||
"""Allows users to edit steps of a recipe they created."""
|
||||
|
||||
template_name = "moku/recipe/edit_step.jinja"
|
||||
form_class = RecipeStepForm
|
||||
|
||||
|
|
@ -69,6 +77,8 @@ class EditStepView(LoginRequiredMixin, UserPassesTestMixin, FormView):
|
|||
|
||||
|
||||
class IndexRecipeView(LoginRequiredMixin, View):
|
||||
"""Shows a list of recipes created by the authenticated user."""
|
||||
|
||||
template_name = "moku/recipe/index.jinja"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
@ -81,6 +91,8 @@ class IndexRecipeView(LoginRequiredMixin, View):
|
|||
|
||||
|
||||
class NewRecipeView(LoginRequiredMixin, FormView):
|
||||
"""Allows users to create a new recipe."""
|
||||
|
||||
template_name = "moku/recipe/form.jinja"
|
||||
form_class = RecipeForm
|
||||
|
||||
|
|
@ -95,6 +107,11 @@ class NewRecipeView(LoginRequiredMixin, FormView):
|
|||
|
||||
|
||||
class ShowRecipeView(FormView):
|
||||
"""
|
||||
Shows users details about a recipe, and allows steps to be created for it if they
|
||||
are logged in as the recipe creator.
|
||||
"""
|
||||
|
||||
template_name = "moku/recipe/show.jinja"
|
||||
form_class = RecipeStepForm
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ from moku.views.base import View
|
|||
|
||||
|
||||
class ChangelogView(View):
|
||||
"""Displays the static changelog page."""
|
||||
|
||||
template_name = "moku/changelog.jinja"
|
||||
|
||||
|
||||
class PrivacyView(View):
|
||||
"""Displays the static privacy policy page."""
|
||||
|
||||
template_name = "moku/privacy.jinja"
|
||||
|
||||
|
||||
class TermsView(View):
|
||||
"""Displays the static terms of use page."""
|
||||
|
||||
template_name = "moku/terms.jinja"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ from moku.views.base import FormView, View
|
|||
|
||||
|
||||
class EditProfileView(LoginRequiredMixin, FormView):
|
||||
"""Allows a user to edit information within their user profile."""
|
||||
|
||||
template_name = "moku/profile/edit.jinja"
|
||||
form_class = ProfileForm
|
||||
|
||||
|
|
@ -26,6 +28,8 @@ class EditProfileView(LoginRequiredMixin, FormView):
|
|||
|
||||
|
||||
class EditSettingsView(LoginRequiredMixin, FormView):
|
||||
"""Allows a user to edit information within their user settings."""
|
||||
|
||||
template_name = "moku/settings.jinja"
|
||||
form_class = UserSettingsForm
|
||||
|
||||
|
|
@ -50,6 +54,8 @@ class EditSettingsView(LoginRequiredMixin, FormView):
|
|||
|
||||
|
||||
class ProfileView(View):
|
||||
"""Shows a user's profile along with a list of their recent posts."""
|
||||
|
||||
template_name = "moku/profile/show.jinja"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
@ -62,6 +68,8 @@ class ProfileView(View):
|
|||
|
||||
|
||||
class SignupView(FormView):
|
||||
"""Allows non-authenticated users to create an account on the site."""
|
||||
|
||||
template_name = "moku/signup.jinja"
|
||||
form_class = UserForm
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue