音楽で楽しみましょう!-Let's have fun with music!-
Signed-off-by: Shin'ya Minazuki <shinyoukai@laidback.moe>
This commit is contained in:
parent
7c3206bf83
commit
54c6d22102
517 changed files with 637 additions and 639 deletions
0
api/funquail_api/common/management/__init__.py
Normal file
0
api/funquail_api/common/management/__init__.py
Normal file
0
api/funquail_api/common/management/commands/__init__.py
Normal file
0
api/funquail_api/common/management/commands/__init__.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import os
|
||||
|
||||
from django.contrib.auth.management.commands.createsuperuser import (
|
||||
Command as BaseCommand,
|
||||
)
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *apps_label, **options):
|
||||
"""
|
||||
Creating Django Superusers would bypass some of our username checks, which can lead to unexpected behaviour.
|
||||
We therefore prohibit the execution of the command.
|
||||
"""
|
||||
if not os.environ.get("FORCE") == "1":
|
||||
raise CommandError(
|
||||
"Running createsuperuser on your FunQuail instance bypasses some of our checks "
|
||||
"which can lead to unexpected behavior of your instance. We therefore suggest to "
|
||||
"run `funkwhale-manage fw users create --superuser` instead."
|
||||
)
|
||||
|
||||
return super().handle(*apps_label, **options)
|
||||
78
api/funquail_api/common/management/commands/gitpod.py
Normal file
78
api/funquail_api/common/management/commands/gitpod.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import os
|
||||
|
||||
import debugpy
|
||||
import uvicorn
|
||||
from django.core.management import call_command
|
||||
from django.core.management.commands.migrate import Command as BaseCommand
|
||||
|
||||
from funkwhale_api.common import preferences
|
||||
from funkwhale_api.music.models import Library
|
||||
from funkwhale_api.users.models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Manage gitpod environment"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("command", nargs="?", type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
command = options["command"]
|
||||
|
||||
if not command:
|
||||
return self.show_help()
|
||||
|
||||
if command == "init":
|
||||
return self.init()
|
||||
|
||||
if command == "dev":
|
||||
return self.dev()
|
||||
|
||||
def show_help(self):
|
||||
self.stdout.write("")
|
||||
self.stdout.write("Available commands:")
|
||||
self.stdout.write("init - Initialize gitpod workspace")
|
||||
self.stdout.write("dev - Run FunQuail in development mode with debug server")
|
||||
self.stdout.write("")
|
||||
|
||||
def init(self):
|
||||
user = User.objects.get(username="gitpod")
|
||||
|
||||
# Allow anonymous access
|
||||
preferences.set("common__api_authentication_required", False)
|
||||
|
||||
# Download music catalog
|
||||
os.system(
|
||||
"git clone https://dev.funkwhale.audio/funkwhale/catalog.git /tmp/catalog"
|
||||
)
|
||||
os.system("mv -f /tmp/catalog/music /workspace/funkwhale/data")
|
||||
os.system("rm -rf /tmp/catalog/music")
|
||||
|
||||
# Import music catalog into library
|
||||
call_command(
|
||||
"create_library",
|
||||
"gitpod",
|
||||
name="funkwhale/catalog",
|
||||
privacy_level="everyone",
|
||||
)
|
||||
call_command(
|
||||
"import_files",
|
||||
Library.objects.get(actor=user.actor).uuid,
|
||||
"/workspace/funkwhale/data/music/",
|
||||
recursive=True,
|
||||
in_place=True,
|
||||
no_input=False,
|
||||
)
|
||||
|
||||
def dev(self):
|
||||
debugpy.listen(5678)
|
||||
uvicorn.run(
|
||||
"config.asgi:application",
|
||||
host="0.0.0.0",
|
||||
port=5000,
|
||||
reload=True,
|
||||
reload_dirs=[
|
||||
"/workspace/funkwhale/api/config/",
|
||||
"/workspace/funkwhale/api/funkwhale_api/",
|
||||
],
|
||||
)
|
||||
95
api/funquail_api/common/management/commands/inplace_to_s3.py
Normal file
95
api/funquail_api/common/management/commands/inplace_to_s3.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import pathlib
|
||||
from argparse import RawTextHelpFormatter
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from funkwhale_api.music import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = """
|
||||
Update the reference for Uploads that have been imported with --in-place and are now moved to s3.
|
||||
|
||||
Please note: This does not move any file! Make sure you already moved the files to your s3 bucket.
|
||||
|
||||
Specify --source to filter the reference to update to files from a specific in-place directory. If no
|
||||
--source is given, all in-place imported track references will be updated.
|
||||
|
||||
Specify --target to specify a subdirectory in the S3 bucket where you moved the files. If no --target is
|
||||
given, the file is expected to be stored in the same path as before.
|
||||
|
||||
Examples:
|
||||
|
||||
Music File: /music/Artist/Album/track.ogg
|
||||
--source: /music
|
||||
--target unset
|
||||
|
||||
All files imported from /music will be updated and expected to be in the same folder structure in the bucket
|
||||
|
||||
Music File: /music/Artist/Album/track.ogg
|
||||
--source: /music
|
||||
--target: /in_place
|
||||
|
||||
The music file is expected to be stored in the bucket in the directory /in_place/Artist/Album/track.ogg
|
||||
"""
|
||||
|
||||
def create_parser(self, *args, **kwargs):
|
||||
parser = super().create_parser(*args, **kwargs)
|
||||
parser.formatter_class = RawTextHelpFormatter
|
||||
return parser
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--no-dry-run",
|
||||
action="store_false",
|
||||
dest="dry_run",
|
||||
default=True,
|
||||
help="Disable dry run mode and apply updates for real on the database",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
type=pathlib.Path,
|
||||
required=True,
|
||||
help="Specify the path of the directory where the files originally were stored to update their reference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--target",
|
||||
type=pathlib.Path,
|
||||
help="Specify a subdirectory in the S3 bucket where you moved the files to.",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
if options["dry_run"]:
|
||||
self.stdout.write("Dry-run on, will not touch the database")
|
||||
else:
|
||||
self.stdout.write("Dry-run off, *changing the database*")
|
||||
self.stdout.write("")
|
||||
|
||||
prefix = f"file://{options['source']}"
|
||||
|
||||
to_change = models.Upload.objects.filter(source__startswith=prefix)
|
||||
|
||||
self.stdout.write(f"Found {to_change.count()} uploads to update.")
|
||||
|
||||
target = options.get("target")
|
||||
if target is None:
|
||||
target = options["source"]
|
||||
|
||||
for upl in to_change:
|
||||
upl.audio_file = str(upl.source).replace(str(prefix), str(target))
|
||||
upl.source = None
|
||||
self.stdout.write(f"Upload expected in {upl.audio_file}")
|
||||
if not options["dry_run"]:
|
||||
upl.save()
|
||||
|
||||
self.stdout.write("")
|
||||
if options["dry_run"]:
|
||||
self.stdout.write(
|
||||
"Nothing was updated, rerun this command with --no-dry-run to apply the changes"
|
||||
)
|
||||
else:
|
||||
self.stdout.write("Updating completed!")
|
||||
|
||||
self.stdout.write("")
|
||||
322
api/funquail_api/common/management/commands/load_test_data.py
Normal file
322
api/funquail_api/common/management/commands/load_test_data.py
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
import math
|
||||
import random
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from funkwhale_api.federation import keys
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.tags import models as tags_models
|
||||
from funkwhale_api.users import models as users_models
|
||||
|
||||
BATCH_SIZE = 500
|
||||
|
||||
|
||||
def create_local_accounts(factories, count, dependencies):
|
||||
password = factories["users.User"].build().password
|
||||
users = factories["users.User"].build_batch(size=count)
|
||||
for user in users:
|
||||
# we set the hashed password by hand, because computing one for each user
|
||||
# is CPU intensive
|
||||
user.password = password
|
||||
users = users_models.User.objects.bulk_create(users, batch_size=BATCH_SIZE)
|
||||
actors = []
|
||||
domain = federation_models.Domain.objects.get_or_create(
|
||||
name=settings.FEDERATION_HOSTNAME
|
||||
)[0]
|
||||
users = [u for u in users if u.pk]
|
||||
private, public = keys.get_key_pair()
|
||||
for user in users:
|
||||
if not user.pk:
|
||||
continue
|
||||
actor = federation_models.Actor(
|
||||
private_key=private.decode("utf-8"),
|
||||
public_key=public.decode("utf-8"),
|
||||
**users_models.get_actor_data(user.username, domain=domain)
|
||||
)
|
||||
actors.append(actor)
|
||||
actors = federation_models.Actor.objects.bulk_create(actors, batch_size=BATCH_SIZE)
|
||||
for user, actor in zip(users, actors):
|
||||
user.actor = actor
|
||||
users_models.User.objects.bulk_update(users, ["actor"])
|
||||
return actors
|
||||
|
||||
|
||||
def create_taggable_items(dependency):
|
||||
def inner(factories, count, dependencies):
|
||||
objs = []
|
||||
tagged_objects = dependencies.get(
|
||||
dependency, list(CONFIG_BY_ID[dependency]["model"].objects.all().only("pk"))
|
||||
)
|
||||
tags = dependencies.get("tags", list(tags_models.Tag.objects.all().only("pk")))
|
||||
for i in range(count):
|
||||
tag = random.choice(tags)
|
||||
tagged_object = random.choice(tagged_objects)
|
||||
objs.append(
|
||||
factories["tags.TaggedItem"].build(
|
||||
content_object=tagged_object, tag=tag
|
||||
)
|
||||
)
|
||||
|
||||
return tags_models.TaggedItem.objects.bulk_create(
|
||||
objs, batch_size=BATCH_SIZE, ignore_conflicts=True
|
||||
)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
CONFIG = [
|
||||
{
|
||||
"id": "tracks",
|
||||
"model": music_models.Track,
|
||||
"factory": "music.Track",
|
||||
"factory_kwargs": {"artist": None, "album": None},
|
||||
"depends_on": [
|
||||
{"field": "album", "id": "albums", "default_factor": 0.1},
|
||||
{"field": "artist", "id": "artists", "default_factor": 0.05},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "albums",
|
||||
"model": music_models.Album,
|
||||
"factory": "music.Album",
|
||||
"factory_kwargs": {"artist": None},
|
||||
"depends_on": [{"field": "artist", "id": "artists", "default_factor": 0.3}],
|
||||
},
|
||||
{"id": "artists", "model": music_models.Artist, "factory": "music.Artist"},
|
||||
{
|
||||
"id": "local_accounts",
|
||||
"model": federation_models.Actor,
|
||||
"handler": create_local_accounts,
|
||||
},
|
||||
{
|
||||
"id": "local_libraries",
|
||||
"model": music_models.Library,
|
||||
"factory": "music.Library",
|
||||
"factory_kwargs": {"actor": None},
|
||||
"depends_on": [{"field": "actor", "id": "local_accounts", "default_factor": 1}],
|
||||
},
|
||||
{
|
||||
"id": "local_uploads",
|
||||
"model": music_models.Upload,
|
||||
"factory": "music.Upload",
|
||||
"factory_kwargs": {"import_status": "finished", "library": None, "track": None},
|
||||
"depends_on": [
|
||||
{
|
||||
"field": "library",
|
||||
"id": "local_libraries",
|
||||
"default_factor": 0.05,
|
||||
"queryset": music_models.Library.objects.all().select_related(
|
||||
"actor__user"
|
||||
),
|
||||
},
|
||||
{"field": "track", "id": "tracks", "default_factor": 1},
|
||||
],
|
||||
},
|
||||
{"id": "tags", "model": tags_models.Tag, "factory": "tags.Tag"},
|
||||
{
|
||||
"id": "track_tags",
|
||||
"model": tags_models.TaggedItem,
|
||||
"queryset": tags_models.TaggedItem.objects.filter(
|
||||
content_type__app_label="music", content_type__model="track"
|
||||
),
|
||||
"handler": create_taggable_items("tracks"),
|
||||
"depends_on": [
|
||||
{
|
||||
"field": "tag",
|
||||
"id": "tags",
|
||||
"default_factor": 0.1,
|
||||
"queryset": tags_models.Tag.objects.all(),
|
||||
"set": False,
|
||||
},
|
||||
{
|
||||
"field": "content_object",
|
||||
"id": "tracks",
|
||||
"default_factor": 1,
|
||||
"set": False,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "album_tags",
|
||||
"model": tags_models.TaggedItem,
|
||||
"queryset": tags_models.TaggedItem.objects.filter(
|
||||
content_type__app_label="music", content_type__model="album"
|
||||
),
|
||||
"handler": create_taggable_items("albums"),
|
||||
"depends_on": [
|
||||
{
|
||||
"field": "tag",
|
||||
"id": "tags",
|
||||
"default_factor": 0.1,
|
||||
"queryset": tags_models.Tag.objects.all(),
|
||||
"set": False,
|
||||
},
|
||||
{
|
||||
"field": "content_object",
|
||||
"id": "albums",
|
||||
"default_factor": 1,
|
||||
"set": False,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "artist_tags",
|
||||
"model": tags_models.TaggedItem,
|
||||
"queryset": tags_models.TaggedItem.objects.filter(
|
||||
content_type__app_label="music", content_type__model="artist"
|
||||
),
|
||||
"handler": create_taggable_items("artists"),
|
||||
"depends_on": [
|
||||
{
|
||||
"field": "tag",
|
||||
"id": "tags",
|
||||
"default_factor": 0.1,
|
||||
"queryset": tags_models.Tag.objects.all(),
|
||||
"set": False,
|
||||
},
|
||||
{
|
||||
"field": "content_object",
|
||||
"id": "artists",
|
||||
"default_factor": 1,
|
||||
"set": False,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
CONFIG_BY_ID = {c["id"]: c for c in CONFIG}
|
||||
|
||||
|
||||
class Rollback(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def create_objects(row, factories, count, **factory_kwargs):
|
||||
return factories[row["factory"]].build_batch(size=count, **factory_kwargs)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = """
|
||||
Inject demo data into your database. Useful for load testing, or setting up a demo instance.
|
||||
|
||||
Use with caution and only if you know what you are doing.
|
||||
"""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--no-dry-run",
|
||||
action="store_false",
|
||||
dest="dry_run",
|
||||
help="Commit the changes to the database",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--create-dependencies", action="store_true", dest="create_dependencies"
|
||||
)
|
||||
for row in CONFIG:
|
||||
parser.add_argument(
|
||||
"--{}".format(row["id"].replace("_", "-")),
|
||||
dest=row["id"],
|
||||
type=int,
|
||||
help="Number of {} objects to create".format(row["id"]),
|
||||
)
|
||||
dependencies = row.get("depends_on", [])
|
||||
for dependency in dependencies:
|
||||
parser.add_argument(
|
||||
"--{}-{}-factor".format(row["id"], dependency["field"]),
|
||||
dest="{}_{}_factor".format(row["id"], dependency["field"]),
|
||||
type=float,
|
||||
help="Number of {} objects to create per {} object".format(
|
||||
dependency["id"], row["id"]
|
||||
),
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from django.apps import apps
|
||||
|
||||
from funkwhale_api import factories
|
||||
|
||||
app_names = [app.name for app in apps.app_configs.values()]
|
||||
factories.registry.autodiscover(app_names)
|
||||
try:
|
||||
return self.inner_handle(*args, **options)
|
||||
except Rollback:
|
||||
pass
|
||||
|
||||
@transaction.atomic
|
||||
def inner_handle(self, *args, **options):
|
||||
results = {}
|
||||
for row in CONFIG:
|
||||
self.create_batch(row, results, options, count=options.get(row["id"]))
|
||||
|
||||
self.stdout.write("\nFinal state of database:\n\n")
|
||||
for row in CONFIG:
|
||||
qs = row.get("queryset", row["model"].objects.all())
|
||||
total = qs.count()
|
||||
self.stdout.write("- {} {} objects".format(total, row["id"]))
|
||||
|
||||
self.stdout.write("")
|
||||
if options["dry_run"]:
|
||||
self.stdout.write(
|
||||
"Run this command with --no-dry-run to commit the changes to the database"
|
||||
)
|
||||
raise Rollback()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Done!"))
|
||||
|
||||
def create_batch(self, row, results, options, count):
|
||||
from funkwhale_api import factories
|
||||
|
||||
if row["id"] in results:
|
||||
# already generated
|
||||
return results[row["id"]]
|
||||
if not count:
|
||||
return []
|
||||
dependencies = row.get("depends_on", [])
|
||||
create_dependencies = options.get("create_dependencies")
|
||||
for dependency in dependencies:
|
||||
dep_count = options.get(dependency["id"])
|
||||
if not create_dependencies and dep_count is None:
|
||||
continue
|
||||
if dep_count is None:
|
||||
factor = options[
|
||||
"{}_{}_factor".format(row["id"], dependency["field"])
|
||||
] or dependency.get("default_factor")
|
||||
dep_count = math.ceil(factor * count)
|
||||
|
||||
results[dependency["id"]] = self.create_batch(
|
||||
CONFIG_BY_ID[dependency["id"]], results, options, count=dep_count
|
||||
)
|
||||
self.stdout.write("Creating {} {}…".format(count, row["id"]))
|
||||
handler = row.get("handler")
|
||||
if handler:
|
||||
objects = handler(factories.registry, count, dependencies=results)
|
||||
else:
|
||||
objects = create_objects(
|
||||
row, factories.registry, count, **row.get("factory_kwargs", {})
|
||||
)
|
||||
for dependency in dependencies:
|
||||
if not dependency.get("set", True):
|
||||
continue
|
||||
if create_dependencies:
|
||||
candidates = results[dependency["id"]]
|
||||
else:
|
||||
# we use existing objects in the database
|
||||
queryset = dependency.get(
|
||||
"queryset", CONFIG_BY_ID[dependency["id"]]["model"].objects.all()
|
||||
)
|
||||
candidates = list(queryset.values_list("pk", flat=True))
|
||||
picked_pks = [random.choice(candidates) for _ in objects]
|
||||
picked_objects = {o.pk: o for o in queryset.filter(pk__in=picked_pks)}
|
||||
for i, obj in enumerate(objects):
|
||||
if create_dependencies:
|
||||
value = random.choice(candidates)
|
||||
else:
|
||||
value = picked_objects[picked_pks[i]]
|
||||
setattr(obj, dependency["field"], value)
|
||||
if not handler:
|
||||
objects = row["model"].objects.bulk_create(objects, batch_size=BATCH_SIZE)
|
||||
results[row["id"]] = objects
|
||||
return objects
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import os
|
||||
|
||||
from django.core.management.base import CommandError
|
||||
from django.core.management.commands.makemigrations import Command as BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *apps_label, **options):
|
||||
"""
|
||||
Running makemigrations in production can have desastrous consequences.
|
||||
|
||||
We ensure the command is disabled, unless a specific env var is provided.
|
||||
"""
|
||||
force = os.environ.get("FORCE") == "1"
|
||||
if not force:
|
||||
raise CommandError(
|
||||
"Running makemigrations on your FunQuail instance can have desastrous"
|
||||
" consequences. This command is disabled, and should only be run in "
|
||||
"development environments."
|
||||
)
|
||||
|
||||
return super().handle(*apps_label, **options)
|
||||
29
api/funquail_api/common/management/commands/migrate.py
Normal file
29
api/funquail_api/common/management/commands/migrate.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from django.core.management.commands.migrate import Command as BaseCommand
|
||||
|
||||
|
||||
def patch_write(buffer):
|
||||
"""
|
||||
Django is trying to help us when running migrate, by checking we don't have
|
||||
model changes not included in migrations. Unfortunately, running makemigrations
|
||||
on production instances create unwanted migrations and corrupt the database.
|
||||
|
||||
So we disabled the makemigrations command, and we're patching the
|
||||
write method to ensure misleading messages are never shown to the user,
|
||||
because https://github.com/django/django/blob/2.1.5/django/core/management/commands/migrate.py#L186
|
||||
does not leave an easy way to disable them.
|
||||
"""
|
||||
unpatched = buffer.write
|
||||
|
||||
def p(message, *args, **kwargs):
|
||||
if "'manage.py makemigrations'" in message or "not yet reflected" in message:
|
||||
return
|
||||
return unpatched(message, *args, **kwargs)
|
||||
|
||||
setattr(buffer, "write", p)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
patch_write(self.stdout)
|
||||
68
api/funquail_api/common/management/commands/script.py
Normal file
68
api/funquail_api/common/management/commands/script.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from funkwhale_api.common import scripts
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Run a specific script from funkwhale_api/common/scripts/"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("script_name", nargs="?", type=str)
|
||||
parser.add_argument(
|
||||
"--noinput",
|
||||
"--no-input",
|
||||
action="store_false",
|
||||
dest="interactive",
|
||||
help="Do NOT prompt the user for input of any kind.",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
name = options["script_name"]
|
||||
if not name:
|
||||
return self.show_help()
|
||||
|
||||
available_scripts = self.get_scripts()
|
||||
try:
|
||||
script = available_scripts[name]
|
||||
except KeyError:
|
||||
raise CommandError(
|
||||
"{} is not a valid script. Run funkwhale-manage script for a "
|
||||
"list of available scripts".format(name)
|
||||
)
|
||||
|
||||
self.stdout.write("")
|
||||
if options["interactive"]:
|
||||
message = (
|
||||
"Are you sure you want to execute the script {}?\n\n"
|
||||
"Type 'yes' to continue, or 'no' to cancel: "
|
||||
).format(name)
|
||||
if input("".join(message)) != "yes":
|
||||
raise CommandError("Script cancelled.")
|
||||
script["entrypoint"](self, **options)
|
||||
|
||||
def show_help(self):
|
||||
self.stdout.write("")
|
||||
self.stdout.write("Available scripts:")
|
||||
self.stdout.write("Launch with: funkwhale-manage script <script_name>")
|
||||
available_scripts = self.get_scripts()
|
||||
for name, script in sorted(available_scripts.items()):
|
||||
self.stdout.write("")
|
||||
self.stdout.write(self.style.SUCCESS(name))
|
||||
self.stdout.write("")
|
||||
for line in script["help"].splitlines():
|
||||
self.stdout.write(f" {line}")
|
||||
self.stdout.write("")
|
||||
|
||||
def get_scripts(self):
|
||||
available_scripts = [
|
||||
k for k in sorted(scripts.__dict__.keys()) if not k.startswith("__")
|
||||
]
|
||||
data = {}
|
||||
for name in available_scripts:
|
||||
module = getattr(scripts, name)
|
||||
data[name] = {
|
||||
"name": name,
|
||||
"help": module.__doc__.strip(),
|
||||
"entrypoint": module.main,
|
||||
}
|
||||
return data
|
||||
43
api/funquail_api/common/management/commands/testdata.py
Normal file
43
api/funquail_api/common/management/commands/testdata.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
from django.core.management.commands.migrate import Command as BaseCommand
|
||||
|
||||
from funkwhale_api.federation import factories
|
||||
from funkwhale_api.federation.models import Actor
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.help = "Helper to generate randomized testdata"
|
||||
self.type_choices = {"notifications": self.handle_notifications}
|
||||
self.missing_args_message = f"Please specify one of the following sub-commands: { *self.type_choices.keys(), }"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
subparsers = parser.add_subparsers(dest="subcommand")
|
||||
|
||||
notification_parser = subparsers.add_parser("notifications")
|
||||
notification_parser.add_argument(
|
||||
"username", type=str, help="Username to send the notifications to"
|
||||
)
|
||||
notification_parser.add_argument(
|
||||
"--count", type=int, help="Number of elements to create", default=1
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.type_choices[options["subcommand"]](options)
|
||||
|
||||
def handle_notifications(self, options):
|
||||
self.stdout.write(
|
||||
f"Create {options['count']} notification(s) for {options['username']}"
|
||||
)
|
||||
try:
|
||||
actor = Actor.objects.get(preferred_username=options["username"])
|
||||
except Actor.DoesNotExist:
|
||||
self.stdout.write(
|
||||
"The user you want to create notifications for does not exist"
|
||||
)
|
||||
return
|
||||
|
||||
follow_activity = factories.ActivityFactory(type="Follow")
|
||||
for _ in range(options["count"]):
|
||||
factories.InboxItemFactory(actor=actor, activity=follow_activity)
|
||||
Loading…
Add table
Add a link
Reference in a new issue