音楽で楽しみましょう!-Let's have fun with music!-

Signed-off-by: Shin'ya Minazuki <shinyoukai@laidback.moe>
This commit is contained in:
Shin'ya Minazuki 2026-01-24 16:16:49 -03:00
commit 54c6d22102
517 changed files with 637 additions and 639 deletions

View file

@ -1,15 +0,0 @@
from . import (
create_actors,
delete_pre_017_federated_uploads,
django_permissions_to_user_permissions,
migrate_to_user_libraries,
test,
)
__all__ = [
"create_actors",
"django_permissions_to_user_permissions",
"migrate_to_user_libraries",
"delete_pre_017_federated_uploads",
"test",
]

View file

@ -1,21 +0,0 @@
"""
Compute different sizes of image used for Album covers and User avatars
"""
from django.db.utils import IntegrityError
from funkwhale_api.users.models import User, create_actor
def main(command, **kwargs):
qs = User.objects.filter(actor__isnull=True).order_by("username")
total = len(qs)
command.stdout.write(f"{total} users found without actors")
for i, user in enumerate(qs):
command.stdout.write(f"{i + 1}/{total} creating actor for {user.username}")
try:
user.actor = create_actor(user)
except IntegrityError as e:
# somehow, an actor with the the url exists in the database
command.stderr.write(f"Error while creating actor: {str(e)}")
continue
user.save(update_fields=["actor"])

View file

@ -1,28 +0,0 @@
"""
Compute different sizes of image used for Album covers and User avatars
"""
from versatileimagefield.image_warmer import VersatileImageFieldWarmer
from funkwhale_api.common.models import Attachment
MODELS = [
(Attachment, "file", "attachment_square"),
]
def main(command, **kwargs):
for model, attribute, key_set in MODELS:
qs = model.objects.exclude(**{f"{attribute}__isnull": True})
qs = qs.exclude(**{attribute: ""})
warmer = VersatileImageFieldWarmer(
instance_or_queryset=qs,
rendition_key_set=key_set,
image_attr=attribute,
verbose=True,
)
command.stdout.write(f"Creating images for {model.__name__} / {attribute}")
num_created, failed_to_create = warmer.warm()
command.stdout.write(
f" {num_created} created, {len(failed_to_create)} in error"
)

View file

@ -1,14 +0,0 @@
"""
Compute different sizes of image used for Album covers and User avatars
"""
from funkwhale_api.music.models import Upload
def main(command, **kwargs):
queryset = Upload.objects.filter(
source__startswith="http", source__contains="/federation/music/file/"
).exclude(source__contains="youtube")
total = queryset.count()
command.stdout.write(f"{total} uploads found")
queryset.delete()

View file

@ -1,28 +0,0 @@
"""
Convert django permissions to user permissions in the database,
following the work done in #152.
"""
from django.contrib.auth.models import Permission
from django.db.models import Q
from funkwhale_api.users import models
mapping = {
"dynamic_preferences.change_globalpreferencemodel": "settings",
"music.add_importbatch": "library",
}
def main(command, **kwargs):
for codename, user_permission in sorted(mapping.items()):
app_label, c = codename.split(".")
p = Permission.objects.get(content_type__app_label=app_label, codename=c)
users = models.User.objects.filter(
Q(groups__permissions=p) | Q(user_permissions=p)
).distinct()
total = users.count()
command.stdout.write(
f"Updating {total} users with {user_permission} permission..."
)
users.update(**{f"permission_{user_permission}": True})

View file

