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 import forms
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import redirect
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import FormView from django.views.generic import FormView
from activities.models import ( from activities.models import Post, PostAttachment, PostAttachmentStates, TimelineEvent
Post,
PostAttachment,
PostAttachmentStates,
PostStates,
TimelineEvent,
)
from core.files import blurhash_image, resize_image from core.files import blurhash_image, resize_image
from core.html import FediverseHtmlParser
from core.models import Config from core.models import Config
from users.shortcuts import by_handle_for_user_or_404 from users.views.base import IdentityViewMixin
from django.contrib.auth.decorators import login_required
@method_decorator(login_required, name="dispatch") class Compose(IdentityViewMixin, FormView):
class Compose(FormView):
template_name = "activities/compose.html" template_name = "activities/compose.html"
class form_class(forms.Form): class form_class(forms.Form):
@ -33,6 +23,7 @@ class Compose(FormView):
}, },
) )
) )
visibility = forms.ChoiceField( visibility = forms.ChoiceField(
choices=[ choices=[
(Post.Visibilities.public, "Public"), (Post.Visibilities.public, "Public"),
@ -42,6 +33,7 @@ class Compose(FormView):
(Post.Visibilities.mentioned, "Mentioned Only"), (Post.Visibilities.mentioned, "Mentioned Only"),
], ],
) )
content_warning = forms.CharField( content_warning = forms.CharField(
required=False, required=False,
label=Config.lazy_system_value("content_warning_text"), 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", 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): def __init__(self, identity, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -102,90 +125,75 @@ class Compose(FormView):
) )
return text 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): def get_form(self, form_class=None):
return self.form_class(identity=self.identity, **self.get_form_kwargs()) return self.form_class(identity=self.identity, **self.get_form_kwargs())
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
if self.post_obj: initial["visibility"] = self.identity.config_identity.default_post_visibility
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
)
return initial return initial
def form_valid(self, form): 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 = [] attachments = []
if "attachment" in self.request.POST: if form.cleaned_data.get("image"):
attachments = PostAttachment.objects.filter( main_file = resize_image(
pk__in=self.request.POST.getlist("attachment", []) form.cleaned_data["image"],
size=(2000, 2000),
cover=False,
) )
# Dispatch based on edit or not thumbnail_file = resize_image(
if self.post_obj: form.cleaned_data["image"],
self.post_obj.edit_local( size=(400, 225),
content=form.cleaned_data["text"], cover=True,
summary=form.cleaned_data.get("content_warning"),
visibility=form.cleaned_data["visibility"],
attachments=attachments,
) )
self.post_obj.transition_perform(PostStates.edited) attachment = PostAttachment.objects.create(
else: blurhash=blurhash_image(thumbnail_file),
post = Post.create_local( 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, 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 attachment.file.save(
TimelineEvent.add_post(self.identity, post) main_file.name,
return redirect(self.identity.urls.view) main_file,
)
def dispatch(self, request, handle=None, post_id=None, *args, **kwargs): attachment.thumbnail.save(
self.identity = by_handle_for_user_or_404(self.request, handle) thumbnail_file.name,
self.post_obj = None thumbnail_file,
if handle and post_id: )
self.post_obj = get_object_or_404(self.identity.posts, pk=post_id) attachment.save()
attachments.append(attachment)
# Grab the reply-to post info now # Create the post
self.reply_to = None post = Post.create_local(
reply_to_id = request.POST.get("reply_to") or request.GET.get("reply_to") author=self.identity,
if reply_to_id: content=form.cleaned_data["text"],
try: summary=form.cleaned_data.get("content_warning"),
self.reply_to = Post.objects.get(pk=reply_to_id) visibility=form.cleaned_data["visibility"],
except Post.DoesNotExist: attachments=attachments,
pass )
# Keep going with normal rendering # Add their own timeline event for immediate visibility
return super().dispatch(request, *args, **kwargs) TimelineEvent.add_post(self.identity, post)
messages.success(self.request, "Your post was created.")
return redirect(".")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["reply_to"] = self.reply_to
context["identity"] = self.identity context["identity"] = self.identity
context["section"] = "compose" context["section"] = "compose"
if self.post_obj:
context["post"] = self.post_obj
return context return context

View file

@ -1,15 +1,13 @@
from django.core.exceptions import PermissionDenied
from django.http import Http404, JsonResponse 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.utils.decorators import method_decorator
from django.views.decorators.vary import vary_on_headers 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 activities.services import PostService
from core.decorators import cache_page_by_ap_json from core.decorators import cache_page_by_ap_json
from core.ld import canonicalise from core.ld import canonicalise
from django.contrib.auth.decorators import login_required
from users.models import Identity from users.models import Identity
from users.shortcuts import by_handle_or_404 from users.shortcuts import by_handle_or_404

View file

@ -1,15 +1,12 @@
from typing import Any from django.contrib.auth.decorators import login_required
from django.http import Http404
from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import ListView, TemplateView 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 activities.services import TimelineService
from core.decorators import cache_page from core.decorators import cache_page
from django.contrib.auth.decorators import login_required from users.models import Identity
from users.models import Bookmark, HashtagFollow, Identity
from users.views.base import IdentityViewMixin from users.views.base import IdentityViewMixin

View file

@ -1,8 +1,8 @@
# Generated by Django 4.2 on 2023-04-29 18:49 # Generated by Django 4.2 on 2023-04-29 18:49
import django.db.models.deletion
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):

View file

@ -1,14 +1,11 @@
import json
from typing import ClassVar from typing import ClassVar
import markdown_it import markdown_it
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.templatetags.static import static
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.safestring import mark_safe 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.generic import TemplateView, View
from django.views.static import serve from django.views.static import serve

View file

@ -28,7 +28,7 @@
{% if post.summary %} {% 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"> <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> </div>
{% endif %} {% endif %}

View file

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

View file

@ -29,7 +29,7 @@
<p> <p>
To see your timelines, compose and edit messages, and follow people, To see your timelines, compose and edit messages, and follow people,
you will need to use a Mastodon-compatible app. Our favourites 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> </p>
<div class="flex-icons"> <div class="flex-icons">
<a href="https://elk.zone/"> <a href="https://elk.zone/">

View file

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

View file

@ -1,7 +1,6 @@
import time import time
import pytest import pytest
from django.conf import settings
from django.test import Client from django.test import Client
from api.models import Application, Token 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.decorators import user_passes_test
from django.contrib.auth.views import redirect_to_login
from django.http import HttpResponseRedirect
def moderator_required(function): def moderator_required(function):

View file

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

View file

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

View file

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

View file

@ -1,10 +1,10 @@
from django import forms from django import forms
from django.core.files import File
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db import models from django.db import models
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import FormView, TemplateView from django.views.generic import FormView, TemplateView
from django.core.files import File
from core.models import Config from core.models import Config
from users.decorators import admin_required 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.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import View from django.views.generic import View
from django.contrib.auth.decorators import login_required
from users.models import Announcement from users.models import Announcement
from users.services import AnnouncementService 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.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") @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.feedgenerator import Rss201rev2Feed
from django.utils.xmlutils import SimplerXMLGenerator from django.utils.xmlutils import SimplerXMLGenerator
from django.views.decorators.vary import vary_on_headers 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.models import Post
from activities.services import TimelineService from activities.services import SearchService, TimelineService
from core.decorators import cache_page, cache_page_by_ap_json from core.decorators import cache_page, cache_page_by_ap_json
from core.ld import canonicalise from core.ld import canonicalise
from core.models import Config from core.models import Config
from django.contrib.auth.decorators import login_required
from users.models import Domain, FollowStates, Identity, IdentityStates from users.models import Domain, FollowStates, Identity, IdentityStates
from users.services import IdentityService from users.services import IdentityService
from activities.services import SearchService
from users.shortcuts import by_handle_or_404 from users.shortcuts import by_handle_or_404

View file

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

View file

@ -1,13 +1,12 @@
import csv 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.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import FormView, View from django.views.generic import FormView, View
from django.contrib.auth.decorators import login_required
from users.models import Follow, InboxMessage from users.models import Follow, InboxMessage
from users.views.base import IdentityViewMixin from users.views.base import IdentityViewMixin

View file

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

View file

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

View file

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