summaryrefslogtreecommitdiffstats
path: root/activities/views
diff options
context:
space:
mode:
Diffstat (limited to 'activities/views')
-rw-r--r--activities/views/compose.py182
-rw-r--r--activities/views/posts.py140
2 files changed, 184 insertions, 138 deletions
diff --git a/activities/views/compose.py b/activities/views/compose.py
new file mode 100644
index 0000000..58dc7fc
--- /dev/null
+++ b/activities/views/compose.py
@@ -0,0 +1,182 @@
+from django import forms
+from django.core.exceptions import PermissionDenied
+from django.shortcuts import get_object_or_404, redirect, render
+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 html_to_plaintext
+from core.models import Config
+from users.decorators import identity_required
+
+
+@method_decorator(identity_required, name="dispatch")
+class Compose(FormView):
+
+ template_name = "activities/compose.html"
+
+ class form_class(forms.Form):
+ text = forms.CharField(
+ widget=forms.Textarea(
+ attrs={
+ "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)
+
+ def clean_text(self):
+ text = self.cleaned_data.get("text")
+ if not text:
+ return text
+ 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_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": html_to_plaintext(self.post_obj.content),
+ "content_warning": self.post_obj.summary,
+ }
+ )
+ else:
+ initial[
+ "visibility"
+ ] = self.request.identity.config_identity.default_post_visibility
+ if self.reply_to:
+ initial["reply_to"] = self.reply_to.pk
+ if self.reply_to.visibility == Post.Visibilities.public:
+ initial["visibility"] = Post.Visibilities.unlisted
+ else:
+ initial["visibility"] = self.reply_to.visibility
+ initial["text"] = f"@{self.reply_to.author.handle} "
+ 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(
+ author=self.request.identity,
+ 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
+ TimelineEvent.add_post(self.request.identity, post)
+ return redirect("/")
+
+ def dispatch(self, request, handle=None, post_id=None, *args, **kwargs):
+ self.post_obj = None
+ if handle and post_id:
+ # Make sure the request identity owns the post!
+ if handle != request.identity.handle:
+ raise PermissionDenied("Post author is not requestor")
+
+ self.post_obj = get_object_or_404(request.identity.posts, pk=post_id)
+
+ # 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
+ if self.post_obj:
+ context["post"] = self.post_obj
+ return context
+
+
+@method_decorator(identity_required, name="dispatch")
+class ImageUpload(FormView):
+ """
+ Handles image upload - returns a new input type hidden to embed in
+ the main form that references an orphaned PostAttachment
+ """
+
+ template_name = "activities/_image_upload.html"
+
+ class form_class(forms.Form):
+ image = forms.ImageField()
+ description = forms.CharField(required=False)
+
+ def form_valid(self, form):
+ # Make a PostAttachment
+ thumbnail_file = resize_image(form.cleaned_data["image"], size=(400, 225))
+ attachment = PostAttachment.objects.create(
+ blurhash=blurhash_image(thumbnail_file),
+ mimetype=form.cleaned_data["image"].image.get_format_mimetype(),
+ width=form.cleaned_data["image"].image.width,
+ height=form.cleaned_data["image"].image.height,
+ name=form.cleaned_data.get("description"),
+ state=PostAttachmentStates.fetched,
+ )
+ attachment.file.save(
+ form.cleaned_data["image"].name,
+ form.cleaned_data["image"],
+ )
+ attachment.thumbnail.save(
+ form.cleaned_data["image"].name,
+ thumbnail_file,
+ )
+ attachment.save()
+ # Return the response, with a hidden input plus a note
+ return render(
+ self.request, "activities/_image_uploaded.html", {"attachment": attachment}
+ )
diff --git a/activities/views/posts.py b/activities/views/posts.py
index 6dbfc8e..8b0ff33 100644
--- a/activities/views/posts.py
+++ b/activities/views/posts.py
@@ -1,21 +1,12 @@
-from django import forms
from django.core.exceptions import PermissionDenied
from django.db import models
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
-from django.views.generic import FormView, TemplateView, View
+from django.views.generic import TemplateView, View
-from activities.models import (
- Post,
- PostInteraction,
- PostInteractionStates,
- PostStates,
- TimelineEvent,
-)
-from core.html import html_to_plaintext
+from activities.models import Post, PostInteraction, PostInteractionStates, PostStates
from core.ld import canonicalise
-from core.models import Config
from users.decorators import identity_required
from users.shortcuts import by_handle_or_404
@@ -188,130 +179,3 @@ class Delete(TemplateView):
def post(self, request):
self.post_obj.transition_perform(PostStates.deleted)
return redirect("/")
-
-
-@method_decorator(identity_required, name="dispatch")
-class Compose(FormView):
-
- template_name = "activities/compose.html"
-
- class form_class(forms.Form):
- id = forms.IntegerField(
- required=False,
- widget=forms.HiddenInput(),
- )
-
- text = forms.CharField(
- widget=forms.Textarea(
- attrs={
- "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)
-
- def clean_text(self):
- text = self.cleaned_data.get("text")
- if not text:
- return text
- 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_initial(self):
- initial = super().get_initial()
- if self.post_obj:
- initial.update(
- {
- "id": self.post_obj.id,
- "reply_to": self.reply_to.pk if self.reply_to else "",
- "visibility": self.post_obj.visibility,
- "text": html_to_plaintext(self.post_obj.content),
- "content_warning": self.post_obj.summary,
- }
- )
- else:
- initial[
- "visibility"
- ] = self.request.identity.config_identity.default_post_visibility
- if self.reply_to:
- initial["reply_to"] = self.reply_to.pk
- if self.reply_to.visibility == Post.Visibilities.public:
- initial["visibility"] = Post.Visibilities.unlisted
- else:
- initial["visibility"] = self.reply_to.visibility
- initial["text"] = f"@{self.reply_to.author.handle} "
- return initial
-
- def form_valid(self, form):
- post_id = form.cleaned_data.get("id")
- if post_id:
- post = get_object_or_404(self.request.identity.posts, pk=post_id)
- post.edit_local(
- content=form.cleaned_data["text"],
- summary=form.cleaned_data.get("content_warning"),
- visibility=form.cleaned_data["visibility"],
- )
-
- # Should there be a timeline event for edits?
- # E.g. "@user edited #123"
-
- post.transition_perform(PostStates.edited)
- else:
- post = Post.create_local(
- author=self.request.identity,
- content=form.cleaned_data["text"],
- summary=form.cleaned_data.get("content_warning"),
- visibility=form.cleaned_data["visibility"],
- reply_to=self.reply_to,
- )
- # Add their own timeline event for immediate visibility
- TimelineEvent.add_post(self.request.identity, post)
- return redirect("/")
-
- def dispatch(self, request, handle=None, post_id=None, *args, **kwargs):
- self.post_obj = None
- if handle and post_id:
- # Make sure the request identity owns the post!
- if handle != request.identity.handle:
- raise PermissionDenied("Post author is not requestor")
-
- self.post_obj = get_object_or_404(request.identity.posts, pk=post_id)
-
- # 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
- return context