@ -1,155 +0,0 @@
"""
Mirate instance files to a library #463. For each user that imported music on an
instance, we will create a "default" library with related files and an instance-level
visibility (unless instance has common__api_authentication_required set to False,
in which case the libraries will be public).
Files without any import job will be bounded to a "default" library on the first
superuser account found. This should now happen though.
This command will also generate federation ids for existing resources.
"""
from django.conf import settings
from django.db.models import CharField, F, Value, functions
from funkwhale_api.common import preferences
from funkwhale_api.federation import models as federation_models
from funkwhale_api.music import models
from funkwhale_api.users.models import User
def create_libraries(open_api, stdout):
local_actors = federation_models.Actor.objects.exclude(user=None).only("pk", "user")
privacy_level = "everyone" if open_api else "instance"
stdout.write(
"* Creating {} libraries with {} visibility".format(
len(local_actors), privacy_level
)
)
libraries_by_user = {}
for a in local_actors:
library, created = models.Library.objects.get_or_create(
name="default", actor=a, defaults={"privacy_level": privacy_level}
)
libraries_by_user[library.actor.user.pk] = library.pk
if created:
stdout.write(f" * Created library {library.pk} for user {a.user.pk}")
else:
stdout.write(
" * Found existing library {} for user {}".format(
library.pk, a.user.pk
)
)
return libraries_by_user
def update_uploads(libraries_by_user, stdout):
stdout.write("* Updating uploads with proper libraries...")
for user_id, library_id in libraries_by_user.items():
jobs = models.ImportJob.objects.filter(
upload__library=None, batch__submitted_by=user_id
)
candidates = models.Upload.objects.filter(
pk__in=jobs.values_list("upload", flat=True)
)
total = candidates.update(library=library_id, import_status="finished")
if total:
stdout.write(f" * Assigned {total} uploads to user {user_id}'s library")
else:
stdout.write(f" * No uploads to assign to user {user_id}'s library")
def update_orphan_uploads(open_api, stdout):
privacy_level = "everyone" if open_api else "instance"
first_superuser = (
User.objects.filter(is_superuser=True)
.exclude(actor=None)
.order_by("pk")
.first()
)
if not first_superuser:
stdout.write("* No superuser found, skipping update orphan uploads")
return
library, _ = models.Library.objects.get_or_create(
name="default",
actor=first_superuser.actor,
defaults={"privacy_level": privacy_level},
)
candidates = (
models.Upload.objects.filter(library=None, jobs__isnull=True)
.exclude(audio_file=None)
.exclude(audio_file="")
)
total = candidates.update(library=library, import_status="finished")
if total:
stdout.write(
"* Assigned {} orphaned uploads to superuser {}".format(
total, first_superuser.pk
)
)
else:
stdout.write("* No orphaned uploads found")
def set_fid(queryset, path, stdout):
model = queryset.model._meta.label
qs = queryset.filter(fid=None)
base_url = f"{settings.FUNKWHALE_URL}{path}"
stdout.write(f"* Assigning federation ids to {model} entries (path: {base_url})")
new_fid = functions.Concat(Value(base_url), F("uuid"), output_field=CharField())
total = qs.update(fid=new_fid)
stdout.write(f" * {total} entries updated")
def update_shared_inbox_url(stdout):
stdout.write("* Update shared inbox url for local actors...")
candidates = federation_models.Actor.objects.local()
url = federation_models.get_shared_inbox_url()
candidates.update(shared_inbox_url=url)
def generate_actor_urls(part, stdout):
field = f"{part}_url"
stdout.write(f"* Update {field} for local actors...")
queryset = federation_models.Actor.objects.local().filter(**{field: None})
base_url = f"{settings.FUNKWHALE_URL}/federation/actors/"
new_field = functions.Concat(
Value(base_url),
F("preferred_username"),
Value(f"/{part}"),
output_field=CharField(),
)
queryset.update(**{field: new_field})
def main(command, **kwargs):
open_api = not preferences.get("common__api_authentication_required")
libraries_by_user = create_libraries(open_api, command.stdout)
update_uploads(libraries_by_user, command.stdout)
update_orphan_uploads(open_api, command.stdout)
set_fid_params = [
(
models.Upload.objects.exclude(library__actor__user=None),
"/federation/music/uploads/",
),
(models.Artist.objects.all(), "/federation/music/artists/"),
(models.Album.objects.all(), "/federation/music/albums/"),
(models.Track.objects.all(), "/federation/music/tracks/"),
]
for qs, path in set_fid_params:
set_fid(qs, path, command.stdout)
update_shared_inbox_url(command.stdout)
for part in ["followers", "following"]:
generate_actor_urls(part, command.stdout)

View file

@ -1,8 +0,0 @@
"""
This is a test script that does nothing.
You can launch it just to check how it works.
"""
def main(command, **kwargs):
command.stdout.write("Test script run successfully")