From 37895e0626102ccde7fa426ee7c48199bc3e16f1 Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Mon, 15 Jan 2024 12:11:35 +0000 Subject: [PATCH] feat: Assign upload groups to existing and news v1 uploads --- api/funkwhale_api/music/admin.py | 6 +++ api/funkwhale_api/music/factories.py | 9 ++++ .../migrations/0059_upload_upload_group.py | 23 +++++++++ .../migrations/0060_auto_20240115_1055.py | 20 ++++++++ .../0061_alter_upload_upload_group.py | 22 ++++++++ api/funkwhale_api/music/models.py | 16 ++++++ api/funkwhale_api/music/views.py | 6 ++- api/tests/music/test_views.py | 50 +++++++++++++++++++ 8 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 api/funkwhale_api/music/migrations/0059_upload_upload_group.py create mode 100644 api/funkwhale_api/music/migrations/0060_auto_20240115_1055.py create mode 100644 api/funkwhale_api/music/migrations/0061_alter_upload_upload_group.py diff --git a/api/funkwhale_api/music/admin.py b/api/funkwhale_api/music/admin.py index dec75f484..abe68a516 100644 --- a/api/funkwhale_api/music/admin.py +++ b/api/funkwhale_api/music/admin.py @@ -70,6 +70,7 @@ class UploadAdmin(admin.ModelAdmin): "size", "bitrate", "import_status", + "upload_group", ] list_select_related = ["track"] search_fields = [ @@ -82,6 +83,11 @@ class UploadAdmin(admin.ModelAdmin): list_filter = ["mimetype", "import_status", "library__privacy_level"] +@admin.register(models.UploadGroup) +class UploadGroupAdmin(admin.ModelAdmin): + pass + + @admin.register(models.UploadVersion) class UploadVersionAdmin(admin.ModelAdmin): list_display = [ diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py index e1a66ceb0..94d09defc 100644 --- a/api/funkwhale_api/music/factories.py +++ b/api/funkwhale_api/music/factories.py @@ -190,6 +190,14 @@ class TrackFactory( license = factory.PostGeneration(_license_post_generation) +@registry.register +class UploadGroupFactory(factory.django.DjangoModelFactory): + name = factory.Faker("name") + + class Meta: + model = "music.UploadGroup" + + @registry.register class UploadFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): fid = factory.Faker("federation_url") @@ -198,6 +206,7 @@ class UploadFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): audio_file = factory.django.FileField( from_path=os.path.join(SAMPLES_PATH, "test.ogg") ) + upload_group = factory.SubFactory(UploadGroupFactory) bitrate = None size = None diff --git a/api/funkwhale_api/music/migrations/0059_upload_upload_group.py b/api/funkwhale_api/music/migrations/0059_upload_upload_group.py new file mode 100644 index 000000000..c0910160e --- /dev/null +++ b/api/funkwhale_api/music/migrations/0059_upload_upload_group.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.23 on 2024-01-15 10:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("music", "0058_uploadgroup"), + ] + + operations = [ + migrations.AddField( + model_name="upload", + name="upload_group", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="uploads", + to="music.uploadgroup", + ), + ), + ] diff --git a/api/funkwhale_api/music/migrations/0060_auto_20240115_1055.py b/api/funkwhale_api/music/migrations/0060_auto_20240115_1055.py new file mode 100644 index 000000000..9043653ef --- /dev/null +++ b/api/funkwhale_api/music/migrations/0060_auto_20240115_1055.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.23 on 2024-01-15 10:55 + +from django.db import migrations + + +def populate_upload_groups(apps, schema_editor): + upload = apps.get_model("music", "Upload") + upload_group = apps.get_model("music", "UploadGroup") + for upload in upload.objects.all(): + group, _ = upload_group.objects.get_or_create(name=upload.import_reference) + upload.upload_group = group + upload.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("music", "0059_upload_upload_group"), + ] + + operations = [migrations.RunPython(populate_upload_groups)] diff --git a/api/funkwhale_api/music/migrations/0061_alter_upload_upload_group.py b/api/funkwhale_api/music/migrations/0061_alter_upload_upload_group.py new file mode 100644 index 000000000..78582b904 --- /dev/null +++ b/api/funkwhale_api/music/migrations/0061_alter_upload_upload_group.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.23 on 2024-01-15 11:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("music", "0060_auto_20240115_1055"), + ] + + operations = [ + migrations.AlterField( + model_name="upload", + name="upload_group", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="uploads", + to="music.uploadgroup", + ), + ), + ] diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 4dea9073c..b96033c4a 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -811,6 +811,9 @@ class Upload(models.Model): related_name="uploads", on_delete=models.CASCADE, ) + upload_group = models.ForeignKey( + "UploadGroup", related_name="uploads", on_delete=models.CASCADE + ) # metadata from federation metadata = JSONField( @@ -827,6 +830,7 @@ class Upload(models.Model): ) # a short reference provided by the client to group multiple files # in the same import + # TODO DEPRECATED This can be removed when APIv1 gets removed or fully replace by import_group.name import_reference = models.CharField(max_length=50, default=get_import_reference) # optional metadata about import results (error messages, etc.) @@ -1563,5 +1567,17 @@ def update_request_status(sender, instance, created, **kwargs): class UploadGroup(models.Model): + """ + Upload groups are supposed to bundle uploads in order to make it easier to keep an overview + + Attributes + ---------- + name A name that can be selected by the user + guid A globally unique identifier to reference the group + """ + name = models.CharField(max_length=255, default=datetime.datetime.now) guid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + def __str__(self): + return self.name diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 7ecfc7d5c..9e98ac159 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -843,7 +843,11 @@ class UploadViewSet( return context def perform_create(self, serializer): - upload = serializer.save() + group_name = serializer.validated_data.get("import_reference") or str( + datetime.datetime.date(datetime.datetime.now()) + ) + upload_group, _ = models.UploadGroup.objects.get_or_create(name=group_name) + upload = serializer.save(upload_group=upload_group) if upload.import_status == "pending": common_utils.on_commit(tasks.process_upload.delay, upload_id=upload.pk) diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 10423b1d5..f51028910 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -811,6 +811,56 @@ def test_user_can_create_upload(logged_in_api_client, factories, mocker, audio_f m.assert_called_once_with(tasks.process_upload.delay, upload_id=upload.pk) +def test_upload_creates_implicit_upload_group( + logged_in_api_client, factories, mocker, audio_file +): + library = factories["music.Library"](actor__user=logged_in_api_client.user) + url = reverse("api:v1:uploads-list") + upload_group_count = models.UploadGroup.objects.count() + + response = logged_in_api_client.post( + url, + { + "audio_file": audio_file, + "source": "upload://test", + "library": library.uuid, + "import_metadata": '{"title": "foo"}', + }, + ) + + assert response.status_code == 201 + assert upload_group_count + 1 == models.UploadGroup.objects.count() + assert ( + models.UploadGroup.objects.filter( + name=str(datetime.datetime.date(datetime.datetime.now())) + ).count() + == 1 + ) + + +def test_upload_creates_named_upload_group( + logged_in_api_client, factories, mocker, audio_file +): + library = factories["music.Library"](actor__user=logged_in_api_client.user) + url = reverse("api:v1:uploads-list") + upload_group_count = models.UploadGroup.objects.count() + + response = logged_in_api_client.post( + url, + { + "audio_file": audio_file, + "source": "upload://test", + "import_reference": "test", + "library": library.uuid, + "import_metadata": '{"title": "foo"}', + }, + ) + + assert response.status_code == 201 + assert upload_group_count + 1 == models.UploadGroup.objects.count() + assert models.UploadGroup.objects.filter(name="test").count() == 1 + + def test_user_can_create_upload_in_channel( logged_in_api_client, factories, mocker, audio_file ):