diff --git a/api/funkwhale_api/federation/migrations/0005_actor_followers.py b/api/funkwhale_api/federation/migrations/0005_actor_followers.py new file mode 100644 index 000000000..94a1c75ac --- /dev/null +++ b/api/funkwhale_api/federation/migrations/0005_actor_followers.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.3 on 2018-04-05 16:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('federation', '0004_followrequest'), + ] + + operations = [ + migrations.AddField( + model_name='actor', + name='followers', + field=models.ManyToManyField(related_name='following', through='federation.Follow', to='federation.Actor'), + ), + ] diff --git a/api/funkwhale_api/music/migrations/0023_auto_20180405_1830.py b/api/funkwhale_api/music/migrations/0023_auto_20180405_1830.py new file mode 100644 index 000000000..3cef1f42e --- /dev/null +++ b/api/funkwhale_api/music/migrations/0023_auto_20180405_1830.py @@ -0,0 +1,47 @@ +# Generated by Django 2.0.3 on 2018-04-05 18:30 + +from django.conf import settings +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('federation', '0005_actor_followers'), + ('music', '0022_importbatch_import_request'), + ] + + operations = [ + migrations.AddField( + model_name='importbatch', + name='federation_actor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='import_batches', to='federation.Actor'), + ), + migrations.AddField( + model_name='importbatch', + name='federation_source', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='importjob', + name='federation_source', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='importjob', + name='metadata', + field=django.contrib.postgres.fields.jsonb.JSONField(default={}), + ), + migrations.AlterField( + model_name='importbatch', + name='source', + field=models.CharField(choices=[('api', 'api'), ('shell', 'shell'), ('federation', 'federation')], default='api', max_length=30), + ), + migrations.AlterField( + model_name='importbatch', + name='submitted_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='imports', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 7138dcdd6..cff162972 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -8,6 +8,7 @@ import markdown from django.conf import settings from django.db import models +from django.contrib.postgres.fields import JSONField from django.core.files.base import ContentFile from django.core.files import File from django.db.models.signals import post_save @@ -65,6 +66,7 @@ class APIModelMixin(models.Model): pass return cleaned_data + class Artist(APIModelMixin): name = models.CharField(max_length=255) @@ -90,10 +92,19 @@ class Artist(APIModelMixin): t.append(tag) return set(t) + @classmethod + def get_or_create_from_name(cls, name, **kwargs): + kwargs.update({'name': name}) + return cls.objects.get_or_create( + name__iexact=name, + defaults=kwargs)[0] + + def import_artist(v): a = Artist.get_or_create_from_api(mbid=v[0]['artist']['id'])[0] return a + def parse_date(v): if len(v) == 4: return datetime.date(int(v), 1, 1) @@ -108,6 +119,7 @@ def import_tracks(instance, cleaned_data, raw_data): track_cleaned_data['position'] = int(track_data['position']) track = importers.load(Track, track_cleaned_data, track_data, Track.import_hooks) + class Album(APIModelMixin): title = models.CharField(max_length=255) artist = models.ForeignKey( @@ -170,6 +182,14 @@ class Album(APIModelMixin): t.append(tag) return set(t) + @classmethod + def get_or_create_from_title(cls, title, **kwargs): + kwargs.update({'title': title}) + return cls.objects.get_or_create( + title__iexact=title, + defaults=kwargs)[0] + + def import_tags(instance, cleaned_data, raw_data): MINIMUM_COUNT = 2 tags_to_add = [] @@ -182,6 +202,7 @@ def import_tags(instance, cleaned_data, raw_data): tags_to_add.append(tag_data['name']) instance.tags.add(*tags_to_add) + def import_album(v): a = Album.get_or_create_from_api(mbid=v[0]['id'])[0] return a @@ -328,7 +349,7 @@ class Track(APIModelMixin): def save(self, **kwargs): try: self.artist - except Artist.DoesNotExist: + except Artist.DoesNotExist: self.artist = self.album.artist super().save(**kwargs) @@ -366,6 +387,13 @@ class Track(APIModelMixin): self.mbid) return settings.FUNKWHALE_URL + '/tracks/{}'.format(self.pk) + @classmethod + def get_or_create_from_title(cls, title, **kwargs): + kwargs.update({'title': title}) + return cls.objects.get_or_create( + title__iexact=title, + defaults=kwargs)[0] + class TrackFile(models.Model): track = models.ForeignKey( @@ -420,7 +448,8 @@ IMPORT_STATUS_CHOICES = ( class ImportBatch(models.Model): IMPORT_BATCH_SOURCES = [ ('api', 'api'), - ('shell', 'shell') + ('shell', 'shell'), + ('federation', 'federation'), ] source = models.CharField( max_length=30, default='api', choices=IMPORT_BATCH_SOURCES) @@ -428,6 +457,8 @@ class ImportBatch(models.Model): submitted_by = models.ForeignKey( 'users.User', related_name='imports', + null=True, + blank=True, on_delete=models.CASCADE) status = models.CharField( choices=IMPORT_STATUS_CHOICES, default='pending', max_length=30) @@ -437,6 +468,16 @@ class ImportBatch(models.Model): null=True, blank=True, on_delete=models.CASCADE) + + federation_source = models.URLField(null=True, blank=True) + federation_actor = models.ForeignKey( + 'federation.Actor', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='import_batches', + ) + class Meta: ordering = ['-creation_date'] @@ -464,6 +505,8 @@ class ImportJob(models.Model): choices=IMPORT_STATUS_CHOICES, default='pending', max_length=30) audio_file = models.FileField( upload_to='imports/%Y/%m/%d', max_length=255, null=True, blank=True) + federation_source = models.URLField(null=True, blank=True) + metadata = JSONField(default={}) class Meta: ordering = ('id', ) diff --git a/api/tests/music/test_import.py b/api/tests/music/test_import.py index e9ad9d0f5..87e1899d6 100644 --- a/api/tests/music/test_import.py +++ b/api/tests/music/test_import.py @@ -61,6 +61,7 @@ def test_import_job_from_federation_no_musicbrainz(factories): job.refresh_from_db() tf = job.track_file + assert tf.source == job.source assert tf.track.title == 'Ping' assert tf.track.artist.name == 'Hello' assert tf.track.album.title == 'World' @@ -82,6 +83,7 @@ def test_import_job_from_federation_musicbrainz_recording(factories, mocker): job.refresh_from_db() tf = job.track_file + assert tf.source == job.source assert tf.track == t track_from_api.assert_called_once_with( mbid=tasks.get_mbid(job.metadata['recording'], 'recording')) @@ -103,6 +105,7 @@ def test_import_job_from_federation_musicbrainz_release(factories, mocker): job.refresh_from_db() tf = job.track_file + assert tf.source == job.source assert tf.track.title == 'Ping' assert tf.track.artist == a.artist assert tf.track.album == a @@ -127,6 +130,7 @@ def test_import_job_from_federation_musicbrainz_artist(factories, mocker): job.refresh_from_db() tf = job.track_file + assert tf.source == job.source assert tf.track.title == 'Ping' assert tf.track.artist == a assert tf.track.album.artist == a