Verified Commit bb3ed760 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'release/0.4'

parents c7d6ad95 a38ca1ed
BACKEND_URL=http://localhost:6001
API_AUTHENTICATION_REQUIRED=True API_AUTHENTICATION_REQUIRED=True
CACHALOT_ENABLED=False CACHALOT_ENABLED=False
RAVEN_ENABLED=false
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
...@@ -2,7 +2,7 @@ variables: ...@@ -2,7 +2,7 @@ variables:
IMAGE_NAME: funkwhale/funkwhale IMAGE_NAME: funkwhale/funkwhale
IMAGE: $IMAGE_NAME:$CI_COMMIT_REF_NAME IMAGE: $IMAGE_NAME:$CI_COMMIT_REF_NAME
IMAGE_LATEST: $IMAGE_NAME:latest IMAGE_LATEST: $IMAGE_NAME:latest
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
stages: stages:
...@@ -14,40 +14,62 @@ test_api: ...@@ -14,40 +14,62 @@ test_api:
services: services:
- postgres:9.4 - postgres:9.4
stage: test stage: test
image: funkwhale/funkwhale:base image: funkwhale/funkwhale:latest
cache:
key: "$CI_PROJECT_ID/pip_cache"
paths:
- "$PIP_CACHE_DIR"
variables: variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache" DJANGO_ALLOWED_HOSTS: "localhost"
DATABASE_URL: "postgresql://postgres@postgres/postgres" DATABASE_URL: "postgresql://postgres@postgres/postgres"
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/base.txt
- pip install -r requirements/local.txt - pip install -r requirements/local.txt
- pip install -r requirements/test.txt - pip install -r requirements/test.txt
script: script:
- pytest - pytest
tags:
- docker
test_front:
stage: test
image: node:9
before_script:
- cd front
script:
- yarn install
- yarn run unit
cache: cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" key: "$CI_PROJECT_ID/front_dependencies"
paths: paths:
- "$CI_PROJECT_DIR/pip-cache" - front/node_modules
- front/yarn.lock
artifacts:
name: "front_${CI_COMMIT_REF_NAME}"
paths:
- front/dist/
tags: tags:
- docker - docker
build_front: build_front:
stage: build stage: build
image: node:6-alpine image: node:9
before_script: before_script:
- cd front - cd front
script: script:
- npm install - yarn install
- npm run build - yarn run build
cache: cache:
key: "$CI_COMMIT_REF_NAME" key: "$CI_PROJECT_ID/front_dependencies"
paths: paths:
- front/node_modules - front/node_modules
- front/yarn.lock
artifacts: artifacts:
name: "front_${CI_COMMIT_REF_NAME}" name: "front_${CI_COMMIT_REF_NAME}"
paths: paths:
......
...@@ -2,8 +2,23 @@ Changelog ...@@ -2,8 +2,23 @@ Changelog
========= =========
0.3.5 (Unreleased) 0.5 (Unreleased)
------------------ ----------------
0.4 (2018-02-18)
----------------
- Front: ambiant colors in player based on current track cover (#59)
- Front: simplified front dev setup thanks to webpack proxy (#59)
- Front: added some unittests for the store (#55)
- Front: fixed broken login redirection when 401
- Front: Removed autoplay on page reload
- API: Added a /instance/settings endpoint
- Front: load /instance/settings on page load
- Added settings to report JS and Python error to a Sentry instance
This is disabled by default, but feel free to enable it if you want
to help us by sending your error reports :) (#8)
0.3.5 (2018-01-07) 0.3.5 (2018-01-07)
......
from rest_framework import routers from rest_framework import routers
from django.conf.urls import include, url from django.conf.urls import include, url
from funkwhale_api.instance import views as instance_views
from funkwhale_api.music import views from funkwhale_api.music import views
from funkwhale_api.playlists import views as playlists_views from funkwhale_api.playlists import views as playlists_views
from rest_framework_jwt import views as jwt_views from rest_framework_jwt import views as jwt_views
...@@ -25,6 +26,10 @@ router.register( ...@@ -25,6 +26,10 @@ router.register(
v1_patterns = router.urls v1_patterns = router.urls
v1_patterns += [ v1_patterns += [
url(r'^instance/',
include(
('funkwhale_api.instance.urls', 'instance'),
namespace='instance')),
url(r'^providers/', url(r'^providers/',
include( include(
('funkwhale_api.providers.urls', 'providers'), ('funkwhale_api.providers.urls', 'providers'),
......
...@@ -12,6 +12,7 @@ from __future__ import absolute_import, unicode_literals ...@@ -12,6 +12,7 @@ from __future__ import absolute_import, unicode_literals
import os import os
import environ import environ
from funkwhale_api import __version__
ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /) ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /)
APPS_DIR = ROOT_DIR.path('funkwhale_api') APPS_DIR = ROOT_DIR.path('funkwhale_api')
...@@ -22,6 +23,10 @@ try: ...@@ -22,6 +23,10 @@ try:
env.read_env(ROOT_DIR.file('.env')) env.read_env(ROOT_DIR.file('.env'))
except FileNotFoundError: except FileNotFoundError:
pass pass
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
# APP CONFIGURATION # APP CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
DJANGO_APPS = ( DJANGO_APPS = (
...@@ -56,10 +61,28 @@ THIRD_PARTY_APPS = ( ...@@ -56,10 +61,28 @@ THIRD_PARTY_APPS = (
'django_filters', 'django_filters',
) )
# Sentry
RAVEN_ENABLED = env.bool("RAVEN_ENABLED", default=False)
RAVEN_DSN = env("RAVEN_DSN", default='')
if RAVEN_ENABLED:
RAVEN_CONFIG = {
'dsn': RAVEN_DSN,
# If you are using git, you can also automatically configure the
# release based on the git info.
'release': __version__,
}
THIRD_PARTY_APPS += (
'raven.contrib.django.raven_compat',
)
# Apps specific for this project go here. # Apps specific for this project go here.
LOCAL_APPS = ( LOCAL_APPS = (
'funkwhale_api.users', # custom users app 'funkwhale_api.users', # custom users app
# Your stuff: custom apps go here # Your stuff: custom apps go here
'funkwhale_api.instance',
'funkwhale_api.music', 'funkwhale_api.music',
'funkwhale_api.favorites', 'funkwhale_api.favorites',
'funkwhale_api.radios', 'funkwhale_api.radios',
...@@ -71,6 +94,7 @@ LOCAL_APPS = ( ...@@ -71,6 +94,7 @@ LOCAL_APPS = (
) )
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# MIDDLEWARE CONFIGURATION # MIDDLEWARE CONFIGURATION
......
...@@ -54,7 +54,6 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') ...@@ -54,7 +54,6 @@ 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')
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# END SITE CONFIGURATION # END SITE CONFIGURATION
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = '0.3.5' __version__ = '0.4'
__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('.')])
...@@ -43,7 +43,7 @@ class TrackFavoriteViewSet(mixins.CreateModelMixin, ...@@ -43,7 +43,7 @@ class TrackFavoriteViewSet(mixins.CreateModelMixin,
favorite = models.TrackFavorite.add(track=track, user=self.request.user) favorite = models.TrackFavorite.add(track=track, user=self.request.user)
return favorite return favorite
@list_route(methods=['delete']) @list_route(methods=['delete', 'post'])
def remove(self, request, *args, **kwargs): def remove(self, request, *args, **kwargs):
try: try:
pk = int(request.data['track']) pk = int(request.data['track'])
......
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
raven = types.Section('raven')
@global_preferences_registry.register
class RavenDSN(types.StringPreference):
show_in_api = True
section = raven
name = 'front_dsn'
default = 'https://9e0562d46b09442bb8f6844e50cbca2b@sentry.eliotberriot.com/4'
verbose_name = (
'A raven DSN key used to report front-ent errors to '
'a sentry instance'
)
help_text = (
'Keeping the default one will report errors to funkwhale developers'
)
SENTRY_HELP_TEXT = (
'Error reporting is disabled by default but you can enable it if'
' you want to help us improve funkwhale'
)
@global_preferences_registry.register
class RavenEnabled(types.BooleanPreference):
show_in_api = True
section = raven
name = 'front_enabled'
default = False
verbose_name = (
'Wether error reporting to a Sentry instance using raven is enabled'
' for front-end errors'
)
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
]
from rest_framework import views
from rest_framework.response import Response
from dynamic_preferences.api import serializers
from dynamic_preferences.registries import global_preferences_registry
class InstanceSettings(views.APIView):
permission_classes = []
authentication_classes = []
def get(self, request, *args, **kwargs):
manager = global_preferences_registry.manager()
manager.all()
all_preferences = manager.model.objects.all().order_by(
'section', 'name'
)
api_preferences = [
p
for p in all_preferences
if getattr(p.preference, 'show_in_api', False)
]
data = serializers.GlobalPreferenceSerializer(
api_preferences, many=True).data
return Response(data, status=200)
...@@ -4,7 +4,7 @@ Populates the database with fake data ...@@ -4,7 +4,7 @@ Populates the database with fake data
import random import random
from funkwhale_api.music import models from funkwhale_api.music import models
from funkwhale_api.music.tests import factories from funkwhale_api.music import factories
def create_data(count=25): def create_data(count=25):
...@@ -19,4 +19,4 @@ def create_data(count=25): ...@@ -19,4 +19,4 @@ def create_data(count=25):
if __name__ == '__main__': if __name__ == '__main__':
main() create_data()
...@@ -31,10 +31,7 @@ class TrackFileSerializer(serializers.ModelSerializer): ...@@ -31,10 +31,7 @@ class TrackFileSerializer(serializers.ModelSerializer):
fields = ('id', 'path', 'duration', 'source', 'filename', 'track') fields = ('id', 'path', 'duration', 'source', 'filename', 'track')
def get_path(self, o): def get_path(self, o):
request = self.context.get('request')
url = o.path url = o.path
if request:
url = request.build_absolute_uri(url)
return url return url
......
...@@ -47,8 +47,7 @@ mutagen>=1.39,<1.40 ...@@ -47,8 +47,7 @@ mutagen>=1.39,<1.40
# Until this is merged # Until this is merged
#django-taggit>=0.22,<0.23 django-taggit>=0.22,<0.23
git+https://github.com/alex/django-taggit.git@95776ac66948ed7ba7c12e35c1170551e3be66a5
# Until this is merged # Until this is merged
git+https://github.com/EliotBerriot/PyMemoize.git@django git+https://github.com/EliotBerriot/PyMemoize.git@django
# Until this is merged # Until this is merged
...@@ -57,3 +56,4 @@ git+https://github.com/EliotBerriot/django-cachalot.git@django-2 ...@@ -57,3 +56,4 @@ git+https://github.com/EliotBerriot/django-cachalot.git@django-2
django-dynamic-preferences>=1.5,<1.6 django-dynamic-preferences>=1.5,<1.6
pyacoustid>=1.1.5,<1.2 pyacoustid>=1.1.5,<1.2
raven>=6.5,<7
...@@ -10,6 +10,7 @@ services: ...@@ -10,6 +10,7 @@ services:
volumes: volumes:
- .:/app - .:/app
environment: environment:
- "DJANGO_ALLOWED_HOSTS=localhost"
- "DATABASE_URL=postgresql://postgres@postgres/postgres" - "DATABASE_URL=postgresql://postgres@postgres/postgres"
postgres: postgres:
image: postgres image: postgres
...@@ -3,6 +3,7 @@ import shutil ...@@ -3,6 +3,7 @@ import shutil
import pytest import pytest
from django.core.cache import cache as django_cache from django.core.cache import cache as django_cache
from dynamic_preferences.registries import global_preferences_registry from dynamic_preferences.registries import global_preferences_registry
from rest_framework.test import APIClient
from funkwhale_api.taskapp import celery from funkwhale_api.taskapp import celery
...@@ -29,7 +30,9 @@ def factories(db): ...@@ -29,7 +30,9 @@ def factories(db):
@pytest.fixture @pytest.fixture
def preferences(db): def preferences(db):
yield global_preferences_registry.manager() manager = global_preferences_registry.manager()
manager.all()
yield manager
@pytest.fixture @pytest.fixture
...@@ -48,6 +51,11 @@ def logged_in_client(db, factories, client): ...@@ -48,6 +51,11 @@ def logged_in_client(db, factories, client):
delattr(client, 'user') delattr(client, 'user')
@pytest.fixture
def api_client(client):
return APIClient()
@pytest.fixture @pytest.fixture
def superuser_client(db, factories, client): def superuser_client(db, factories, client):
user = factories['users.SuperUser']() user = factories['users.SuperUser']()
......
from django.urls import reverse
from dynamic_preferences.api import serializers
def test_can_list_settings_via_api(preferences, api_client):
url = reverse('api:v1:instance:settings')
all_preferences = preferences.model.objects.all()
expected_preferences = {
p.preference.identifier(): p
for p in all_preferences
if getattr(p.preference, 'show_in_api', False)}
assert len(expected_preferences) > 0
response = api_client.get(url)
assert response.status_code == 200
assert len(response.data) == len(expected_preferences)
for p in response.data:
i = '__'.join([p['section'], p['name']])
assert i in expected_preferences
...@@ -58,11 +58,14 @@ def test_user_can_remove_favorite_via_api(logged_in_client, factories, client): ...@@ -58,11 +58,14 @@ def test_user_can_remove_favorite_via_api(logged_in_client, factories, client):
assert response.status_code == 204 assert response.status_code == 204
assert TrackFavorite.objects.count() == 0 assert TrackFavorite.objects.count() == 0
def test_user_can_remove_favorite_via_api_using_track_id(factories, logged_in_client):
@pytest.mark.parametrize('method', ['delete', 'post'])
def test_user_can_remove_favorite_via_api_using_track_id(
method, factories, logged_in_client):
favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user) favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
url = reverse('api:v1:favorites:tracks-remove') url = reverse('api:v1:favorites:tracks-remove')
response = logged_in_client.delete( response = getattr(logged_in_client, method)(
url, json.dumps({'track': favorite.track.pk}), url, json.dumps({'track': favorite.track.pk}),
content_type='application/json' content_type='application/json'
) )
......
...@@ -78,3 +78,10 @@ API_AUTHENTICATION_REQUIRED=True ...@@ -78,3 +78,10 @@ API_AUTHENTICATION_REQUIRED=True
# public: anybody can register an account # public: anybody can register an account
# disabled: nobody can register an account # disabled: nobody can register an account
REGISTRATION_MODE=disabled REGISTRATION_MODE=disabled
# Sentry/Raven error reporting (server side)
# Enable Raven if you want to help improve funkwhale by
# automatically sending error reports our Sentry instance.
# This will help us detect and correct bugs
RAVEN_ENABLED=false
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
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