diff --git a/activities/views/compose.py b/activities/views/compose.py
index d8cfb41..4da4154 100644
--- a/activities/views/compose.py
+++ b/activities/views/compose.py
@@ -1,27 +1,17 @@
from django import forms
from django.conf import settings
-from django.core.exceptions import PermissionDenied
-from django.shortcuts import get_object_or_404, redirect, render
+from django.contrib import messages
+from django.shortcuts import redirect
from django.utils import timezone
-from django.utils.decorators import method_decorator
from django.views.generic import FormView
-from activities.models import (
- Post,
- PostAttachment,
- PostAttachmentStates,
- PostStates,
- TimelineEvent,
-)
+from activities.models import Post, PostAttachment, PostAttachmentStates, TimelineEvent
from core.files import blurhash_image, resize_image
-from core.html import FediverseHtmlParser
from core.models import Config
-from users.shortcuts import by_handle_for_user_or_404
-from django.contrib.auth.decorators import login_required
+from users.views.base import IdentityViewMixin
-@method_decorator(login_required, name="dispatch")
-class Compose(FormView):
+class Compose(IdentityViewMixin, FormView):
template_name = "activities/compose.html"
class form_class(forms.Form):
@@ -33,6 +23,7 @@ class Compose(FormView):
},
)
)
+
visibility = forms.ChoiceField(
choices=[
(Post.Visibilities.public, "Public"),
@@ -42,6 +33,7 @@ class Compose(FormView):
(Post.Visibilities.mentioned, "Mentioned Only"),
],
)
+
content_warning = forms.CharField(
required=False,
label=Config.lazy_system_value("content_warning_text"),
@@ -52,7 +44,38 @@ class Compose(FormView):
),
help_text="Optional - Post will be hidden behind this text until clicked",
)
- reply_to = forms.CharField(widget=forms.HiddenInput(), required=False)
+
+ 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
+ make called errorlist
+ make 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
+ 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)
@@ -102,90 +125,75 @@ class Compose(FormView):
)
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()
- 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,
- "content_warning": self.post_obj.summary,
- }
- )
- else:
- initial[
- "visibility"
- ] = self.identity.config_identity.default_post_visibility
- if self.reply_to:
- initial["reply_to"] = self.reply_to.pk
- initial["visibility"] = self.reply_to.visibility
- initial["content_warning"] = self.reply_to.summary
- # Build a set of mentions for the content to start as
- mentioned = {self.reply_to.author}
- mentioned.update(self.reply_to.mentions.all())
- mentioned.discard(self.identity)
- initial["text"] = "".join(
- f"@{identity.handle} "
- for identity in mentioned
- if identity.username
- )
+ initial["visibility"] = self.identity.config_identity.default_post_visibility
return initial
def form_valid(self, form):
- # Gather any attachment objects now, they're not in the form proper
+ # See if we need to make an image attachment
attachments = []
- if "attachment" in self.request.POST:
- attachments = PostAttachment.objects.filter(
- pk__in=self.request.POST.getlist("attachment", [])
+ if form.cleaned_data.get("image"):
+ main_file = resize_image(
+ form.cleaned_data["image"],
+ size=(2000, 2000),
+ cover=False,
)
- # 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,
+ thumbnail_file = resize_image(
+ form.cleaned_data["image"],
+ size=(400, 225),
+ cover=True,
)
- self.post_obj.transition_perform(PostStates.edited)
- else:
- post = Post.create_local(
+ 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,
- 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.identity, post)
- return redirect(self.identity.urls.view)
-
- def dispatch(self, request, handle=None, post_id=None, *args, **kwargs):
- self.identity = by_handle_for_user_or_404(self.request, handle)
- self.post_obj = None
- if handle and post_id:
- self.post_obj = get_object_or_404(self.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)
+ 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["reply_to"] = self.reply_to
context["identity"] = self.identity
context["section"] = "compose"
- if self.post_obj:
- context["post"] = self.post_obj
return context
diff --git a/activities/views/posts.py b/activities/views/posts.py
index 99539fa..797a6df 100644
--- a/activities/views/posts.py
+++ b/activities/views/posts.py
@@ -1,15 +1,13 @@
-from django.core.exceptions import PermissionDenied
from django.http import Http404, JsonResponse
-from django.shortcuts import get_object_or_404, redirect, render
+from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.views.decorators.vary import vary_on_headers
-from django.views.generic import TemplateView, View
+from django.views.generic import TemplateView
-from activities.models import Post, PostInteraction, PostStates
+from activities.models import Post, PostStates
from activities.services import PostService
from core.decorators import cache_page_by_ap_json
from core.ld import canonicalise
-from django.contrib.auth.decorators import login_required
from users.models import Identity
from users.shortcuts import by_handle_or_404
diff --git a/activities/views/timelines.py b/activities/views/timelines.py
index 4f8dad4..20ba613 100644
--- a/activities/views/timelines.py
+++ b/activities/views/timelines.py
@@ -1,15 +1,12 @@
-from typing import Any
-from django.http import Http404
-from django.core.paginator import Paginator
+from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.views.generic import ListView, TemplateView
-from activities.models import Hashtag, PostInteraction, TimelineEvent
+from activities.models import Hashtag, TimelineEvent
from activities.services import TimelineService
from core.decorators import cache_page
-from django.contrib.auth.decorators import login_required
-from users.models import Bookmark, HashtagFollow, Identity
+from users.models import Identity
from users.views.base import IdentityViewMixin
diff --git a/core/migrations/0002_domain_config.py b/core/migrations/0002_domain_config.py
index 3d220b4..e5305a2 100644
--- a/core/migrations/0002_domain_config.py
+++ b/core/migrations/0002_domain_config.py
@@ -1,8 +1,8 @@
# Generated by Django 4.2 on 2023-04-29 18:49
+import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
-import django.db.models.deletion
class Migration(migrations.Migration):
diff --git a/core/views.py b/core/views.py
index 7fae532..4171736 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1,14 +1,11 @@
-import json
from typing import ClassVar
import markdown_it
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import redirect
-from django.templatetags.static import static
from django.utils.decorators import method_decorator
from django.utils.safestring import mark_safe
-from django.views.decorators.cache import cache_control
from django.views.generic import TemplateView, View
from django.views.static import serve
diff --git a/templates/activities/_post.html b/templates/activities/_post.html
index f3219ac..6834ffb 100644
--- a/templates/activities/_post.html
+++ b/templates/activities/_post.html
@@ -28,7 +28,7 @@
{% if post.summary %}
- {{ post.summary }}
+ {{ post.summary }}
{% endif %}
diff --git a/templates/activities/compose.html b/templates/activities/compose.html
index b7b4cdf..2f2eaed 100644
--- a/templates/activities/compose.html
+++ b/templates/activities/compose.html
@@ -3,20 +3,20 @@
{% block title %}Compose{% endblock %}
{% block settings_content %}
-