takahe/activities/views/compose.py

192 lines
7.4 KiB
Python
Raw Normal View History

2022-12-02 01:46:49 +00:00
from django import forms
from django.conf import settings
2022-12-02 01:46:49 +00:00
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
2022-12-02 01:46:49 +00:00
from django.utils.decorators import method_decorator
from django.views.generic import FormView
from activities.models import (
Post,
PostAttachment,
PostAttachmentStates,
PostStates,
TimelineEvent,
)
from core.files import blurhash_image, resize_image
from core.html import FediverseHtmlParser
2022-12-02 01:46:49 +00:00
from core.models import Config
2023-04-28 01:09:16 +00:00
from users.shortcuts import by_handle_for_user_or_404
2023-04-27 06:54:38 +00:00
from django.contrib.auth.decorators import login_required
2022-12-02 01:46:49 +00:00
2023-04-27 06:54:38 +00:00
@method_decorator(login_required, name="dispatch")
2022-12-02 01:46:49 +00:00
class Compose(FormView):
template_name = "activities/compose.html"
class form_class(forms.Form):
text = forms.CharField(
widget=forms.Textarea(
attrs={
2022-12-11 16:23:39 +00:00
"autofocus": "autofocus",
2022-12-02 01:46:49 +00:00
"placeholder": "What's on your mind?",
},
)
)
visibility = forms.ChoiceField(
choices=[
(Post.Visibilities.public, "Public"),
(Post.Visibilities.local_only, "Local Only"),
(Post.Visibilities.unlisted, "Unlisted"),
(Post.Visibilities.followers, "Followers & Mentioned Only"),
(Post.Visibilities.mentioned, "Mentioned Only"),
],
)
content_warning = forms.CharField(
required=False,
label=Config.lazy_system_value("content_warning_text"),
widget=forms.TextInput(
attrs={
"placeholder": Config.lazy_system_value("content_warning_text"),
},
),
help_text="Optional - Post will be hidden behind this text until clicked",
)
reply_to = forms.CharField(widget=forms.HiddenInput(), required=False)
2023-04-27 06:54:38 +00:00
def __init__(self, identity, *args, **kwargs):
2022-12-11 16:34:44 +00:00
super().__init__(*args, **kwargs)
2023-04-27 06:54:38 +00:00
self.identity = identity
2022-12-11 16:34:44 +00:00
self.fields["text"].widget.attrs[
"_"
] = rf"""
init
-- Move cursor to the end of existing text
set my.selectionStart to my.value.length
end
2022-12-11 16:34:44 +00:00
on load or input
2023-01-01 03:17:48 +00:00
-- Unicode-aware counting to match Python
-- <LF> will be normalized as <CR><LF> in Django
set characters to Array.from(my.value.replaceAll('\n','\r\n').trim()).length
2022-12-11 16:34:44 +00:00
put {Config.system.post_length} - characters into #character-counter
if characters > {Config.system.post_length} then
set #character-counter's style.color to 'var(--color-text-error)'
add [@disabled=] to #post-button
else
set #character-counter's style.color to ''
remove @disabled from #post-button
end
"""
2022-12-02 01:46:49 +00:00
def clean_text(self):
text = self.cleaned_data.get("text")
# Check minimum interval
2023-04-27 06:54:38 +00:00
last_post = self.identity.posts.order_by("-created").first()
if (
last_post
and (timezone.now() - last_post.created).total_seconds()
< Config.system.post_minimum_interval
):
raise forms.ValidationError(
f"You must wait at least {Config.system.post_minimum_interval} seconds between posts"
)
2022-12-02 01:46:49 +00:00
if not text:
return text
# Check post length
2022-12-02 01:46:49 +00:00
length = len(text)
if length > Config.system.post_length:
raise forms.ValidationError(
f"Maximum post length is {Config.system.post_length} characters (you have {length})"
)
return text
def get_form(self, form_class=None):
2023-04-27 06:54:38 +00:00
return self.form_class(identity=self.identity, **self.get_form_kwargs())
2022-12-02 01:46:49 +00:00
def get_initial(self):
initial = super().get_initial()
if self.post_obj:
initial.update(
{
"reply_to": self.reply_to.pk if self.reply_to else "",
"visibility": self.post_obj.visibility,
"text": FediverseHtmlParser(self.post_obj.content).plain_text,
2022-12-02 01:46:49 +00:00
"content_warning": self.post_obj.summary,
}
)
else:
initial[
"visibility"
2023-04-27 06:54:38 +00:00
] = self.identity.config_identity.default_post_visibility
2022-12-02 01:46:49 +00:00
if self.reply_to:
initial["reply_to"] = self.reply_to.pk
2023-04-29 20:53:53 +00:00
initial["visibility"] = self.reply_to.visibility
initial["content_warning"] = self.reply_to.summary
2022-12-05 03:22:24 +00:00
# Build a set of mentions for the content to start as
mentioned = {self.reply_to.author}
mentioned.update(self.reply_to.mentions.all())
2023-04-27 06:54:38 +00:00
mentioned.discard(self.identity)
2022-12-05 03:22:24 +00:00
initial["text"] = "".join(
f"@{identity.handle} "
for identity in mentioned
if identity.username
2022-12-05 03:22:24 +00:00
)
2022-12-02 01:46:49 +00:00
return initial
def form_valid(self, form):
# Gather any attachment objects now, they're not in the form proper
attachments = []
if "attachment" in self.request.POST:
attachments = PostAttachment.objects.filter(
pk__in=self.request.POST.getlist("attachment", [])
)
# Dispatch based on edit or not
if self.post_obj:
self.post_obj.edit_local(
content=form.cleaned_data["text"],
summary=form.cleaned_data.get("content_warning"),
visibility=form.cleaned_data["visibility"],
attachments=attachments,
)
self.post_obj.transition_perform(PostStates.edited)
else:
post = Post.create_local(
2023-04-27 06:54:38 +00:00
author=self.identity,
2022-12-02 01:46:49 +00:00
content=form.cleaned_data["text"],
summary=form.cleaned_data.get("content_warning"),
visibility=form.cleaned_data["visibility"],
reply_to=self.reply_to,
attachments=attachments,
)
# Add their own timeline event for immediate visibility
2023-04-27 06:54:38 +00:00
TimelineEvent.add_post(self.identity, post)
2023-04-28 01:09:16 +00:00
return redirect(self.identity.urls.view)
2022-12-02 01:46:49 +00:00
def dispatch(self, request, handle=None, post_id=None, *args, **kwargs):
2023-04-28 01:09:16 +00:00
self.identity = by_handle_for_user_or_404(self.request, handle)
2022-12-02 01:46:49 +00:00
self.post_obj = None
if handle and post_id:
2023-04-27 06:54:38 +00:00
self.post_obj = get_object_or_404(self.identity.posts, pk=post_id)
2022-12-02 01:46:49 +00:00
# Grab the reply-to post info now
self.reply_to = None
reply_to_id = request.POST.get("reply_to") or request.GET.get("reply_to")
if reply_to_id:
try:
self.reply_to = Post.objects.get(pk=reply_to_id)
except Post.DoesNotExist:
pass
# Keep going with normal rendering
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["reply_to"] = self.reply_to
2023-04-27 06:54:38 +00:00
context["identity"] = self.identity
2023-04-28 01:09:16 +00:00
context["section"] = "compose"
2022-12-02 01:46:49 +00:00
if self.post_obj:
context["post"] = self.post_obj
return context