Fix linting, restore one image to compose

This commit is contained in:
Andrew Godwin 2023-05-03 11:41:52 -06:00
parent 32e74a620d
commit 2e605ca9a9
23 changed files with 131 additions and 141 deletions

View file

@ -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 <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)
@ -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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -28,7 +28,7 @@
{% if post.summary %}
<div class="summary" _="on click or keyup[key is 'Enter'] toggle .enabled on <.{{ post.summary_class }} .summary/> then toggle .hidden on <.{{ post.summary_class }} .content/> then halt" tabindex="0">
{{ post.summary }}
{{ post.summary }}
</div>
{% endif %}

View file

@ -3,20 +3,20 @@
{% block title %}Compose{% endblock %}
{% block settings_content %}
<form action="." method="POST">
<form action="." method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<legend>Compose</legend>
{% if reply_to %}
<label>Replying to</label>
{% include "activities/_mini_post.html" with post=reply_to %}
{% endif %}
<p><i>For more advanced posting options, like editing and image uploads, please use an app.</i></p>
{{ form.reply_to }}
<p><i>For more advanced posting options, like editing and multiple image uploads, please use an app.</i></p>
{{ form.id }}
{% include "forms/_field.html" with field=form.text %}
{% include "forms/_field.html" with field=form.content_warning %}
{% include "forms/_field.html" with field=form.visibility %}
{% include "forms/_field.html" with field=form.content_warning %}
</fieldset>
<fieldset>
<legend>Image</legend>
{% include "forms/_field.html" with field=form.image %}
{% include "forms/_field.html" with field=form.image_caption %}
</fieldset>
<div class="buttons">
<span id="character-counter">{{ config.post_length }}</span>

View file

@ -29,7 +29,7 @@
<p>
To see your timelines, compose and edit messages, and follow people,
you will need to use a Mastodon-compatible app. Our favourites
are listed below.
are listed below, but there's lots more options out there!
</p>
<div class="flex-icons">
<a href="https://elk.zone/">

View file

@ -2,8 +2,6 @@ import pytest
from django.test.client import Client
from pytest_django.asserts import assertContains
from activities.models import Post
from core.models import Config
from users.models import Identity

View file

@ -1,7 +1,6 @@
import time
import pytest
from django.conf import settings
from django.test import Client
from api.models import Application, Token

View file

@ -1,8 +1,4 @@
from functools import wraps
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.views import redirect_to_login
from django.http import HttpResponseRedirect
def moderator_required(function):

View file

@ -11,7 +11,7 @@ class DomainMiddleware:
def __call__(self, request):
request.domain = None
if "HTTP_HOST" in request.META:
request.domain = Domain.get_domain(request.META["HTTP_HOST"])
if "host" in request.headers:
request.domain = Domain.get_domain(request.headers["host"])
response = self.get_response(request)
return response

View file

@ -1,7 +1,7 @@
import json
import ssl
from typing import Optional
from functools import cached_property
from typing import Optional
import httpx
import pydantic
@ -11,9 +11,9 @@ from django.conf import settings
from django.db import models
from core.exceptions import capture_message
from core.models import Config
from stator.models import State, StateField, StateGraph, StatorModel
from users.schemas import NodeInfo
from core.models import Config
class DomainStates(StateGraph):

View file

@ -1,9 +1,11 @@
import urlman
from functools import cached_property
from core.models import Config
import urlman
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
from core.models import Config
class UserManager(BaseUserManager):
"""

View file

@ -1,10 +1,10 @@
from django import forms
from django.core.files import File
from django.core.validators import RegexValidator
from django.db import models
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.views.generic import FormView, TemplateView
from django.core.files import File
from core.models import Config
from users.decorators import admin_required

View file

@ -1,9 +1,9 @@
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views.generic import View
from django.contrib.auth.decorators import login_required
from users.models import Announcement
from users.services import AnnouncementService

View file

@ -1,6 +1,7 @@
from users.shortcuts import by_handle_for_user_or_404
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from users.shortcuts import by_handle_for_user_or_404
@method_decorator(login_required, name="dispatch")

View file

@ -10,17 +10,15 @@ from django.utils.decorators import method_decorator
from django.utils.feedgenerator import Rss201rev2Feed
from django.utils.xmlutils import SimplerXMLGenerator
from django.views.decorators.vary import vary_on_headers
from django.views.generic import FormView, ListView, TemplateView, View
from django.views.generic import FormView, ListView
from activities.models import Post, PostInteraction
from activities.services import TimelineService
from activities.models import Post
from activities.services import SearchService, TimelineService
from core.decorators import cache_page, cache_page_by_ap_json
from core.ld import canonicalise
from core.models import Config
from django.contrib.auth.decorators import login_required
from users.models import Domain, FollowStates, Identity, IdentityStates
from users.services import IdentityService
from activities.services import SearchService
from users.shortcuts import by_handle_or_404

View file

@ -1,8 +1,6 @@
from django.db import models
from django.utils.decorators import method_decorator
from django.views.generic import ListView
from django.contrib.auth.decorators import login_required
from users.models import Follow, FollowStates, IdentityStates
from users.views.base import IdentityViewMixin

View file

@ -1,13 +1,12 @@
import csv
from typing import Any
from django import forms, http
from django import forms
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views.generic import FormView, View
from django.contrib.auth.decorators import login_required
from users.models import Follow, InboxMessage
from users.views.base import IdentityViewMixin

View file

@ -1,4 +1,5 @@
from django import forms
from django.contrib.auth.decorators import login_required
from django.core.files import File
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
@ -6,10 +7,9 @@ from django.views.generic import FormView
from core.html import FediverseHtmlParser
from core.models.config import Config
from django.contrib.auth.decorators import login_required
from users.models import IdentityStates
from users.shortcuts import by_handle_or_404
from users.services import IdentityService
from users.shortcuts import by_handle_or_404
@method_decorator(login_required, name="dispatch")

View file

@ -1,9 +1,8 @@
from django import forms
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import FormView
from django.contrib.auth.decorators import login_required
@method_decorator(login_required, name="dispatch")
class SecurityPage(FormView):

View file

@ -2,13 +2,13 @@ from functools import partial
from typing import ClassVar
from django import forms
from django.contrib.auth.decorators import login_required
from django.core.files import File
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views.generic import FormView
from core.models.config import Config, UploadedImage
from django.contrib.auth.decorators import login_required
from users.shortcuts import by_handle_or_404