feat: 📝 allow editing of posts

This commit is contained in:
m5ka 2024-03-27 15:43:24 +00:00
parent 98a9809cda
commit 2fed369027
9 changed files with 159 additions and 88 deletions

View file

@ -5,7 +5,7 @@ from django.urls import include, path
from moku.views.auth import LoginView, LogoutView
from moku.views.blog import IndexBlogView
from moku.views.post import FeedView
from moku.views.post import EditPostview, FeedView
from moku.views.recipe import (
DeleteRecipeView,
DeleteStepView,
@ -34,6 +34,7 @@ urlpatterns = [
path("blog", IndexBlogView.as_view(), name="blog.index"),
path("privacy", PrivacyView.as_view(), name="privacy"),
path("terms", TermsView.as_view(), name="terms"),
path("edit/<str:uuid>", EditPostview.as_view(), name="post.edit"),
path("user/<str:username>", ProfileView.as_view(), name="profile"),
path("user/<str:username>/json", UserJSONView.as_view(), name="json"),
path("recipes", IndexRecipeView.as_view(), name="recipe.index"),

View file

@ -62,6 +62,10 @@ body {
color: var(--charcoal);
}
a.subtle {
font-weight: normal;
}
a.subtle:hover {
color: var(--orange);
}
@ -295,6 +299,7 @@ form .emoji-picker ul li input[type=radio]:checked + label {
form button[type=submit]:not(.logout) {
padding: .4rem .6rem;
font-size: 1.5rem;
font-family: var(--font-family);
background: var(--tangerine);
border: 1px solid var(--orange);
outline: none;
@ -430,14 +435,14 @@ header nav ul {
margin-block-start: 1.2rem;
}
.block ul,
.block ul:not(.errors):not(.emoji-picker-group),
.block ol {
padding-inline-start: 3.2rem;
display: grid;
row-gap: .4rem;
}
.block ul {
.block ul:not(.errors):not(.emoji-picker-group) {
list-style: disc;
}

View file

@ -1,73 +0,0 @@
{% extends "moku/base.jinja" %}
{% block content %}
<div class="grid-content">
<aside>
{% if request.user.is_authenticated %}
<form action="" method="POST" enctype="multipart/form-data">
{% 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_recipe">{{ form.recipe.label }}</label>
{{ form.recipe }}
<span class="help">{{ form.recipe.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 %}

View file

@ -0,0 +1,8 @@
{% extends "moku/base.jinja" %}
{% block content %}
<div class="content block">
<h2>{% trans time_ago=post.created_at|naturaltime %}editing post from {{ time_ago }}{% endtrans %}</h2>
{% include "moku/snippets/post_form.jinja" %}
</div>
{% endblock content %}

View file

@ -0,0 +1,26 @@
{% extends "moku/base.jinja" %}
{% block content %}
<div class="grid-content">
<aside>
{% if request.user.is_authenticated %}
{% include "moku/snippets/post_form.jinja" %}
{% 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 %}

View file

@ -9,7 +9,13 @@
<div class="emoji">{{ post.emoji }}</div>
<div class="body">
<p class="food user-content">{{ post.text|safe }}</p>
<p class="metadata">{{ post.created_at|naturaltime }}</p>
<p class="metadata">
{{ post.created_at|naturaltime }}
{% if request.user.is_authenticated and request.user.id == post.created_by.id %}
·
<a href="{{ url('post.edit', uuid=post.uuid) }}" class="subtle">edit</a>
{% endif %}
</p>
{% if post.recipe %}
<details class="recipe">
<summary>{% trans %}recipe{% endtrans %}</summary>

View file

@ -0,0 +1,55 @@
<form action="" method="POST" enctype="multipart/form-data">
{% include "moku/snippets/form_errors.jinja" %}
{% csrf_token %}
<div class="emoji-picker">
{% set current_emoji = form.emoji.value() or emoji[0][1][0] %}
{% for emoji_category in emoji %}
<details{% if current_emoji in emoji_category[1] %} open{% endif %}>
<summary>{{ emoji_category[0] }}</summary>
<ul class="emoji-picker-group">
{% 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 emoji_choice == current_emoji %} 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" value="{{ form.food.value() or "" }}" 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 }}"{% if form.verb.value() == verb %} selected{% endif %}>{{ verb_label }}</option>
{% endfor %}
</select>
<span class="help" id="help_verb">{{ form.verb.help_text }}</span>
</div>
<div class="field">
<label for="id_recipe">{{ form.recipe.label }}</label>
{{ form.recipe }}
<span class="help">{{ form.recipe.help_text }}</span>
</div>
<div class="field">
<label for="id_image">{{ form.image.label }}</label>
<div>{{ form.image }}</div>
<span class="help" id="help_image">{{ form.image.help_text }}</span>
</div>
<div class="field">
<button type="submit">{% trans %}post!{% endtrans %}</button>
</div>
</form>

View file

@ -1,6 +1,8 @@
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.shortcuts import get_object_or_404, redirect
from django.utils.functional import cached_property
from django.utils.translation import gettext as _
from moku.constants import EMOJI_CATEGORIES, Verbs
@ -11,10 +13,57 @@ from moku.models.recipe import Recipe
from moku.views.base import FormView
def _get_verbs(username):
return (
(verb[0], verb[1] % {"user": f"@{username}", "food": "..."})
for verb in Verbs.CHOICES
)
class EditPostview(LoginRequiredMixin, UserPassesTestMixin, FormView):
"""Allows users to edit their previous posts."""
template_name = "moku/post/edit.jinja"
form_class = PostForm
def form_valid(self, form):
if (
form.instance.recipe
and form.instance.recipe.created_by.id != self.request.user.id
):
messages.error(
self.request, _("you can't add someone else's recipe to your post!")
)
return self.form_invalid(form)
if "image" in form.changed_data and form.instance.image.name:
form.instance.image = process_post_image(form.instance.image)
form.save()
messages.success(self.request, _("your post was updated!"))
return redirect("feed")
def get_context_data(self, **kwargs):
return {
**super().get_context_data(**kwargs),
"post": self.post_object,
"verbs": _get_verbs(self.request.user.username),
"emoji": EMOJI_CATEGORIES,
}
def get_form(self):
return self.form_class(instance=self.post_object, **self.get_form_kwargs())
@cached_property
def post_object(self):
return get_object_or_404(Post, uuid=self.kwargs.get("uuid"))
def test_func(self):
return self.post_object.created_by.id == self.request.user.id
class FeedView(FormView):
"""Allows users to see recent posts and create a new post."""
template_name = "moku/feed.jinja"
template_name = "moku/post/index.jinja"
form_class = PostForm
def form_valid(self, form):
@ -29,7 +78,7 @@ class FeedView(FormView):
)
return redirect("feed")
form.instance.created_by = self.request.user
if "image" in form.changed_data and form.instance.image is not None:
if "image" in form.changed_data and form.instance.image.name:
form.instance.image = process_post_image(form.instance.image)
form.save()
messages.success(self.request, _("your post was made!"))
@ -48,13 +97,7 @@ class FeedView(FormView):
return {
**context,
"emoji": EMOJI_CATEGORIES,
"verbs": (
(
verb[0],
verb[1] % {"user": f"@{self.request.user.username}", "food": "..."},
)
for verb in Verbs.CHOICES
),
"verbs": _get_verbs(self.request.user.username),
}
def get_form(self, form_class=None):

View file

@ -27,7 +27,7 @@ class EditProfileView(LoginRequiredMixin, FormView):
page_title = gettext_lazy("edit profile")
def form_valid(self, form):
if "avatar" in form.changed_data and form.instance.avatar is not None:
if "avatar" in form.changed_data and form.instance.avatar.name:
form.instance.avatar = process_avatar_image(form.instance.avatar)
form.save()
messages.success(self.request, _("profile updated successfully!"))