diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0b68d19c0..d77d91f17 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,14 +13,22 @@ stages:
test_api:
stage: test
image: funkwhale/funkwhale:base
+ variables:
+ PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
+ DATABASE_URL: "sqlite://"
before_script:
+ - python3 -m venv --copies virtualenv
+ - source virtualenv/bin/activate
- cd api
+ - pip install -r requirements/base.txt
+ - pip install -r requirements/local.txt
- pip install -r requirements/test.txt
script:
- pytest
- variables:
- DATABASE_URL: "sqlite://"
-
+ cache:
+ key: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
+ paths:
+ - "$CI_PROJECT_DIR/pip-cache"
tags:
- docker
diff --git a/CHANGELOG b/CHANGELOG
index 6faa5d267..446146c29 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,10 +2,30 @@ Changelog
=========
-0.2.5 (unreleased)
+0.2.6 (Unreleased)
------------------
+
+0.2.5 (2017-12-15)
+------------------
+
+Features:
+
+- Import: can now specify search template when querying import sources (#45)
+- Login form: now redirect to previous page after login (#2)
+- 404: a decent 404 template, at least (#48)
+
+Bugfixes:
+
+- Player: better handling of errors when fetching the audio file (#46)
+- Csrf: default CSRF_TRUSTED_ORIGINS to ALLOWED_HOSTS to avoid Csrf issues on admin (#49)
+
+Tech:
+
+- Django 2 compatibility, lot of packages upgrades (#47)
+
+
0.2.4 (2017-12-14)
------------------
diff --git a/api/Dockerfile b/api/Dockerfile
index bb1942c22..def9a4316 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -8,7 +8,9 @@ COPY ./requirements.apt /requirements.apt
RUN apt-get update -qq && grep "^[^#;]" requirements.apt | xargs apt-get install -y
-COPY ./requirements /requirements
+COPY ./requirements/base.txt /requirements
+RUN pip install -r /requirements/base.txt
+COPY ./requirements/production.txt /requirements
RUN pip install -r /requirements/production.txt
COPY . /app
diff --git a/api/config/api_urls.py b/api/config/api_urls.py
index 5ed4cffdd..13205fe3d 100644
--- a/api/config/api_urls.py
+++ b/api/config/api_urls.py
@@ -25,22 +25,32 @@ v1_patterns = router.urls
v1_patterns += [
url(r'^providers/',
- include('funkwhale_api.providers.urls', namespace='providers')),
+ include(
+ ('funkwhale_api.providers.urls', 'providers'),
+ namespace='providers')),
url(r'^favorites/',
- include('funkwhale_api.favorites.urls', namespace='favorites')),
+ include(
+ ('funkwhale_api.favorites.urls', 'favorites'),
+ namespace='favorites')),
url(r'^search$',
views.Search.as_view(), name='search'),
url(r'^radios/',
- include('funkwhale_api.radios.urls', namespace='radios')),
+ include(
+ ('funkwhale_api.radios.urls', 'radios'),
+ namespace='radios')),
url(r'^history/',
- include('funkwhale_api.history.urls', namespace='history')),
+ include(
+ ('funkwhale_api.history.urls', 'history'),
+ namespace='history')),
url(r'^users/',
- include('funkwhale_api.users.api_urls', namespace='users')),
+ include(
+ ('funkwhale_api.users.api_urls', 'users'),
+ namespace='users')),
url(r'^token/',
jwt_views.obtain_jwt_token),
url(r'^token/refresh/', jwt_views.refresh_jwt_token),
]
urlpatterns = [
- url(r'^v1/', include(v1_patterns, namespace='v1'))
+ url(r'^v1/', include((v1_patterns, 'v1'), namespace='v1'))
]
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index b10a0310c..9804bb9c0 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -75,7 +75,7 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# MIDDLEWARE CONFIGURATION
# ------------------------------------------------------------------------------
-MIDDLEWARE_CLASSES = (
+MIDDLEWARE = (
# Make sure djangosecure.middleware.SecurityMiddleware is listed first
'django.contrib.sessions.middleware.SessionMiddleware',
'funkwhale_api.users.middleware.AnonymousSessionMiddleware',
diff --git a/api/config/settings/local.py b/api/config/settings/local.py
index e8108e98b..e0d497e79 100644
--- a/api/config/settings/local.py
+++ b/api/config/settings/local.py
@@ -31,7 +31,7 @@ EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND',
# django-debug-toolbar
# ------------------------------------------------------------------------------
-MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
+MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
# INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',)
diff --git a/api/config/settings/production.py b/api/config/settings/production.py
index a132076c7..e00983305 100644
--- a/api/config/settings/production.py
+++ b/api/config/settings/production.py
@@ -36,7 +36,7 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
#
#
# # Make sure djangosecure.middleware.SecurityMiddleware is listed first
-# MIDDLEWARE_CLASSES = SECURITY_MIDDLEWARE + MIDDLEWARE_CLASSES
+# MIDDLEWARE = SECURITY_MIDDLEWARE + MIDDLEWARE
#
# # set this to 60 seconds and then to 518400 when you can prove it works
# SECURE_HSTS_SECONDS = 60
@@ -55,6 +55,8 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Hosts/domain names that are valid for this site
# See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
+CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
+
# END SITE CONFIGURATION
INSTALLED_APPS += ("gunicorn", )
diff --git a/api/config/settings/test.py b/api/config/settings/test.py
index 7d02c417b..105645496 100644
--- a/api/config/settings/test.py
+++ b/api/config/settings/test.py
@@ -22,8 +22,8 @@ CACHES = {
'LOCATION': ''
}
}
-INSTALLED_APPS += ('kombu.transport.django',)
-BROKER_URL = 'django://'
+
+BROKER_URL = 'memory://'
# TESTING
# ------------------------------------------------------------------------------
diff --git a/api/config/urls.py b/api/config/urls.py
index 8764640d1..8c490a5e6 100644
--- a/api/config/urls.py
+++ b/api/config/urls.py
@@ -10,9 +10,9 @@ from django.views import defaults as default_views
urlpatterns = [
# Django Admin, use {% url 'admin:index' %}
- url(settings.ADMIN_URL, include(admin.site.urls)),
+ url(settings.ADMIN_URL, admin.site.urls),
- url(r'^api/', include("config.api_urls", namespace="api")),
+ url(r'^api/', include(("config.api_urls", 'api'), namespace="api")),
url(r'^api/auth/', include('rest_auth.urls')),
url(r'^api/auth/registration/', include('funkwhale_api.users.rest_auth_urls')),
url(r'^accounts/', include('allauth.urls')),
diff --git a/api/funkwhale_api/__init__.py b/api/funkwhale_api/__init__.py
index 92ed1ff33..28e4b3203 100644
--- a/api/funkwhale_api/__init__.py
+++ b/api/funkwhale_api/__init__.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
-__version__ = '0.2.4'
+__version__ = '0.2.5'
__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
diff --git a/api/funkwhale_api/common/permissions.py b/api/funkwhale_api/common/permissions.py
index 3f13b2005..6b61d9839 100644
--- a/api/funkwhale_api/common/permissions.py
+++ b/api/funkwhale_api/common/permissions.py
@@ -7,5 +7,5 @@ class ConditionalAuthentication(BasePermission):
def has_permission(self, request, view):
if settings.API_AUTHENTICATION_REQUIRED:
- return request.user and request.user.is_authenticated()
+ return request.user and request.user.is_authenticated
return True
diff --git a/api/funkwhale_api/contrib/sites/migrations/0001_initial.py b/api/funkwhale_api/contrib/sites/migrations/0001_initial.py
index 555d02c42..cf95cec65 100644
--- a/api/funkwhale_api/contrib/sites/migrations/0001_initial.py
+++ b/api/funkwhale_api/contrib/sites/migrations/0001_initial.py
@@ -25,7 +25,7 @@ class Migration(migrations.Migration):
'ordering': ('domain',),
},
managers=[
- (b'objects', django.contrib.sites.models.SiteManager()),
+ ('objects', django.contrib.sites.models.SiteManager()),
],
),
]
diff --git a/api/funkwhale_api/favorites/migrations/0001_initial.py b/api/funkwhale_api/favorites/migrations/0001_initial.py
index 0a6f0e5fc..c2bd03182 100644
--- a/api/funkwhale_api/favorites/migrations/0001_initial.py
+++ b/api/funkwhale_api/favorites/migrations/0001_initial.py
@@ -19,8 +19,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
- ('track', models.ForeignKey(related_name='track_favorites', to='music.Track')),
- ('user', models.ForeignKey(related_name='track_favorites', to=settings.AUTH_USER_MODEL)),
+ ('track', models.ForeignKey(related_name='track_favorites', to='music.Track', on_delete=models.CASCADE)),
+ ('user', models.ForeignKey(related_name='track_favorites', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
options={
'ordering': ('-creation_date',),
diff --git a/api/funkwhale_api/favorites/models.py b/api/funkwhale_api/favorites/models.py
index f9c6426e6..899ed9cff 100644
--- a/api/funkwhale_api/favorites/models.py
+++ b/api/funkwhale_api/favorites/models.py
@@ -5,8 +5,10 @@ from funkwhale_api.music.models import Track
class TrackFavorite(models.Model):
creation_date = models.DateTimeField(default=timezone.now)
- user = models.ForeignKey('users.User', related_name='track_favorites')
- track = models.ForeignKey(Track, related_name='track_favorites')
+ user = models.ForeignKey(
+ 'users.User', related_name='track_favorites', on_delete=models.CASCADE)
+ track = models.ForeignKey(
+ Track, related_name='track_favorites', on_delete=models.CASCADE)
class Meta:
unique_together = ('track', 'user')
diff --git a/api/funkwhale_api/favorites/tests/test_favorites.py b/api/funkwhale_api/favorites/tests/test_favorites.py
index 230f030fe..78c64a413 100644
--- a/api/funkwhale_api/favorites/tests/test_favorites.py
+++ b/api/funkwhale_api/favorites/tests/test_favorites.py
@@ -1,6 +1,6 @@
import json
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from funkwhale_api.music.models import Track, Artist
from funkwhale_api.favorites.models import TrackFavorite
diff --git a/api/funkwhale_api/history/migrations/0001_initial.py b/api/funkwhale_api/history/migrations/0001_initial.py
index 5ddfc26f3..7b6f950ed 100644
--- a/api/funkwhale_api/history/migrations/0001_initial.py
+++ b/api/funkwhale_api/history/migrations/0001_initial.py
@@ -20,8 +20,8 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('end_date', models.DateTimeField(null=True, blank=True, default=django.utils.timezone.now)),
('session_key', models.CharField(null=True, blank=True, max_length=100)),
- ('track', models.ForeignKey(related_name='listenings', to='music.Track')),
- ('user', models.ForeignKey(blank=True, null=True, related_name='listenings', to=settings.AUTH_USER_MODEL)),
+ ('track', models.ForeignKey(related_name='listenings', to='music.Track', on_delete=models.CASCADE)),
+ ('user', models.ForeignKey(blank=True, null=True, related_name='listenings', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
options={
'ordering': ('-end_date',),
diff --git a/api/funkwhale_api/history/models.py b/api/funkwhale_api/history/models.py
index 0810ecf81..f7f62de62 100644
--- a/api/funkwhale_api/history/models.py
+++ b/api/funkwhale_api/history/models.py
@@ -7,8 +7,14 @@ from funkwhale_api.music.models import Track
class Listening(models.Model):
end_date = models.DateTimeField(default=timezone.now, null=True, blank=True)
- track = models.ForeignKey(Track, related_name="listenings")
- user = models.ForeignKey('users.User', related_name="listenings", null=True, blank=True)
+ track = models.ForeignKey(
+ Track, related_name="listenings", on_delete=models.CASCADE)
+ user = models.ForeignKey(
+ 'users.User',
+ related_name="listenings",
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE)
session_key = models.CharField(max_length=100, null=True, blank=True)
class Meta:
diff --git a/api/funkwhale_api/history/tests/factories.py b/api/funkwhale_api/history/tests/factories.py
new file mode 100644
index 000000000..0a411adf0
--- /dev/null
+++ b/api/funkwhale_api/history/tests/factories.py
@@ -0,0 +1,12 @@
+import factory
+from funkwhale_api.music.tests import factories
+
+from funkwhale_api.users.tests.factories import UserFactory
+
+
+class ListeningFactory(factory.django.DjangoModelFactory):
+ user = factory.SubFactory(UserFactory)
+ track = factory.SubFactory(factories.TrackFactory)
+
+ class Meta:
+ model = 'history.Listening'
diff --git a/api/funkwhale_api/history/tests/test_history.py b/api/funkwhale_api/history/tests/test_history.py
index 61009615a..5cb45c946 100644
--- a/api/funkwhale_api/history/tests/test_history.py
+++ b/api/funkwhale_api/history/tests/test_history.py
@@ -1,15 +1,16 @@
import random
import json
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.core.exceptions import ValidationError
from django.utils import timezone
-from model_mommy import mommy
+from funkwhale_api.music.tests.factories import TrackFactory
from funkwhale_api.users.models import User
from funkwhale_api.history import models
+
class TestHistory(TestCase):
def setUp(self):
@@ -17,12 +18,12 @@ class TestHistory(TestCase):
self.user = User.objects.create_user(username='test', email='test@test.com', password='test')
def test_can_create_listening(self):
- track = mommy.make('music.Track')
+ track = TrackFactory()
now = timezone.now()
l = models.Listening.objects.create(user=self.user, track=track)
def test_anonymous_user_can_create_listening_via_api(self):
- track = mommy.make('music.Track')
+ track = TrackFactory()
url = self.reverse('api:v1:history:listenings-list')
response = self.client.post(url, {
'track': track.pk,
@@ -34,7 +35,7 @@ class TestHistory(TestCase):
self.assertIsNotNone(listening.session_key)
def test_logged_in_user_can_create_listening_via_api(self):
- track = mommy.make('music.Track')
+ track = TrackFactory()
self.client.login(username=self.user.username, password='test')
diff --git a/api/funkwhale_api/history/views.py b/api/funkwhale_api/history/views.py
index d65a70f87..32bad6060 100644
--- a/api/funkwhale_api/history/views.py
+++ b/api/funkwhale_api/history/views.py
@@ -22,14 +22,14 @@ class ListeningViewSet(mixins.CreateModelMixin,
def get_queryset(self):
queryset = super().get_queryset()
- if self.request.user.is_authenticated():
+ if self.request.user.is_authenticated:
return queryset.filter(user=self.request.user)
else:
return queryset.filter(session_key=self.request.session.session_key)
def get_serializer_context(self):
context = super().get_serializer_context()
- if self.request.user.is_authenticated():
+ if self.request.user.is_authenticated:
context['user'] = self.request.user
else:
context['session_key'] = self.request.session.session_key
diff --git a/api/funkwhale_api/music/migrations/0001_initial.py b/api/funkwhale_api/music/migrations/0001_initial.py
index e78647948..265b81577 100644
--- a/api/funkwhale_api/music/migrations/0001_initial.py
+++ b/api/funkwhale_api/music/migrations/0001_initial.py
@@ -44,7 +44,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
- ('submitted_by', models.ForeignKey(related_name='imports', to=settings.AUTH_USER_MODEL)),
+ ('submitted_by', models.ForeignKey(related_name='imports', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
),
migrations.CreateModel(
@@ -54,7 +54,7 @@ class Migration(migrations.Migration):
('source', models.URLField()),
('mbid', models.UUIDField(editable=False)),
('status', models.CharField(default='pending', choices=[('pending', 'Pending'), ('finished', 'finished')], max_length=30)),
- ('batch', models.ForeignKey(related_name='jobs', to='music.ImportBatch')),
+ ('batch', models.ForeignKey(related_name='jobs', to='music.ImportBatch', on_delete=models.CASCADE)),
],
),
migrations.CreateModel(
@@ -64,8 +64,8 @@ class Migration(migrations.Migration):
('mbid', models.UUIDField(editable=False, blank=True, null=True)),
('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
('title', models.CharField(max_length=255)),
- ('album', models.ForeignKey(related_name='tracks', blank=True, null=True, to='music.Album')),
- ('artist', models.ForeignKey(related_name='tracks', to='music.Artist')),
+ ('album', models.ForeignKey(related_name='tracks', blank=True, null=True, to='music.Album', on_delete=models.CASCADE)),
+ ('artist', models.ForeignKey(related_name='tracks', to='music.Artist', on_delete=models.CASCADE)),
],
options={
'abstract': False,
@@ -78,12 +78,12 @@ class Migration(migrations.Migration):
('audio_file', models.FileField(upload_to='tracks')),
('source', models.URLField(blank=True, null=True)),
('duration', models.IntegerField(blank=True, null=True)),
- ('track', models.ForeignKey(related_name='files', to='music.Track')),
+ ('track', models.ForeignKey(related_name='files', to='music.Track', on_delete=models.CASCADE)),
],
),
migrations.AddField(
model_name='album',
name='artist',
- field=models.ForeignKey(related_name='albums', to='music.Artist'),
+ field=models.ForeignKey(related_name='albums', to='music.Artist', on_delete=models.CASCADE),
),
]
diff --git a/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py b/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py
index 2046a7127..3a3d93989 100644
--- a/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py
+++ b/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py
@@ -39,11 +39,11 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='lyrics',
name='work',
- field=models.ForeignKey(related_name='lyrics', to='music.Work', blank=True, null=True),
+ field=models.ForeignKey(related_name='lyrics', to='music.Work', blank=True, null=True, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='track',
name='work',
- field=models.ForeignKey(related_name='tracks', to='music.Work', blank=True, null=True),
+ field=models.ForeignKey(related_name='tracks', to='music.Work', blank=True, null=True, on_delete=models.CASCADE),
),
]
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 95a47fd4a..b7cf0f25c 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -10,7 +10,7 @@ from django.conf import settings
from django.db import models
from django.core.files.base import ContentFile
from django.core.files import File
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.utils import timezone
from taggit.managers import TaggableManager
from versatileimagefield.fields import VersatileImageField
@@ -108,7 +108,8 @@ def import_tracks(instance, cleaned_data, raw_data):
class Album(APIModelMixin):
title = models.CharField(max_length=255)
- artist = models.ForeignKey(Artist, related_name='albums')
+ artist = models.ForeignKey(
+ Artist, related_name='albums', on_delete=models.CASCADE)
release_date = models.DateField(null=True)
release_group_id = models.UUIDField(null=True, blank=True)
cover = VersatileImageField(upload_to='albums/covers/%Y/%m/%d', null=True, blank=True)
@@ -245,7 +246,12 @@ class Work(APIModelMixin):
class Lyrics(models.Model):
- work = models.ForeignKey(Work, related_name='lyrics', null=True, blank=True)
+ work = models.ForeignKey(
+ Work,
+ related_name='lyrics',
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE)
url = models.URLField(unique=True)
content = models.TextField(null=True, blank=True)
@@ -268,10 +274,21 @@ class Lyrics(models.Model):
class Track(APIModelMixin):
title = models.CharField(max_length=255)
- artist = models.ForeignKey(Artist, related_name='tracks')
+ artist = models.ForeignKey(
+ Artist, related_name='tracks', on_delete=models.CASCADE)
position = models.PositiveIntegerField(null=True, blank=True)
- album = models.ForeignKey(Album, related_name='tracks', null=True, blank=True)
- work = models.ForeignKey(Work, related_name='tracks', null=True, blank=True)
+ album = models.ForeignKey(
+ Album,
+ related_name='tracks',
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE)
+ work = models.ForeignKey(
+ Work,
+ related_name='tracks',
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE)
musicbrainz_model = 'recording'
api = musicbrainz.api.recordings
@@ -340,7 +357,8 @@ class Track(APIModelMixin):
class TrackFile(models.Model):
- track = models.ForeignKey(Track, related_name='files')
+ track = models.ForeignKey(
+ Track, related_name='files', on_delete=models.CASCADE)
audio_file = models.FileField(upload_to='tracks/%Y/%m/%d', max_length=255)
source = models.URLField(null=True, blank=True)
duration = models.IntegerField(null=True, blank=True)
@@ -376,7 +394,8 @@ class TrackFile(models.Model):
class ImportBatch(models.Model):
creation_date = models.DateTimeField(default=timezone.now)
- submitted_by = models.ForeignKey('users.User', related_name='imports')
+ submitted_by = models.ForeignKey(
+ 'users.User', related_name='imports', on_delete=models.CASCADE)
class Meta:
ordering = ['-creation_date']
@@ -392,9 +411,14 @@ class ImportBatch(models.Model):
return 'finished'
class ImportJob(models.Model):
- batch = models.ForeignKey(ImportBatch, related_name='jobs')
+ batch = models.ForeignKey(
+ ImportBatch, related_name='jobs', on_delete=models.CASCADE)
track_file = models.ForeignKey(
- TrackFile, related_name='jobs', null=True, blank=True)
+ TrackFile,
+ related_name='jobs',
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE)
source = models.URLField()
mbid = models.UUIDField(editable=False)
STATUS_CHOICES = (
diff --git a/api/funkwhale_api/music/tests/factories.py b/api/funkwhale_api/music/tests/factories.py
index b554e3e14..567e2a765 100644
--- a/api/funkwhale_api/music/tests/factories.py
+++ b/api/funkwhale_api/music/tests/factories.py
@@ -59,3 +59,30 @@ class ImportJobFactory(factory.django.DjangoModelFactory):
class Meta:
model = 'music.ImportJob'
+
+
+class WorkFactory(factory.django.DjangoModelFactory):
+ mbid = factory.Faker('uuid4')
+ language = 'eng'
+ nature = 'song'
+ title = factory.Faker('sentence', nb_words=3)
+
+ class Meta:
+ model = 'music.Work'
+
+
+class LyricsFactory(factory.django.DjangoModelFactory):
+ work = factory.SubFactory(WorkFactory)
+ url = factory.Faker('url')
+ content = factory.Faker('paragraphs', nb=4)
+
+ class Meta:
+ model = 'music.Lyrics'
+
+
+class TagFactory(factory.django.DjangoModelFactory):
+ name = factory.SelfAttribute('slug')
+ slug = factory.Faker('slug')
+
+ class Meta:
+ model = 'taggit.Tag'
diff --git a/api/funkwhale_api/music/tests/test_api.py b/api/funkwhale_api/music/tests/test_api.py
index b7c25424f..2460fa97d 100644
--- a/api/funkwhale_api/music/tests/test_api.py
+++ b/api/funkwhale_api/music/tests/test_api.py
@@ -1,7 +1,7 @@
import json
import unittest
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from funkwhale_api.music import models
from funkwhale_api.utils.tests import TMPDirTestCaseMixin
diff --git a/api/funkwhale_api/music/tests/test_lyrics.py b/api/funkwhale_api/music/tests/test_lyrics.py
index 0ea22371b..9a05e5eb8 100644
--- a/api/funkwhale_api/music/tests/test_lyrics.py
+++ b/api/funkwhale_api/music/tests/test_lyrics.py
@@ -1,25 +1,26 @@
import json
import unittest
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
-from model_mommy import mommy
+from django.urls import reverse
from funkwhale_api.music import models
from funkwhale_api.musicbrainz import api
from funkwhale_api.music import serializers
from funkwhale_api.users.models import User
+from funkwhale_api.music import lyrics as lyrics_utils
from .mocking import lyricswiki
+from . import factories
from . import data as api_data
-from funkwhale_api.music import lyrics as lyrics_utils
+
+
class TestLyrics(TestCase):
@unittest.mock.patch('funkwhale_api.music.lyrics._get_html',
return_value=lyricswiki.content)
def test_works_import_lyrics_if_any(self, *mocks):
- lyrics = mommy.make(
- models.Lyrics,
+ lyrics = factories.LyricsFactory(
url='http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!')
lyrics.fetch_content()
@@ -42,7 +43,7 @@ Is it me you're looking for?
content = """Hello
Is it me you're looking for?"""
- l = mommy.make(models.Lyrics, content=content)
+ l = factories.LyricsFactory(content=content)
expected = "
Hello Is it me you're looking for?
"
self.assertHTMLEqual(expected, l.content_rendered)
@@ -54,8 +55,7 @@ Is it me you're looking for?"""
@unittest.mock.patch('funkwhale_api.music.lyrics._get_html',
return_value=lyricswiki.content)
def test_works_import_lyrics_if_any(self, *mocks):
- track = mommy.make(
- models.Track,
+ track = factories.TrackFactory(
work=None,
mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
diff --git a/api/funkwhale_api/music/tests/test_music.py b/api/funkwhale_api/music/tests/test_music.py
index cc01bc9ed..5cf9d0cf9 100644
--- a/api/funkwhale_api/music/tests/test_music.py
+++ b/api/funkwhale_api/music/tests/test_music.py
@@ -2,13 +2,11 @@ from test_plus.test import TestCase
import unittest.mock
from funkwhale_api.music import models
import datetime
-from model_mommy import mommy
+
+from . import factories
from . import data as api_data
from .cover import binary_data
-def prettyprint(d):
- import json
- print(json.dumps(d, sort_keys=True, indent=4))
class TestMusic(TestCase):
@@ -79,9 +77,9 @@ class TestMusic(TestCase):
self.assertEqual(track, track2)
def test_album_tags_deduced_from_tracks_tags(self):
- tag = mommy.make('taggit.Tag')
- album = mommy.make('music.Album')
- tracks = mommy.make('music.Track', album=album, _quantity=5)
+ tag = factories.TagFactory()
+ album = factories.AlbumFactory()
+ tracks = factories.TrackFactory.create_batch(album=album, size=5)
for track in tracks:
track.tags.add(tag)
@@ -92,10 +90,10 @@ class TestMusic(TestCase):
self.assertIn(tag, album.tags)
def test_artist_tags_deduced_from_album_tags(self):
- tag = mommy.make('taggit.Tag')
- artist = mommy.make('music.Artist')
- album = mommy.make('music.Album', artist=artist)
- tracks = mommy.make('music.Track', album=album, _quantity=5)
+ tag = factories.TagFactory()
+ artist = factories.ArtistFactory()
+ album = factories.AlbumFactory(artist=artist)
+ tracks = factories.TrackFactory.create_batch(album=album, size=5)
for track in tracks:
track.tags.add(tag)
@@ -108,7 +106,7 @@ class TestMusic(TestCase):
@unittest.mock.patch('funkwhale_api.musicbrainz.api.images.get_front', return_value=binary_data)
def test_can_download_image_file_for_album(self, *mocks):
# client._api.get_image_front('55ea4f82-b42b-423e-a0e5-290ccdf443ed')
- album = mommy.make('music.Album', mbid='55ea4f82-b42b-423e-a0e5-290ccdf443ed')
+ album = factories.AlbumFactory(mbid='55ea4f82-b42b-423e-a0e5-290ccdf443ed')
album.get_image()
album.save()
diff --git a/api/funkwhale_api/music/tests/test_works.py b/api/funkwhale_api/music/tests/test_works.py
index 84cb51cde..55714bce2 100644
--- a/api/funkwhale_api/music/tests/test_works.py
+++ b/api/funkwhale_api/music/tests/test_works.py
@@ -1,23 +1,24 @@
import json
import unittest
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
-from model_mommy import mommy
+from django.urls import reverse
from funkwhale_api.music import models
from funkwhale_api.musicbrainz import api
from funkwhale_api.music import serializers
+from funkwhale_api.music.tests import factories
from funkwhale_api.users.models import User
from . import data as api_data
+
class TestWorks(TestCase):
@unittest.mock.patch('funkwhale_api.musicbrainz.api.works.get',
return_value=api_data.works['get']['chop_suey'])
def test_can_import_work(self, *mocks):
- recording = mommy.make(
- models.Track, mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
+ recording = factories.TrackFactory(
+ mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
work = models.Work.create_from_api(id=mbid)
@@ -36,8 +37,7 @@ class TestWorks(TestCase):
@unittest.mock.patch('funkwhale_api.musicbrainz.api.recordings.get',
return_value=api_data.tracks['get']['chop_suey'])
def test_can_get_work_from_recording(self, *mocks):
- recording = mommy.make(
- models.Track,
+ recording = factories.TrackFactory(
work=None,
mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 72982e4c5..c32fa8f7f 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -2,7 +2,7 @@ import os
import json
import unicodedata
import urllib
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.db import models, transaction
from django.db.models.functions import Length
from django.conf import settings
@@ -102,7 +102,7 @@ class TrackViewSet(TagViewSetMixin, SearchMixin, viewsets.ReadOnlyModelViewSet):
queryset = super().get_queryset()
filter_favorites = self.request.GET.get('favorites', None)
user = self.request.user
- if user.is_authenticated() and filter_favorites == 'true':
+ if user.is_authenticated and filter_favorites == 'true':
queryset = queryset.filter(track_favorites__user=user)
return queryset
diff --git a/api/funkwhale_api/musicbrainz/tests/test_api.py b/api/funkwhale_api/musicbrainz/tests/test_api.py
index f962e0f78..b0911f1c5 100644
--- a/api/funkwhale_api/musicbrainz/tests/test_api.py
+++ b/api/funkwhale_api/musicbrainz/tests/test_api.py
@@ -1,7 +1,7 @@
import json
import unittest
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from funkwhale_api.musicbrainz import api
from . import data as api_data
diff --git a/api/funkwhale_api/playlists/migrations/0001_initial.py b/api/funkwhale_api/playlists/migrations/0001_initial.py
index f42ca154e..bc97d8122 100644
--- a/api/funkwhale_api/playlists/migrations/0001_initial.py
+++ b/api/funkwhale_api/playlists/migrations/0001_initial.py
@@ -22,7 +22,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=50)),
('is_public', models.BooleanField(default=False)),
('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
- ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='playlists')),
+ ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='playlists', on_delete=models.CASCADE)),
],
),
migrations.CreateModel(
@@ -33,9 +33,9 @@ class Migration(migrations.Migration):
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('position', models.PositiveIntegerField(db_index=True, editable=False)),
- ('playlist', models.ForeignKey(to='playlists.Playlist', related_name='playlist_tracks')),
- ('previous', mptt.fields.TreeOneToOneField(null=True, to='playlists.PlaylistTrack', related_name='next', blank=True)),
- ('track', models.ForeignKey(to='music.Track', related_name='playlist_tracks')),
+ ('playlist', models.ForeignKey(to='playlists.Playlist', related_name='playlist_tracks', on_delete=models.CASCADE)),
+ ('previous', mptt.fields.TreeOneToOneField(null=True, to='playlists.PlaylistTrack', related_name='next', blank=True, on_delete=models.CASCADE)),
+ ('track', models.ForeignKey(to='music.Track', related_name='playlist_tracks', on_delete=models.CASCADE)),
],
options={
'ordering': ('-playlist', 'position'),
diff --git a/api/funkwhale_api/playlists/models.py b/api/funkwhale_api/playlists/models.py
index 35a30a704..e89dce81c 100644
--- a/api/funkwhale_api/playlists/models.py
+++ b/api/funkwhale_api/playlists/models.py
@@ -7,7 +7,8 @@ from mptt.models import MPTTModel, TreeOneToOneField
class Playlist(models.Model):
name = models.CharField(max_length=50)
is_public = models.BooleanField(default=False)
- user = models.ForeignKey('users.User', related_name="playlists")
+ user = models.ForeignKey(
+ 'users.User', related_name="playlists", on_delete=models.CASCADE)
creation_date = models.DateTimeField(default=timezone.now)
def __str__(self):
@@ -21,9 +22,18 @@ class Playlist(models.Model):
class PlaylistTrack(MPTTModel):
- track = models.ForeignKey('music.Track', related_name='playlist_tracks')
- previous = TreeOneToOneField('self', blank=True, null=True, related_name='next')
- playlist = models.ForeignKey(Playlist, related_name='playlist_tracks')
+ track = models.ForeignKey(
+ 'music.Track',
+ related_name='playlist_tracks',
+ on_delete=models.CASCADE)
+ previous = TreeOneToOneField(
+ 'self',
+ blank=True,
+ null=True,
+ related_name='next',
+ on_delete=models.CASCADE)
+ playlist = models.ForeignKey(
+ Playlist, related_name='playlist_tracks', on_delete=models.CASCADE)
class MPTTMeta:
level_attr = 'position'
diff --git a/api/funkwhale_api/playlists/tests/test_playlists.py b/api/funkwhale_api/playlists/tests/test_playlists.py
index 49025cd11..2f61889ee 100644
--- a/api/funkwhale_api/playlists/tests/test_playlists.py
+++ b/api/funkwhale_api/playlists/tests/test_playlists.py
@@ -1,11 +1,10 @@
import json
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.core.exceptions import ValidationError
from django.utils import timezone
-from model_mommy import mommy
-
+from funkwhale_api.music.tests import factories
from funkwhale_api.users.models import User
from funkwhale_api.playlists import models
from funkwhale_api.playlists.serializers import PlaylistSerializer
@@ -18,7 +17,7 @@ class TestPlayLists(TestCase):
self.user = User.objects.create_user(username='test', email='test@test.com', password='test')
def test_can_create_playlist(self):
- tracks = list(mommy.make('music.Track', _quantity=5))
+ tracks = factories.TrackFactory.create_batch(size=5)
playlist = models.Playlist.objects.create(user=self.user, name="test")
previous = None
@@ -49,7 +48,7 @@ class TestPlayLists(TestCase):
self.assertEqual(playlist.name, 'test')
def test_can_add_playlist_track_via_api(self):
- tracks = list(mommy.make('music.Track', _quantity=5))
+ tracks = factories.TrackFactory.create_batch(size=5)
playlist = models.Playlist.objects.create(user=self.user, name="test")
self.client.login(username=self.user.username, password='test')
diff --git a/api/funkwhale_api/providers/urls.py b/api/funkwhale_api/providers/urls.py
index a6d417e5b..10975da53 100644
--- a/api/funkwhale_api/providers/urls.py
+++ b/api/funkwhale_api/providers/urls.py
@@ -1,8 +1,11 @@
from django.conf.urls import include, url
from funkwhale_api.music import views
-
urlpatterns = [
- url(r'^youtube/', include('funkwhale_api.providers.youtube.urls', namespace='youtube')),
- url(r'^musicbrainz/', include('funkwhale_api.musicbrainz.urls', namespace='musicbrainz')),
+ url(r'^youtube/', include(
+ ('funkwhale_api.providers.youtube.urls', 'youtube'),
+ namespace='youtube')),
+ url(r'^musicbrainz/', include(
+ ('funkwhale_api.musicbrainz.urls', 'musicbrainz'),
+ namespace='musicbrainz')),
]
diff --git a/api/funkwhale_api/providers/youtube/tests/test_youtube.py b/api/funkwhale_api/providers/youtube/tests/test_youtube.py
index db6bd8413..8a1dd1eb7 100644
--- a/api/funkwhale_api/providers/youtube/tests/test_youtube.py
+++ b/api/funkwhale_api/providers/youtube/tests/test_youtube.py
@@ -2,7 +2,7 @@ import json
from collections import OrderedDict
import unittest
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from funkwhale_api.providers.youtube.client import client
from . import data as api_data
diff --git a/api/funkwhale_api/radios/migrations/0001_initial.py b/api/funkwhale_api/radios/migrations/0001_initial.py
index 9ec25805d..46faf749e 100644
--- a/api/funkwhale_api/radios/migrations/0001_initial.py
+++ b/api/funkwhale_api/radios/migrations/0001_initial.py
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('radio_type', models.CharField(max_length=50)),
('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
- ('user', models.ForeignKey(related_name='radio_sessions', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
+ ('user', models.ForeignKey(related_name='radio_sessions', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)),
],
),
migrations.CreateModel(
@@ -28,8 +28,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('position', models.IntegerField(default=1)),
- ('session', models.ForeignKey(to='radios.RadioSession', related_name='session_tracks')),
- ('track', models.ForeignKey(to='music.Track', related_name='radio_session_tracks')),
+ ('session', models.ForeignKey(to='radios.RadioSession', related_name='session_tracks', on_delete=models.CASCADE)),
+ ('track', models.ForeignKey(to='music.Track', related_name='radio_session_tracks', on_delete=models.CASCADE)),
],
options={
'ordering': ('session', 'position'),
diff --git a/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py b/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py
index 4629d68fe..7c70abc2e 100644
--- a/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py
+++ b/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py
@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='radiosession',
name='related_object_content_type',
- field=models.ForeignKey(null=True, to='contenttypes.ContentType', blank=True),
+ field=models.ForeignKey(null=True, to='contenttypes.ContentType', blank=True, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='radiosession',
diff --git a/api/funkwhale_api/radios/models.py b/api/funkwhale_api/radios/models.py
index a3a353132..984b34a1f 100644
--- a/api/funkwhale_api/radios/models.py
+++ b/api/funkwhale_api/radios/models.py
@@ -7,11 +7,20 @@ from django.contrib.contenttypes.models import ContentType
from funkwhale_api.music.models import Track
class RadioSession(models.Model):
- user = models.ForeignKey('users.User', related_name='radio_sessions', null=True, blank=True)
+ user = models.ForeignKey(
+ 'users.User',
+ related_name='radio_sessions',
+ null=True,
+ blank=True,
+ on_delete=models.CASCADE)
session_key = models.CharField(max_length=100, null=True, blank=True)
radio_type = models.CharField(max_length=50)
creation_date = models.DateTimeField(default=timezone.now)
- related_object_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True)
+ related_object_content_type = models.ForeignKey(
+ ContentType,
+ blank=True,
+ null=True,
+ on_delete=models.CASCADE)
related_object_id = models.PositiveIntegerField(blank=True, null=True)
related_object = GenericForeignKey('related_object_content_type', 'related_object_id')
@@ -43,9 +52,11 @@ class RadioSession(models.Model):
return registry[self.radio_type](session=self)
class RadioSessionTrack(models.Model):
- session = models.ForeignKey(RadioSession, related_name='session_tracks')
+ session = models.ForeignKey(
+ RadioSession, related_name='session_tracks', on_delete=models.CASCADE)
position = models.IntegerField(default=1)
- track = models.ForeignKey(Track, related_name='radio_session_tracks')
+ track = models.ForeignKey(
+ Track, related_name='radio_session_tracks', on_delete=models.CASCADE)
class Meta:
ordering = ('session', 'position')
diff --git a/api/funkwhale_api/radios/tests/test_radios.py b/api/funkwhale_api/radios/tests/test_radios.py
index 5729b4412..ab27d4516 100644
--- a/api/funkwhale_api/radios/tests/test_radios.py
+++ b/api/funkwhale_api/radios/tests/test_radios.py
@@ -1,16 +1,18 @@
import random
import json
from test_plus.test import TestCase
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.core.exceptions import ValidationError
-from model_mommy import mommy
from funkwhale_api.radios import radios
from funkwhale_api.radios import models
from funkwhale_api.favorites.models import TrackFavorite
from funkwhale_api.users.models import User
from funkwhale_api.music.models import Artist
+from funkwhale_api.music.tests import factories
+from funkwhale_api.history.tests.factories import ListeningFactory
+
class TestRadios(TestCase):
@@ -55,7 +57,7 @@ class TestRadios(TestCase):
self.assertTrue(picks[2] > picks[1])
def test_can_get_choices_for_favorites_radio(self):
- tracks = mommy.make('music.Track', _quantity=100)
+ tracks = factories.TrackFactory.create_batch(size=100)
for i in range(20):
TrackFavorite.add(track=random.choice(tracks), user=self.user)
@@ -73,7 +75,7 @@ class TestRadios(TestCase):
self.assertIn(pick, choices)
def test_can_use_radio_session_to_filter_choices(self):
- tracks = mommy.make('music.Track', _quantity=30)
+ tracks = factories.TrackFactory.create_batch(size=30)
radio = radios.RandomRadio()
session = radio.start_session(self.user)
@@ -85,7 +87,7 @@ class TestRadios(TestCase):
self.assertEqual(len(set(tracks_id)), 30)
def test_can_restore_radio_from_previous_session(self):
- tracks = mommy.make('music.Track', _quantity=30)
+ tracks = factories.TrackFactory.create_batch(size=30)
radio = radios.RandomRadio()
session = radio.start_session(self.user)
@@ -115,7 +117,7 @@ class TestRadios(TestCase):
self.assertIsNotNone(session.session_key)
def test_can_get_track_for_session_from_api(self):
- tracks = mommy.make('music.Track', _quantity=1)
+ tracks = factories.TrackFactory.create_batch(size=1)
self.client.login(username=self.user.username, password='test')
url = reverse('api:v1:radios:sessions-list')
@@ -129,7 +131,7 @@ class TestRadios(TestCase):
self.assertEqual(data['track']['id'], tracks[0].id)
self.assertEqual(data['position'], 1)
- next_track = mommy.make('music.Track')
+ next_track = factories.TrackFactory()
response = self.client.post(url, {'session': session.pk})
data = json.loads(response.content.decode('utf-8'))
@@ -148,9 +150,10 @@ class TestRadios(TestCase):
radio.start_session(self.user, related_object=self.user)
def test_can_start_artist_radio(self):
- artist = mommy.make('music.Artist')
- wrong_tracks = mommy.make('music.Track', _quantity=30)
- good_tracks = mommy.make('music.Track', artist=artist, _quantity=5)
+ artist = factories.ArtistFactory()
+ wrong_tracks = factories.TrackFactory.create_batch(size=30)
+ good_tracks = factories.TrackFactory.create_batch(
+ artist=artist, size=5)
radio = radios.ArtistRadio()
session = radio.start_session(self.user, related_object=artist)
@@ -159,9 +162,9 @@ class TestRadios(TestCase):
self.assertIn(radio.pick(), good_tracks)
def test_can_start_tag_radio(self):
- tag = mommy.make('taggit.Tag')
- wrong_tracks = mommy.make('music.Track', _quantity=30)
- good_tracks = mommy.make('music.Track', _quantity=5)
+ tag = factories.TagFactory()
+ wrong_tracks = factories.TrackFactory.create_batch(size=30)
+ good_tracks = factories.TrackFactory.create_batch(size=5)
for track in good_tracks:
track.tags.add(tag)
@@ -172,7 +175,7 @@ class TestRadios(TestCase):
self.assertIn(radio.pick(), good_tracks)
def test_can_start_artist_radio_from_api(self):
- artist = mommy.make('music.Artist')
+ artist = factories.ArtistFactory()
url = reverse('api:v1:radios:sessions-list')
response = self.client.post(url, {'radio_type': 'artist', 'related_object_id': artist.id})
@@ -181,10 +184,10 @@ class TestRadios(TestCase):
self.assertEqual(session.related_object, artist)
def test_can_start_less_listened_radio(self):
- history = mommy.make('history.Listening', _quantity=5, user=self.user)
+ history = ListeningFactory.create_batch(size=5, user=self.user)
wrong_tracks = [h.track for h in history]
- good_tracks = mommy.make('music.Track', _quantity=30)
+ good_tracks = factories.TrackFactory.create_batch(size=30)
radio = radios.LessListenedRadio()
session = radio.start_session(self.user)
diff --git a/api/funkwhale_api/radios/urls.py b/api/funkwhale_api/radios/urls.py
index 57e47f063..8c31df093 100644
--- a/api/funkwhale_api/radios/urls.py
+++ b/api/funkwhale_api/radios/urls.py
@@ -6,4 +6,5 @@ router = routers.SimpleRouter()
router.register(r'sessions', views.RadioSessionViewSet, 'sessions')
router.register(r'tracks', views.RadioSessionTrackViewSet, 'tracks')
+
urlpatterns = router.urls
diff --git a/api/funkwhale_api/radios/views.py b/api/funkwhale_api/radios/views.py
index 1ae788fcb..9243d6a90 100644
--- a/api/funkwhale_api/radios/views.py
+++ b/api/funkwhale_api/radios/views.py
@@ -19,14 +19,14 @@ class RadioSessionViewSet(mixins.CreateModelMixin,
def get_queryset(self):
queryset = super().get_queryset()
- if self.request.user.is_authenticated():
+ if self.request.user.is_authenticated:
return queryset.filter(user=self.request.user)
else:
return queryset.filter(session_key=self.request.session.session_key)
def get_serializer_context(self):
context = super().get_serializer_context()
- if self.request.user.is_authenticated():
+ if self.request.user.is_authenticated:
context['user'] = self.request.user
else:
context['session_key'] = self.request.session.session_key
@@ -44,7 +44,7 @@ class RadioSessionTrackViewSet(mixins.CreateModelMixin,
serializer.is_valid(raise_exception=True)
session = serializer.validated_data['session']
try:
- if request.user.is_authenticated():
+ if request.user.is_authenticated:
assert request.user == session.user
else:
assert request.session.session_key == session.session_key
diff --git a/api/funkwhale_api/users/middleware.py b/api/funkwhale_api/users/middleware.py
index 0f572c203..e3eba95f3 100644
--- a/api/funkwhale_api/users/middleware.py
+++ b/api/funkwhale_api/users/middleware.py
@@ -1,6 +1,11 @@
-class AnonymousSessionMiddleware(object):
- def process_request(self, request):
+class AnonymousSessionMiddleware:
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
if not request.session.session_key:
request.session.save()
+ response = self.get_response(request)
+ return response
diff --git a/api/funkwhale_api/users/migrations/0001_initial.py b/api/funkwhale_api/users/migrations/0001_initial.py
index 8327d2890..ef9240c91 100644
--- a/api/funkwhale_api/users/migrations/0001_initial.py
+++ b/api/funkwhale_api/users/migrations/0001_initial.py
@@ -38,7 +38,7 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'users',
},
managers=[
- (b'objects', django.contrib.auth.models.UserManager()),
+ ('objects', django.contrib.auth.models.UserManager()),
],
),
]
diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py
index c5ca896ab..c8d0b534c 100644
--- a/api/funkwhale_api/users/models.py
+++ b/api/funkwhale_api/users/models.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals, absolute_import
from django.contrib.auth.models import AbstractUser
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
diff --git a/api/requirements/base.txt b/api/requirements/base.txt
index 3a11afadf..aee122259 100644
--- a/api/requirements/base.txt
+++ b/api/requirements/base.txt
@@ -1,59 +1,59 @@
# Bleeding edge Django
-django==1.11
+django>=2.0,<2.1
# Configuration
-django-environ==0.4.0
-django-secure==1.0.1
-whitenoise==2.0.6
-
-# Models
-django-model-utils==2.3.1
+django-environ>=0.4,<0.5
+whitenoise>=3.3,<3.4
# Images
-Pillow==3.0.0
+Pillow>=4.3,<4.4
# For user registration, either via email or social
# Well-built with regular release cycles!
-django-allauth==0.24.1
+django-allauth>=0.34,<0.35
# Python-PostgreSQL Database Adapter
-psycopg2==2.6.1
+psycopg2>=2.7,<=2.8
# Time zones support
-pytz==2015.7
+pytz==2017.3
# Redis support
-django-redis==4.3.0
-redis>=2.10.0
+django-redis>=4.5,<4.6
+redis>=2.10,<2.11
-celery==3.1.19
+celery>=3.1,<3.2
# Your custom requirements go here
-django-cors-headers==2.1.0
+django-cors-headers>=2.1,<2.2
musicbrainzngs==0.6
-youtube_dl>=2015.12.21
-djangorestframework==3.6.3
-djangorestframework-jwt==1.11.0
-django-celery==3.2.1
-django-mptt==0.8.7
-google-api-python-client==1.6.2
-arrow==0.10.0
-django-taggit==0.22.1
-persisting-theory==0.2.1
-django-versatileimagefield==1.7.1
-django-cachalot==1.5.0
-django-filter==1.1
-django-rest-auth==0.9.1
-beautifulsoup4==4.6.0
-Markdown==2.6.8
-ipython==6.1.0
-mutagen==1.38
+youtube_dl>=2017.12.14
+djangorestframework>=3.7,<3.8
+djangorestframework-jwt>=1.11,<1.12
+django-celery>=3.2,<3.3
+django-mptt>=0.9,<0.10
+google-api-python-client>=1.6,<1.7
+arrow>=0.12,<0.13
+persisting-theory>=0.2,<0.3
+django-versatileimagefield>=1.8,<1.9
+django-filter>=1.1,<1.2
+django-rest-auth>=0.9,<0.10
+beautifulsoup4>=4.6,<4.7
+Markdown>=2.6,<2.7
+ipython>=6,<7
+mutagen>=1.39,<1.40
+# Until this is merged
+#django-taggit>=0.22,<0.23
+git+https://github.com/jdufresne/django-taggit.git@e8f7f216f04c9781bebc84363ab24d575f948ede
# Until this is merged
git+https://github.com/EliotBerriot/PyMemoize.git@django
+# Until this is merged
+#django-cachalot==1.5.0
+git+https://github.com/EliotBerriot/django-cachalot.git@django-2
-django-dynamic-preferences>=1.3,<1.4
+django-dynamic-preferences>=1.5,<1.6
diff --git a/api/requirements/local.txt b/api/requirements/local.txt
index 3f681b26d..d8a1561e0 100644
--- a/api/requirements/local.txt
+++ b/api/requirements/local.txt
@@ -1,15 +1,15 @@
# Local development dependencies go here
--r base.txt
-coverage==4.0.3
-django_coverage_plugin==1.1
-Sphinx==1.6.2
-django-extensions==1.5.9
-Werkzeug==0.11.2
-django-test-plus==1.0.11
+
+coverage>=4.4,<4.5
+django_coverage_plugin>=1.5,<1.6
+Sphinx>=1.6,<1.7
+django-extensions>=1.9,<1.10
+Werkzeug>=0.13,<0.14
+django-test-plus>=1.0.20
factory_boy>=2.8.1
# django-debug-toolbar that works with Django 1.5+
-django-debug-toolbar>=1.5,<1.6
+django-debug-toolbar>=1.9,<1.10
# improved REPL
ipdb==0.8.1
diff --git a/api/requirements/production.txt b/api/requirements/production.txt
index 10d05fd34..42b66eb15 100644
--- a/api/requirements/production.txt
+++ b/api/requirements/production.txt
@@ -1,8 +1,5 @@
# Pro-tip: Try not to put anything here. There should be no dependency in
# production that isn't in development.
--r base.txt
-
-
# WSGI Handler
# ------------------------------------------------
diff --git a/api/requirements/test.txt b/api/requirements/test.txt
index 7c304bbdb..bde5a2df9 100644
--- a/api/requirements/test.txt
+++ b/api/requirements/test.txt
@@ -1,9 +1,6 @@
# Test dependencies go here.
--r local.txt
-
-flake8==2.5.0
-model-mommy==1.3.2
+flake8
pytest
pytest-django
pytest-mock
diff --git a/front/src/audio/index.js b/front/src/audio/index.js
index 7750ee500..4896b83b0 100644
--- a/front/src/audio/index.js
+++ b/front/src/audio/index.js
@@ -26,6 +26,7 @@ class Audio {
if (options.onEnded) {
this.onEnded = options.onEnded
}
+ this.onError = options.onError
this.state = {
preload: preload,
@@ -60,8 +61,12 @@ class Audio {
init (src, options = {}) {
if (!src) throw Error('src must be required')
this.state.startLoad = true
- if (this.state.tried === this.state.try) {
+ if (this.state.tried >= this.state.try) {
this.state.failed = true
+ logger.default.error('Cannot fetch audio', src)
+ if (this.onError) {
+ this.onError(src)
+ }
return
}
this.$Audio = new window.Audio(src)
diff --git a/front/src/audio/queue.js b/front/src/audio/queue.js
index bde0bf863..8c69638e8 100644
--- a/front/src/audio/queue.js
+++ b/front/src/audio/queue.js
@@ -140,7 +140,6 @@ class Queue {
} else {
index = index || this.tracks.length
}
- console.log('INDEEEEEX', index)
tracks.forEach((t) => {
self.append(t, index, true)
index += 1
@@ -243,7 +242,11 @@ class Queue {
rate: 1,
loop: false,
volume: this.state.volume,
- onEnded: this.handleAudioEnded.bind(this)
+ onEnded: this.handleAudioEnded.bind(this),
+ onError: function (src) {
+ self.errored = true
+ self.next()
+ }
})
this.audio = audio
audio.updateHook('playState', function (e) {
diff --git a/front/src/components/PageNotFound.vue b/front/src/components/PageNotFound.vue
new file mode 100644
index 000000000..3b88a6921
--- /dev/null
+++ b/front/src/components/PageNotFound.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+
We're sorry, the page you asked for does not exists.
+
Requested URL: {{ path }}
+
+ Go to home page
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front/src/components/auth/Login.vue b/front/src/components/auth/Login.vue
index 867738759..54e7b82e0 100644
--- a/front/src/components/auth/Login.vue
+++ b/front/src/components/auth/Login.vue
@@ -43,6 +43,9 @@ import auth from '@/auth'
export default {
name: 'login',
+ props: {
+ next: {type: String}
+ },
data () {
return {
// We need to initialize the component with any
@@ -69,7 +72,7 @@ export default {
}
// We need to pass the component's this context
// to properly make use of http in the auth service
- auth.login(this, credentials, {path: '/library'}, function (response) {
+ auth.login(this, credentials, {path: this.next}, function (response) {
// error callback
if (response.status === 400) {
self.error = 'invalid_credentials'
diff --git a/front/src/components/library/import/ArtistImport.vue b/front/src/components/library/import/ArtistImport.vue
index 4f049f52e..870a886e1 100644
--- a/front/src/components/library/import/ArtistImport.vue
+++ b/front/src/components/library/import/ArtistImport.vue
@@ -12,6 +12,10 @@
{{ t }}
+
+ Query template
+
+
diff --git a/front/src/components/library/import/ImportMixin.vue b/front/src/components/library/import/ImportMixin.vue
index f3fc6fca6..475241f3d 100644
--- a/front/src/components/library/import/ImportMixin.vue
+++ b/front/src/components/library/import/ImportMixin.vue
@@ -13,10 +13,12 @@ export default {
metadata: {type: Object, required: true},
defaultEnabled: {type: Boolean, default: true},
backends: {type: Array},
- defaultBackendId: {type: String}
+ defaultBackendId: {type: String},
+ queryTemplate: {type: String, default: '$artist $title'}
},
data () {
return {
+ customQueryTemplate: this.queryTemplate,
currentBackendId: this.defaultBackendId,
isImporting: false,
enabled: this.defaultEnabled
@@ -56,6 +58,9 @@ export default {
return this.backends.filter(b => {
return b.id === self.currentBackendId
})[0]
+ },
+ realQueryTemplate () {
+
}
},
watch: {
@@ -70,6 +75,14 @@ export default {
},
enabled (newValue) {
this.$emit('enabled', this.importData, newValue)
+ },
+ queryTemplate (newValue, oldValue) {
+ // we inherit from the prop template unless the component changed
+ // the value
+ if (oldValue === this.customQueryTemplate) {
+ // no changed from prop, we keep the sync
+ this.customQueryTemplate = newValue
+ }
}
}
}
diff --git a/front/src/components/library/import/ReleaseImport.vue b/front/src/components/library/import/ReleaseImport.vue
index 9f8c1d347..51d5a2fea 100644
--- a/front/src/components/library/import/ReleaseImport.vue
+++ b/front/src/components/library/import/ReleaseImport.vue
@@ -20,6 +20,7 @@
:release-metadata="metadata"
:backends="backends"
:default-backend-id="defaultBackendId"
+ :query-template="customQueryTemplate"
@import-data-changed="recordTrackData"
@enabled="recordTrackEnabled"
>
diff --git a/front/src/components/library/import/TrackImport.vue b/front/src/components/library/import/TrackImport.vue
index 3081091c5..2275bcf34 100644
--- a/front/src/components/library/import/TrackImport.vue
+++ b/front/src/components/library/import/TrackImport.vue
@@ -92,13 +92,7 @@ export default Vue.extend({
releaseMetadata: {type: Object, required: true}
},
data () {
- let queryParts = [
- this.releaseMetadata['artist-credit'][0]['artist']['name'],
- this.releaseMetadata['title'],
- this.metadata['recording']['title']
- ]
return {
- query: queryParts.join(' '),
isLoading: false,
results: [],
currentResultIndex: 0,
@@ -151,6 +145,18 @@ export default Vue.extend({
mbid: this.metadata.recording.id,
source: this.importedUrl
}
+ },
+ query () {
+ let queryMapping = [
+ ['artist', this.releaseMetadata['artist-credit'][0]['artist']['name']],
+ ['album', this.releaseMetadata['title']],
+ ['title', this.metadata['recording']['title']]
+ ]
+ let query = this.customQueryTemplate
+ queryMapping.forEach(e => {
+ query = query.split('$' + e[0]).join(e[1])
+ })
+ return query
}
},
watch: {
diff --git a/front/src/main.js b/front/src/main.js
index a214c3881..f15363512 100644
--- a/front/src/main.js
+++ b/front/src/main.js
@@ -32,7 +32,7 @@ Vue.http.interceptors.push(function (request, next) {
// redirect to login form when we get unauthorized response from server
if (response.status === 401) {
logger.default.warn('Received 401 response from API, redirecting to login form')
- router.push({name: 'login'})
+ router.push({name: 'login', query: {next: router.currentRoute.fullPath}})
}
})
})
diff --git a/front/src/router/index.js b/front/src/router/index.js
index e546172b5..d727276fc 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue'
import Router from 'vue-router'
+import PageNotFound from '@/components/PageNotFound'
import Home from '@/components/Home'
import Login from '@/components/auth/Login'
import Profile from '@/components/auth/Profile'
@@ -30,7 +31,8 @@ export default new Router({
{
path: '/login',
name: 'login',
- component: Login
+ component: Login,
+ props: (route) => ({ next: route.query.next || '/library' })
},
{
path: '/logout',
@@ -71,7 +73,7 @@ export default new Router({
},
{ path: 'import/batches/:id', name: 'library.import.batches.detail', component: BatchDetail, props: true }
]
- }
-
+ },
+ { path: '*', component: PageNotFound }
]
})