Copy Emoji to local and delete file with record (#407)
This commit is contained in:
parent
feb7a673eb
commit
21d565d282
|
@ -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):
|
||||||
|
|
|
@ -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"]:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue