Verified Commit 20eaa5e6 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'release/0.2.5'

parents 142a8050 5ac9d261
...@@ -13,14 +13,22 @@ stages: ...@@ -13,14 +13,22 @@ stages:
test_api: test_api:
stage: test stage: test
image: funkwhale/funkwhale:base image: funkwhale/funkwhale:base
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
DATABASE_URL: "sqlite://"
before_script: before_script:
- python3 -m venv --copies virtualenv
- source virtualenv/bin/activate
- cd api - cd api
- pip install -r requirements/base.txt
- pip install -r requirements/local.txt
- pip install -r requirements/test.txt - pip install -r requirements/test.txt
script: script:
- pytest - pytest
variables: cache:
DATABASE_URL: "sqlite://" key: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
paths:
- "$CI_PROJECT_DIR/pip-cache"
tags: tags:
- docker - docker
......
...@@ -2,10 +2,30 @@ 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) 0.2.4 (2017-12-14)
------------------ ------------------
......
...@@ -8,7 +8,9 @@ COPY ./requirements.apt /requirements.apt ...@@ -8,7 +8,9 @@ COPY ./requirements.apt /requirements.apt
RUN apt-get update -qq && grep "^[^#;]" requirements.apt | xargs apt-get install -y 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 RUN pip install -r /requirements/production.txt
COPY . /app COPY . /app
......
...@@ -25,22 +25,32 @@ v1_patterns = router.urls ...@@ -25,22 +25,32 @@ v1_patterns = router.urls
v1_patterns += [ v1_patterns += [
url(r'^providers/', url(r'^providers/',
include('funkwhale_api.providers.urls', namespace='providers')), include(
('funkwhale_api.providers.urls', 'providers'),
namespace='providers')),
url(r'^favorites/', url(r'^favorites/',
include('funkwhale_api.favorites.urls', namespace='favorites')), include(
('funkwhale_api.favorites.urls', 'favorites'),
namespace='favorites')),
url(r'^search$', url(r'^search$',
views.Search.as_view(), name='search'), views.Search.as_view(), name='search'),
url(r'^radios/', url(r'^radios/',
include('funkwhale_api.radios.urls', namespace='radios')), include(
('funkwhale_api.radios.urls', 'radios'),
namespace='radios')),
url(r'^history/', url(r'^history/',
include('funkwhale_api.history.urls', namespace='history')), include(
('funkwhale_api.history.urls', 'history'),
namespace='history')),
url(r'^users/', url(r'^users/',
include('funkwhale_api.users.api_urls', namespace='users')), include(
('funkwhale_api.users.api_urls', 'users'),
namespace='users')),
url(r'^token/', url(r'^token/',
jwt_views.obtain_jwt_token), jwt_views.obtain_jwt_token),
url(r'^token/refresh/', jwt_views.refresh_jwt_token), url(r'^token/refresh/', jwt_views.refresh_jwt_token),
] ]
urlpatterns = [ urlpatterns = [
url(r'^v1/', include(v1_patterns, namespace='v1')) url(r'^v1/', include((v1_patterns, 'v1'), namespace='v1'))
] ]
...@@ -75,7 +75,7 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS ...@@ -75,7 +75,7 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# MIDDLEWARE CONFIGURATION # MIDDLEWARE CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
MIDDLEWARE_CLASSES = ( MIDDLEWARE = (
# Make sure djangosecure.middleware.SecurityMiddleware is listed first # Make sure djangosecure.middleware.SecurityMiddleware is listed first
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'funkwhale_api.users.middleware.AnonymousSessionMiddleware', 'funkwhale_api.users.middleware.AnonymousSessionMiddleware',
......
...@@ -31,7 +31,7 @@ EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', ...@@ -31,7 +31,7 @@ EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND',
# django-debug-toolbar # django-debug-toolbar
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
# INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',) # INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',)
......
...@@ -36,7 +36,7 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') ...@@ -36,7 +36,7 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# #
# #
# # Make sure djangosecure.middleware.SecurityMiddleware is listed first # # 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 # # set this to 60 seconds and then to 518400 when you can prove it works
# SECURE_HSTS_SECONDS = 60 # SECURE_HSTS_SECONDS = 60
...@@ -55,6 +55,8 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') ...@@ -55,6 +55,8 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Hosts/domain names that are valid for this site # Hosts/domain names that are valid for this site
# See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts # See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS') ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# END SITE CONFIGURATION # END SITE CONFIGURATION
INSTALLED_APPS += ("gunicorn", ) INSTALLED_APPS += ("gunicorn", )
......
...@@ -22,8 +22,8 @@ CACHES = { ...@@ -22,8 +22,8 @@ CACHES = {
'LOCATION': '' 'LOCATION': ''
} }
} }
INSTALLED_APPS += ('kombu.transport.django',)
BROKER_URL = 'django://' BROKER_URL = 'memory://'
# TESTING # TESTING
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
......
...@@ -10,9 +10,9 @@ from django.views import defaults as default_views ...@@ -10,9 +10,9 @@ from django.views import defaults as default_views
urlpatterns = [ urlpatterns = [
# Django Admin, use {% url 'admin:index' %} # 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/', include('rest_auth.urls')),
url(r'^api/auth/registration/', include('funkwhale_api.users.rest_auth_urls')), url(r'^api/auth/registration/', include('funkwhale_api.users.rest_auth_urls')),
url(r'^accounts/', include('allauth.urls')), url(r'^accounts/', include('allauth.urls')),
......
# -*- coding: utf-8 -*- # -*- 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('.')]) __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
...@@ -7,5 +7,5 @@ class ConditionalAuthentication(BasePermission): ...@@ -7,5 +7,5 @@ class ConditionalAuthentication(BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
if settings.API_AUTHENTICATION_REQUIRED: if settings.API_AUTHENTICATION_REQUIRED:
return request.user and request.user.is_authenticated() return request.user and request.user.is_authenticated
return True return True
...@@ -25,7 +25,7 @@ class Migration(migrations.Migration): ...@@ -25,7 +25,7 @@ class Migration(migrations.Migration):
'ordering': ('domain',), 'ordering': ('domain',),
}, },
managers=[ managers=[
(b'objects', django.contrib.sites.models.SiteManager()), ('objects', django.contrib.sites.models.SiteManager()),
], ],
), ),
] ]
...@@ -19,8 +19,8 @@ class Migration(migrations.Migration): ...@@ -19,8 +19,8 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
('creation_date', models.DateTimeField(default=django.utils.timezone.now)), ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
('track', models.ForeignKey(related_name='track_favorites', to='music.Track')), ('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)), ('user', models.ForeignKey(related_name='track_favorites', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
], ],
options={ options={
'ordering': ('-creation_date',), 'ordering': ('-creation_date',),
......
...@@ -5,8 +5,10 @@ from funkwhale_api.music.models import Track ...@@ -5,8 +5,10 @@ from funkwhale_api.music.models import Track
class TrackFavorite(models.Model): class TrackFavorite(models.Model):
creation_date = models.DateTimeField(default=timezone.now) creation_date = models.DateTimeField(default=timezone.now)
user = models.ForeignKey('users.User', related_name='track_favorites') user = models.ForeignKey(
track = models.ForeignKey(Track, related_name='track_favorites') 'users.User', related_name='track_favorites', on_delete=models.CASCADE)
track = models.ForeignKey(
Track, related_name='track_favorites', on_delete=models.CASCADE)
class Meta: class Meta:
unique_together = ('track', 'user') unique_together = ('track', 'user')
......
import json import json
from test_plus.test import TestCase 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.music.models import Track, Artist
from funkwhale_api.favorites.models import TrackFavorite from funkwhale_api.favorites.models import TrackFavorite
......
...@@ -20,8 +20,8 @@ class Migration(migrations.Migration): ...@@ -20,8 +20,8 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), ('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)), ('end_date', models.DateTimeField(null=True, blank=True, default=django.utils.timezone.now)),
('session_key', models.CharField(null=True, blank=True, max_length=100)), ('session_key', models.CharField(null=True, blank=True, max_length=100)),
('track', models.ForeignKey(related_name='listenings', to='music.Track')), ('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)), ('user', models.ForeignKey(blank=True, null=True, related_name='listenings', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
], ],
options={ options={
'ordering': ('-end_date',), 'ordering': ('-end_date',),
......
...@@ -7,8 +7,14 @@ from funkwhale_api.music.models import Track ...@@ -7,8 +7,14 @@ from funkwhale_api.music.models import Track
class Listening(models.Model): class Listening(models.Model):
end_date = models.DateTimeField(default=timezone.now, null=True, blank=True) end_date = models.DateTimeField(default=timezone.now, null=True, blank=True)
track = models.ForeignKey(Track, related_name="listenings") track = models.ForeignKey(
user = models.ForeignKey('users.User', related_name="listenings", null=True, blank=True) 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) session_key = models.CharField(max_length=100, null=True, blank=True)
class Meta: class Meta:
......
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'
import random import random
import json import json
from test_plus.test import TestCase 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.core.exceptions import ValidationError
from django.utils import timezone 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.users.models import User
from funkwhale_api.history import models from funkwhale_api.history import models
class TestHistory(TestCase): class TestHistory(TestCase):
def setUp(self): def setUp(self):
...@@ -17,12 +18,12 @@ class TestHistory(TestCase): ...@@ -17,12 +18,12 @@ class TestHistory(TestCase):
self.user = User.objects.create_user(username='test', email='test@test.com', password='test') self.user = User.objects.create_user(username='test', email='test@test.com', password='test')
def test_can_create_listening(self): def test_can_create_listening(self):
track = mommy.make('music.Track') track = TrackFactory()
now = timezone.now() now = timezone.now()
l = models.Listening.objects.create(user=self.user, track=track) l = models.Listening.objects.create(user=self.user, track=track)
def test_anonymous_user_can_create_listening_via_api(self): 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') url = self.reverse('api:v1:history:listenings-list')
response = self.client.post(url, { response = self.client.post(url, {
'track': track.pk, 'track': track.pk,
...@@ -34,7 +35,7 @@ class TestHistory(TestCase): ...@@ -34,7 +35,7 @@ class TestHistory(TestCase):
self.assertIsNotNone(listening.session_key) self.assertIsNotNone(listening.session_key)
def test_logged_in_user_can_create_listening_via_api(self): 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') self.client.login(username=self.user.username, password='test')
......
...@@ -22,14 +22,14 @@ class ListeningViewSet(mixins.CreateModelMixin, ...@@ -22,14 +22,14 @@ class ListeningViewSet(mixins.CreateModelMixin,
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
if self.request.user.is_authenticated(): if self.request.user.is_authenticated:
return queryset.filter(user=self.request.user) return queryset.filter(user=self.request.user)
else: else:
return queryset.filter(session_key=self.request.session.session_key) return queryset.filter(session_key=self.request.session.session_key)
def get_serializer_context(self): def get_serializer_context(self):
context = super().get_serializer_context() context = super().get_serializer_context()
if self.request.user.is_authenticated(): if self.request.user.is_authenticated:
context['user'] = self.request.user context['user'] = self.request.user
else: else:
context['session_key'] = self.request.session.session_key context['session_key'] = self.request.session.session_key
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment