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"] 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") @admin.action(description="Force Execution")
def force_execution(self, request, queryset): def force_execution(self, request, queryset):
@ -96,6 +104,17 @@ class EmojiAdmin(admin.ModelAdmin):
f'<img src="{instance.full_url().relative}" style="height: 22px">' 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) @admin.register(PostAttachment)
class PostAttachmentAdmin(admin.ModelAdmin): class PostAttachmentAdmin(admin.ModelAdmin):

View file

@ -9,6 +9,7 @@ from asgiref.sync import sync_to_async
from cachetools import TTLCache, cached from cachetools import TTLCache, cached
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from django.db import models from django.db import models
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -131,9 +132,15 @@ class Emoji(StatorModel):
admin_delete = "{admin}{self.pk}/delete/" admin_delete = "{admin}{self.pk}/delete/"
admin_enable = "{admin}{self.pk}/enable/" admin_enable = "{admin}{self.pk}/enable/"
admin_disable = "{admin}{self.pk}/disable/" admin_disable = "{admin}{self.pk}/disable/"
admin_copy = "{admin}{self.pk}/copy/"
emoji_regex = re.compile(r"\B:([a-zA-Z0-9(_)-]+):\B") 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): def clean(self):
super().clean() super().clean()
if self.local ^ (self.domain is None): if self.local ^ (self.domain is None):
@ -195,6 +202,40 @@ class Emoji(StatorModel):
) )
return self.fullcode 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 @classmethod
def emojis_from_content(cls, content: str, domain: Domain | None) -> list["Emoji"]: 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>/enable/", admin.EmojiEnable.as_view()),
path("admin/emoji/<pk>/disable/", admin.EmojiEnable.as_view(enable=False)), path("admin/emoji/<pk>/disable/", admin.EmojiEnable.as_view(enable=False)),
path("admin/emoji/<pk>/delete/", admin.EmojiDelete.as_view()), path("admin/emoji/<pk>/delete/", admin.EmojiDelete.as_view()),
path("admin/emoji/<pk>/copy/", admin.EmojiCopyLocal.as_view()),
path( path(
"admin/announcements/", "admin/announcements/",
admin.AnnouncementsRoot.as_view(), admin.AnnouncementsRoot.as_view(),

View file

@ -32,6 +32,9 @@
<td> <td>
</td> </td>
<td class="actions"> <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 %} {% if not emoji.is_usable %}
<span class="bad">Disabled</span> <span class="bad">Disabled</span>
<a hx-post="{{ emoji.urls.admin_enable }}" title="Enable"><i class="fa-solid fa-circle-check"></i></a> <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, Domains,
) )
from users.views.admin.emoji import ( # noqa from users.views.admin.emoji import ( # noqa
EmojiCopyLocal,
EmojiCreate, EmojiCreate,
EmojiDelete, EmojiDelete,
EmojiEnable, EmojiEnable,

View file

@ -99,3 +99,18 @@ class EmojiEnable(HTMXActionView):
def action(self, emoji: Emoji): def action(self, emoji: Emoji):
emoji.public = self.enable emoji.public = self.enable
emoji.save() 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()