style: 💭 update docstring comments across project

This commit is contained in:
m5ka 2024-03-26 12:44:19 +00:00
parent a4f8275383
commit 2187af7b10
22 changed files with 104 additions and 35 deletions

View file

@ -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")}),

View file

@ -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"

View file

@ -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/
"""

View file

@ -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"))]

View file

@ -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/
"""

View file

@ -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."""

View file

@ -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=("", ""))

View file

@ -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")

View file

@ -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",)

View file

@ -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")

View file

@ -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)

View 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

View file

@ -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()}">'

View file

@ -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,

View file

@ -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
)

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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"

View file

@ -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