Skip to content
Snippets Groups Projects
Verified Commit 37b6dd40 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'release/0.6'

parents 4530e4f4 6011cf20
Branches
Tags 0.19.0
No related merge requests found
Showing
with 324 additions and 15 deletions
from django.conf import settings
from rest_framework import serializers from rest_framework import serializers
from funkwhale_api.activity import serializers as activity_serializers
from funkwhale_api.music.serializers import TrackSerializerNested from funkwhale_api.music.serializers import TrackSerializerNested
from funkwhale_api.music.serializers import TrackActivitySerializer
from funkwhale_api.users.serializers import UserActivitySerializer
from . import models from . import models
class TrackFavoriteActivitySerializer(activity_serializers.ModelSerializer):
type = serializers.SerializerMethodField()
object = TrackActivitySerializer(source='track')
actor = UserActivitySerializer(source='user')
published = serializers.DateTimeField(source='creation_date')
class Meta:
model = models.TrackFavorite
fields = [
'id',
'local_id',
'object',
'type',
'actor',
'published'
]
def get_actor(self, obj):
return UserActivitySerializer(obj.user).data
def get_type(self, obj):
return 'Like'
class UserTrackFavoriteSerializer(serializers.ModelSerializer): class UserTrackFavoriteSerializer(serializers.ModelSerializer):
# track = TrackSerializerNested(read_only=True) # track = TrackSerializerNested(read_only=True)
class Meta: class Meta:
......
...@@ -4,6 +4,7 @@ from rest_framework.response import Response ...@@ -4,6 +4,7 @@ from rest_framework.response import Response
from rest_framework import pagination from rest_framework import pagination
from rest_framework.decorators import list_route from rest_framework.decorators import list_route
from funkwhale_api.activity import record
from funkwhale_api.music.models import Track from funkwhale_api.music.models import Track
from funkwhale_api.common.permissions import ConditionalAuthentication from funkwhale_api.common.permissions import ConditionalAuthentication
...@@ -33,6 +34,7 @@ class TrackFavoriteViewSet(mixins.CreateModelMixin, ...@@ -33,6 +34,7 @@ class TrackFavoriteViewSet(mixins.CreateModelMixin,
instance = self.perform_create(serializer) instance = self.perform_create(serializer)
serializer = self.get_serializer(instance=instance) serializer = self.get_serializer(instance=instance)
headers = self.get_success_headers(serializer.data) headers = self.get_success_headers(serializer.data)
record.send(instance)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def get_queryset(self): def get_queryset(self):
......
from funkwhale_api.common import channels
from funkwhale_api.activity import record
from . import serializers
record.registry.register_serializer(
serializers.ListeningActivitySerializer)
@record.registry.register_consumer('history.Listening')
def broadcast_listening_to_instance_activity(data, obj):
if obj.user.privacy_level not in ['instance', 'everyone']:
return
channels.group_send('instance_activity', {
'type': 'event.send',
'text': '',
'data': data
})
...@@ -25,3 +25,8 @@ class Listening(models.Model): ...@@ -25,3 +25,8 @@ class Listening(models.Model):
raise ValidationError('Cannot have both session_key and user empty for listening') raise ValidationError('Cannot have both session_key and user empty for listening')
super().save(**kwargs) super().save(**kwargs)
def get_activity_url(self):
return '{}/listenings/tracks/{}'.format(
self.user.get_activity_url(), self.pk)
from rest_framework import serializers from rest_framework import serializers
from funkwhale_api.activity import serializers as activity_serializers
from funkwhale_api.music.serializers import TrackSerializerNested from funkwhale_api.music.serializers import TrackSerializerNested
from funkwhale_api.music.serializers import TrackActivitySerializer
from funkwhale_api.users.serializers import UserActivitySerializer
from . import models from . import models
class ListeningActivitySerializer(activity_serializers.ModelSerializer):
type = serializers.SerializerMethodField()
object = TrackActivitySerializer(source='track')
actor = UserActivitySerializer(source='user')
published = serializers.DateTimeField(source='end_date')
class Meta:
model = models.Listening
fields = [
'id',
'local_id',
'object',
'type',
'actor',
'published'
]
def get_actor(self, obj):
return UserActivitySerializer(obj.user).data
def get_type(self, obj):
return 'Listen'
class ListeningSerializer(serializers.ModelSerializer): class ListeningSerializer(serializers.ModelSerializer):
class Meta: class Meta:
......
...@@ -3,8 +3,9 @@ from rest_framework import status ...@@ -3,8 +3,9 @@ from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import detail_route from rest_framework.decorators import detail_route
from funkwhale_api.music.serializers import TrackSerializerNested from funkwhale_api.activity import record
from funkwhale_api.common.permissions import ConditionalAuthentication from funkwhale_api.common.permissions import ConditionalAuthentication
from funkwhale_api.music.serializers import TrackSerializerNested
from . import models from . import models
from . import serializers from . import serializers
...@@ -17,6 +18,12 @@ class ListeningViewSet(mixins.CreateModelMixin, ...@@ -17,6 +18,12 @@ class ListeningViewSet(mixins.CreateModelMixin,
queryset = models.Listening.objects.all() queryset = models.Listening.objects.all()
permission_classes = [ConditionalAuthentication] permission_classes = [ConditionalAuthentication]
def perform_create(self, serializer):
r = super().perform_create(serializer)
if self.request.user.is_authenticated:
record.send(serializer.instance)
return r
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:
......
from funkwhale_api.common.consumers import JsonAuthConsumer
class InstanceActivityConsumer(JsonAuthConsumer):
groups = ["instance_activity"]
def event_send(self, message):
self.send_json(message['data'])
...@@ -360,6 +360,12 @@ class Track(APIModelMixin): ...@@ -360,6 +360,12 @@ class Track(APIModelMixin):
self.title, self.title,
) )
def get_activity_url(self):
if self.mbid:
return 'https://musicbrainz.org/recording/{}'.format(
self.mbid)
return settings.FUNKWHALE_URL + '/tracks/{}'.format(self.pk)
class TrackFile(models.Model): class TrackFile(models.Model):
track = models.ForeignKey( track = models.ForeignKey(
......
from rest_framework import serializers from rest_framework import serializers
from taggit.models import Tag from taggit.models import Tag
from funkwhale_api.activity import serializers as activity_serializers
from . import models from . import models
...@@ -127,3 +129,24 @@ class ImportBatchSerializer(serializers.ModelSerializer): ...@@ -127,3 +129,24 @@ class ImportBatchSerializer(serializers.ModelSerializer):
model = models.ImportBatch model = models.ImportBatch
fields = ('id', 'jobs', 'status', 'creation_date', 'import_request') fields = ('id', 'jobs', 'status', 'creation_date', 'import_request')
read_only_fields = ('creation_date',) read_only_fields = ('creation_date',)
class TrackActivitySerializer(activity_serializers.ModelSerializer):
type = serializers.SerializerMethodField()
name = serializers.CharField(source='title')
artist = serializers.CharField(source='artist.name')
album = serializers.CharField(source='album.title')
class Meta:
model = models.Track
fields = [
'id',
'local_id',
'name',
'type',
'artist',
'album',
]
def get_type(self, obj):
return 'Audio'
# Generated by Django 2.0.2 on 2018-03-01 19:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0003_auto_20171226_1357'),
]
operations = [
migrations.AddField(
model_name='user',
name='privacy_level',
field=models.CharField(choices=[('me', 'Only me'), ('followers', 'Me and my followers'), ('instance', 'Everyone on my instance, and my followers'), ('everyone', 'Everyone, including people on other instances')], default='instance', max_length=30),
),
]
...@@ -3,6 +3,7 @@ from __future__ import unicode_literals, absolute_import ...@@ -3,6 +3,7 @@ from __future__ import unicode_literals, absolute_import
import uuid import uuid
from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.urls import reverse from django.urls import reverse
from django.db import models from django.db import models
...@@ -10,6 +11,14 @@ from django.utils.encoding import python_2_unicode_compatible ...@@ -10,6 +11,14 @@ from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
PRIVACY_LEVEL_CHOICES = [
('me', 'Only me'),
('followers', 'Me and my followers'),
('instance', 'Everyone on my instance, and my followers'),
('everyone', 'Everyone, including people on other instances'),
]
@python_2_unicode_compatible @python_2_unicode_compatible
class User(AbstractUser): class User(AbstractUser):
...@@ -30,6 +39,9 @@ class User(AbstractUser): ...@@ -30,6 +39,9 @@ class User(AbstractUser):
}, },
} }
privacy_level = models.CharField(
max_length=30, choices=PRIVACY_LEVEL_CHOICES, default='instance')
def __str__(self): def __str__(self):
return self.username return self.username
...@@ -43,3 +55,6 @@ class User(AbstractUser): ...@@ -43,3 +55,6 @@ class User(AbstractUser):
def set_password(self, raw_password): def set_password(self, raw_password):
super().set_password(raw_password) super().set_password(raw_password)
self.update_secret_key() self.update_secret_key()
def get_activity_url(self):
return settings.FUNKWHALE_URL + '/@{}'.format(self.username)
from rest_framework import serializers from rest_framework import serializers
from funkwhale_api.activity import serializers as activity_serializers
from . import models from . import models
class UserActivitySerializer(activity_serializers.ModelSerializer):
type = serializers.SerializerMethodField()
name = serializers.CharField(source='username')
local_id = serializers.CharField(source='username')
class Meta:
model = models.User
fields = [
'id',
'local_id',
'name',
'type'
]
def get_type(self, obj):
return 'Person'
class UserBasicSerializer(serializers.ModelSerializer): class UserBasicSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.User model = models.User
fields = ['id', 'username', 'name', 'date_joined'] fields = ['id', 'username', 'name', 'date_joined']
class UserSerializer(serializers.ModelSerializer): class UserWriteSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = [
'name',
'privacy_level'
]
class UserReadSerializer(serializers.ModelSerializer):
permissions = serializers.SerializerMethodField() permissions = serializers.SerializerMethodField()
...@@ -24,6 +53,7 @@ class UserSerializer(serializers.ModelSerializer): ...@@ -24,6 +53,7 @@ class UserSerializer(serializers.ModelSerializer):
'is_superuser', 'is_superuser',
'permissions', 'permissions',
'date_joined', 'date_joined',
'privacy_level'
] ]
def get_permissions(self, o): def get_permissions(self, o):
......
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import mixins
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import list_route from rest_framework.decorators import list_route
...@@ -23,12 +24,25 @@ class RegisterView(BaseRegisterView): ...@@ -23,12 +24,25 @@ class RegisterView(BaseRegisterView):
return get_adapter().is_open_for_signup(request) return get_adapter().is_open_for_signup(request)
class UserViewSet(viewsets.GenericViewSet): class UserViewSet(
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
queryset = models.User.objects.all() queryset = models.User.objects.all()
serializer_class = serializers.UserSerializer serializer_class = serializers.UserWriteSerializer
lookup_field = 'username'
@list_route(methods=['get']) @list_route(methods=['get'])
def me(self, request, *args, **kwargs): def me(self, request, *args, **kwargs):
"""Return information about the current user""" """Return information about the current user"""
serializer = self.serializer_class(request.user) serializer = serializers.UserReadSerializer(request.user)
return Response(serializer.data) return Response(serializer.data)
def update(self, request, *args, **kwargs):
if not self.request.user.username == kwargs.get('username'):
return Response(status=403)
return super().update(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
if not self.request.user.username == kwargs.get('username'):
return Response(status=403)
return super().partial_update(request, *args, **kwargs)
build-essential build-essential
gettext curl
zlib1g-dev ffmpeg
libjpeg-dev libjpeg-dev
zlib1g-dev libmagic-dev
libpq-dev libpq-dev
postgresql-client postgresql-client
libmagic-dev
ffmpeg
python3-dev python3-dev
curl
...@@ -59,3 +59,5 @@ pyacoustid>=1.1.5,<1.2 ...@@ -59,3 +59,5 @@ pyacoustid>=1.1.5,<1.2
raven>=6.5,<7 raven>=6.5,<7
python-magic==0.4.15 python-magic==0.4.15
ffmpeg-python==0.1.10 ffmpeg-python==0.1.10
channels>=2,<2.1
channels_redis>=2.1,<2.2
...@@ -4,7 +4,4 @@ ...@@ -4,7 +4,4 @@
# WSGI Handler # WSGI Handler
# ------------------------------------------------ # ------------------------------------------------
# there's no python 3 support in stable, have to use the latest release candidate for gevent daphne==2.0.4
gevent==1.1rc1
gunicorn==19.4.1
...@@ -12,5 +12,6 @@ services: ...@@ -12,5 +12,6 @@ services:
environment: environment:
- "DJANGO_ALLOWED_HOSTS=localhost" - "DJANGO_ALLOWED_HOSTS=localhost"
- "DATABASE_URL=postgresql://postgres@postgres/postgres" - "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "FUNKWHALE_URL=https://funkwhale.test"
postgres: postgres:
image: postgres image: postgres
import pytest
from django.db import models
from rest_framework import serializers
from funkwhale_api.activity import record
class FakeModel(models.Model):
class Meta:
app_label = 'tests'
class FakeSerializer(serializers.ModelSerializer):
class Meta:
model = FakeModel
fields = ['id']
def test_can_bind_serializer_to_model(activity_registry):
activity_registry.register_serializer(FakeSerializer)
assert activity_registry['tests.FakeModel']['serializer'] == FakeSerializer
def test_can_bind_consumer_to_model(activity_registry):
activity_registry.register_serializer(FakeSerializer)
@activity_registry.register_consumer('tests.FakeModel')
def propagate(data, obj):
return True
assert activity_registry['tests.FakeModel']['consumers'] == [propagate]
def test_record_object_calls_consumer(activity_registry, mocker):
activity_registry.register_serializer(FakeSerializer)
stub = mocker.stub()
activity_registry.register_consumer('tests.FakeModel')(stub)
o = FakeModel(id=1)
data = FakeSerializer(o).data
record.send(o)
stub.assert_called_once_with(data=data, obj=o)
import pytest
from rest_framework_jwt.settings import api_settings
from funkwhale_api.common.auth import TokenAuthMiddleware
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
@pytest.mark.parametrize('query_string', [
b'token=wrong',
b'',
])
def test_header_anonymous(query_string, factories):
def callback(scope):
assert scope['user'].is_anonymous
scope = {
'query_string': query_string
}
consumer = TokenAuthMiddleware(callback)
consumer(scope)
def test_header_correct_token(factories):
user = factories['users.User']()
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
def callback(scope):
assert scope['user'] == user
scope = {
'query_string': 'token={}'.format(token).encode('utf-8')
}
consumer = TokenAuthMiddleware(callback)
consumer(scope)
from funkwhale_api.common import consumers
def test_auth_consumer_requires_valid_user(mocker):
m = mocker.patch('funkwhale_api.common.consumers.JsonAuthConsumer.close')
scope = {'user': None}
consumer = consumers.JsonAuthConsumer(scope=scope)
consumer.connect()
m.assert_called_once_with()
def test_auth_consumer_requires_user_in_scope(mocker):
m = mocker.patch('funkwhale_api.common.consumers.JsonAuthConsumer.close')
scope = {}
consumer = consumers.JsonAuthConsumer(scope=scope)
consumer.connect()
m.assert_called_once_with()
def test_auth_consumer_accepts_connection(mocker, factories):
user = factories['users.User']()
m = mocker.patch('funkwhale_api.common.consumers.JsonAuthConsumer.accept')
scope = {'user': user}
consumer = consumers.JsonAuthConsumer(scope=scope)
consumer.connect()
m.assert_called_once_with()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment