200 lines
7.6 KiB
Python
200 lines
7.6 KiB
Python
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.shortcuts import redirect
|
|
from django.utils import timezone
|
|
from django.views.generic import FormView
|
|
|
|
from activities.models import Post, PostAttachment, PostAttachmentStates, TimelineEvent
|
|
from core.files import blurhash_image, resize_image
|
|
from core.models import Config
|
|
from users.views.base import IdentityViewMixin
|
|
|
|
|
|
class Compose(IdentityViewMixin, FormView):
|
|
template_name = "activities/compose.html"
|
|
|
|
class form_class(forms.Form):
|
|
text = forms.CharField(
|
|
widget=forms.Textarea(
|
|
attrs={
|
|
"autofocus": "autofocus",
|
|
"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",
|
|
)
|
|
|
|
image = forms.ImageField(
|
|
required=False,
|
|
help_text="Optional - For multiple image uploads and cropping, please use an app",
|
|
widget=forms.FileInput(
|
|
attrs={
|
|
"_": f"""
|
|
on change
|
|
if me.files[0].size > {settings.SETUP.MEDIA_MAX_IMAGE_FILESIZE_MB * 1024 ** 2}
|
|
add [@disabled=] to #upload
|
|
|
|
remove <ul.errorlist/>
|
|
make <ul.errorlist/> called errorlist
|
|
make <li/> called error
|
|
set size_in_mb to (me.files[0].size / 1024 / 1024).toFixed(2)
|
|
put 'File must be {settings.SETUP.MEDIA_MAX_IMAGE_FILESIZE_MB}MB or less (actual: ' + size_in_mb + 'MB)' into error
|
|
put error into errorlist
|
|
put errorlist before me
|
|
else
|
|
remove @disabled from #upload
|
|
remove <ul.errorlist/>
|
|
end
|
|
end
|
|
"""
|
|
}
|
|
),
|
|
)
|
|
|
|
image_caption = forms.CharField(
|
|
required=False,
|
|
help_text="Provide an image caption for the visually impaired",
|
|
)
|
|
|
|
def __init__(self, identity, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.identity = identity
|
|
self.fields["text"].widget.attrs[
|
|
"_"
|
|
] = rf"""
|
|
init
|
|
-- Move cursor to the end of existing text
|
|
set my.selectionStart to my.value.length
|
|
end
|
|
|
|
on load or input
|
|
-- 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
|
|
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
|
|
"""
|
|
|
|
def clean_text(self):
|
|
text = self.cleaned_data.get("text")
|
|
# Check minimum interval
|
|
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"
|
|
)
|
|
if not text:
|
|
return text
|
|
# Check post length
|
|
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 clean_image(self):
|
|
value = self.cleaned_data.get("image")
|
|
if value:
|
|
max_mb = settings.SETUP.MEDIA_MAX_IMAGE_FILESIZE_MB
|
|
max_bytes = max_mb * 1024 * 1024
|
|
if value.size > max_bytes:
|
|
# Erase the file from our data to stop trying to show it again
|
|
self.files = {}
|
|
raise forms.ValidationError(
|
|
f"File must be {max_mb}MB or less (actual: {value.size / 1024 ** 2:.2f})"
|
|
)
|
|
return value
|
|
|
|
def get_form(self, form_class=None):
|
|
return self.form_class(identity=self.identity, **self.get_form_kwargs())
|
|
|
|
def get_initial(self):
|
|
initial = super().get_initial()
|
|
initial["visibility"] = self.identity.config_identity.default_post_visibility
|
|
return initial
|
|
|
|
def form_valid(self, form):
|
|
# See if we need to make an image attachment
|
|
attachments = []
|
|
if form.cleaned_data.get("image"):
|
|
main_file = resize_image(
|
|
form.cleaned_data["image"],
|
|
size=(2000, 2000),
|
|
cover=False,
|
|
)
|
|
thumbnail_file = resize_image(
|
|
form.cleaned_data["image"],
|
|
size=(400, 225),
|
|
cover=True,
|
|
)
|
|
attachment = PostAttachment.objects.create(
|
|
blurhash=blurhash_image(thumbnail_file),
|
|
mimetype="image/webp",
|
|
width=main_file.image.width,
|
|
height=main_file.image.height,
|
|
name=form.cleaned_data.get("image_caption"),
|
|
state=PostAttachmentStates.fetched,
|
|
author=self.identity,
|
|
)
|
|
attachment.file.save(
|
|
main_file.name,
|
|
main_file,
|
|
)
|
|
attachment.thumbnail.save(
|
|
thumbnail_file.name,
|
|
thumbnail_file,
|
|
)
|
|
attachment.save()
|
|
attachments.append(attachment)
|
|
# Create the post
|
|
post = Post.create_local(
|
|
author=self.identity,
|
|
content=form.cleaned_data["text"],
|
|
summary=form.cleaned_data.get("content_warning"),
|
|
visibility=form.cleaned_data["visibility"],
|
|
attachments=attachments,
|
|
)
|
|
# Add their own timeline event for immediate visibility
|
|
TimelineEvent.add_post(self.identity, post)
|
|
messages.success(self.request, "Your post was created.")
|
|
return redirect(".")
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["identity"] = self.identity
|
|
context["section"] = "compose"
|
|
return context
|