Copy Emoji to local and delete file with record (#407)

This commit is contained in:
Michael Manfre 2023-01-14 12:35:20 -05:00 committed by GitHub
parent feb7a673eb
commit 21d565d282
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 1 deletions

View file

@ -73,7 +73,15 @@ class EmojiAdmin(admin.ModelAdmin):
readonly_fields = ["preview", "created", "updated", "to_ap_tag"]
actions = ["force_execution", "approve_emoji", "reject_emoji"]
actions = ["force_execution", "approve_emoji", "reject_emoji", "copy_to_local"]
def delete_queryset(self, request, queryset):
for instance in queryset:
# individual deletes to ensure file is deleted
instance.delete()
def delete_model(self, request, obj):
super().delete_model(request, obj)
@admin.action(description="Force Execution")
def force_execution(self, request, queryset):
@ -96,6 +104,17 @@ class EmojiAdmin(admin.ModelAdmin):
f'<img src="{instance.full_url().relative}" style="height: 22px">'
)
@admin.action(description="Copy Emoji to Local")
def copy_to_local(self, request, queryset):
emojis = {}
for instance in queryset:
emoji = instance.copy_to_local(save=False)
if emoji:
emojis[emoji.shortcode] = emoji
Emoji.objects.bulk_create(emojis.values(), batch_size=50, ignore_conflicts=True)
Emoji.locals = Emoji.load_locals()
@admin.register(PostAttachment)
class PostAttachmentAdmin(admin.ModelAdmin):

View file

@ -9,6 +9,7 @@ from asgiref.sync import sync_to_async
from cachetools import TTLCache, cached
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from django.db import models
from django.utils.safestring import mark_safe
@ -131,9 +132,15 @@ class Emoji(StatorModel):
admin_delete = "{admin}{self.pk}/delete/"
admin_enable = "{admin}{self.pk}/enable/"
admin_disable = "{admin}{self.pk}/disable/"
admin_copy = "{admin}{self.pk}/copy/"
emoji_regex = re.compile(r"\B:([a-zA-Z0-9(_)-]+):\B")
def delete(self, using=None, keep_parents=False):
if self.file:
self.file.delete()
return super().delete(using=using, keep_parents=keep_parents)
def clean(self):
super().clean()
if self.local ^ (self.domain is None):
@ -195,6 +202,40 @@ class Emoji(StatorModel):
)
return self.fullcode
@property
def can_copy_local(self):
if not hasattr(Emoji, "locals"):
Emoji.locals = Emoji.load_locals()
return not self.local and self.is_usable and self.shortcode not in Emoji.locals
def copy_to_local(self, *, save: bool = True):
"""
Copy this (non-local) Emoji to local for use by Users of this instance. Returns
the Emoji instance, or None if the copy failed to happen. Specify save=False to
return the object without saving to database (for bulk saving).
"""
if not self.can_copy_local:
return None
emoji = None
if self.file:
# new emoji gets its own copy of the file
file = ContentFile(self.file.read())
file.name = self.file.name
emoji = Emoji(
shortcode=self.shortcode,
domain=None,
local=True,
mimetype=self.mimetype,
file=file,
category=self.category,
)
if save:
emoji.save()
# add this new one to the locals cache
Emoji.locals[self.shortcode] = emoji
return emoji
@classmethod
def emojis_from_content(cls, content: str, domain: Domain | None) -> list["Emoji"]:
"""

View file

@ -173,6 +173,7 @@ urlpatterns = [
path("admin/emoji/<pk>/enable/", admin.EmojiEnable.as_view()),
path("admin/emoji/<pk>/disable/", admin.EmojiEnable.as_view(enable=False)),
path("admin/emoji/<pk>/delete/", admin.EmojiDelete.as_view()),
path("admin/emoji/<pk>/copy/", admin.EmojiCopyLocal.as_view()),
path(
"admin/announcements/",
admin.AnnouncementsRoot.as_view(),

View file

@ -32,6 +32,9 @@
<td>
</td>
<td class="actions">
{% if emoji.can_copy_local %}
<a hx-post="{{ emoji.urls.admin_copy }}" title="Copy to Local"><i class="fa-solid fa-copy"></i></a>
{% endif %}
{% if not emoji.is_usable %}
<span class="bad">Disabled</span>
<a hx-post="{{ emoji.urls.admin_enable }}" title="Enable"><i class="fa-solid fa-circle-check"></i></a>

View file

@ -17,6 +17,7 @@ from users.views.admin.domains import ( # noqa
Domains,
)
from users.views.admin.emoji import ( # noqa
EmojiCopyLocal,
EmojiCreate,
EmojiDelete,
EmojiEnable,

View file

@ -99,3 +99,18 @@ class EmojiEnable(HTMXActionView):
def action(self, emoji: Emoji):
emoji.public = self.enable
emoji.save()
@method_decorator(moderator_required, name="dispatch")
class EmojiCopyLocal(HTMXActionView):
"""
Duplicates a domain emoji to be local, as long as it is usable and the
shortcode is available.
"""
model = Emoji
def action(self, emoji: Emoji):
# Force reload locals cache to avoid potential for shortcode dupes
Emoji.locals = Emoji.load_locals()
emoji.copy_to_local()