Views and logic for custom radios

merge-requests/154/head
Eliot Berriot 2018-01-07 22:13:32 +01:00
rodzic df63252105
commit e7f0c1b88b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: DD6965E2476E5C27
13 zmienionych plików z 774 dodań i 18 usunięć

Wyświetl plik

@ -262,6 +262,16 @@ class Lyrics(models.Model):
extensions=['markdown.extensions.nl2br'])
class TrackQuerySet(models.QuerySet):
def for_nested_serialization(self):
return (self.select_related()
.select_related('album__artist')
.prefetch_related(
'tags',
'files',
'artist__albums__tracks__tags'))
class Track(APIModelMixin):
title = models.CharField(max_length=255)
artist = models.ForeignKey(
@ -302,6 +312,7 @@ class Track(APIModelMixin):
import_hooks = [
import_tags
]
objects = TrackQuerySet.as_manager()
tags = TaggableManager()
class Meta:

Wyświetl plik

@ -116,13 +116,7 @@ class TrackViewSet(TagViewSetMixin, SearchMixin, viewsets.ReadOnlyModelViewSet):
"""
A simple ViewSet for viewing and editing accounts.
"""
queryset = (models.Track.objects.all()
.select_related()
.select_related('album__artist')
.prefetch_related(
'tags',
'files',
'artist__albums__tracks__tags'))
queryset = (models.Track.objects.all().for_nested_serialization())
serializer_class = serializers.TrackSerializerNested
permission_classes = [ConditionalAuthentication]
search_fields = ['title', 'artist__name']

Wyświetl plik

@ -0,0 +1,34 @@
import factory
from funkwhale_api.factories import registry
from funkwhale_api.users.factories import UserFactory
@registry.register
class RadioFactory(factory.django.DjangoModelFactory):
name = factory.Faker('name')
description = factory.Faker('paragraphs')
user = factory.SubFactory(UserFactory)
config = []
class Meta:
model = 'radios.Radio'
@registry.register
class RadioSessionFactory(factory.django.DjangoModelFactory):
user = factory.SubFactory(UserFactory)
class Meta:
model = 'radios.RadioSession'
@registry.register(name='radios.CustomRadioSession')
class RadioSessionFactory(factory.django.DjangoModelFactory):
user = factory.SubFactory(UserFactory)
radio_type = 'custom'
custom_radio = factory.SubFactory(
RadioFactory, user=factory.SelfAttribute('..user'))
class Meta:
model = 'radios.RadioSession'

Wyświetl plik

@ -0,0 +1,201 @@
import collections
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.urls import reverse_lazy
import persisting_theory
from funkwhale_api.music import models
from funkwhale_api.taskapp.celery import require_instance
class RadioFilterRegistry(persisting_theory.Registry):
def prepare_data(self, data):
return data()
def prepare_name(self, data, name=None):
return data.code
@property
def exposed_filters(self):
return [
f for f in self.values() if f.expose_in_api
]
registry = RadioFilterRegistry()
def run(filters, **kwargs):
candidates = kwargs.pop('candidates', models.Track.objects.all())
final_query = None
final_query = registry['group'].get_query(
candidates, filters=filters, **kwargs)
if final_query:
candidates = candidates.filter(final_query)
return candidates.order_by('pk')
def validate(filter_config):
try:
f = registry[filter_config['type']]
except KeyError:
raise ValidationError(
'Invalid type "{}"'.format(filter_config['type']))
f.validate(filter_config)
return True
def test(filter_config, **kwargs):
"""
Run validation and also gather the candidates for the given config
"""
data = {
'errors': [],
'candidates': {
'count': None,
'sample': None,
}
}
try:
validate(filter_config)
except ValidationError as e:
data['errors'] = [e.message]
return data
candidates = run([filter_config], **kwargs)
data['candidates']['count'] = candidates.count()
data['candidates']['sample'] = candidates[:10]
return data
def clean_config(filter_config):
f = registry[filter_config['type']]
return f.clean_config(filter_config)
class RadioFilter(object):
help_text = None
label = None
fields = []
expose_in_api = True
def get_query(self, candidates, **kwargs):
return candidates
def clean_config(self, filter_config):
return filter_config
def validate(self, config):
operator = config.get('operator', 'and')
try:
assert operator in ['or', 'and']
except AssertionError:
raise ValidationError(
'Invalid operator "{}"'.format(config['operator']))
@registry.register
class GroupFilter(RadioFilter):
code = 'group'
expose_in_api = False
def get_query(self, candidates, filters, **kwargs):
if not filters:
return
final_query = None
for filter_config in filters:
f = registry[filter_config['type']]
conf = collections.ChainMap(filter_config, kwargs)
query = f.get_query(candidates, **conf)
if filter_config.get('not', False):
query = ~query
if not final_query:
final_query = query
else:
operator = filter_config.get('operator', 'and')
if operator == 'and':
final_query &= query
elif operator == 'or':
final_query |= query
else:
raise ValueError(
'Invalid query operator "{}"'.format(operator))
return final_query
def validate(self, config):
super().validate(config)
for fc in config['filters']:
registry[fc['type']].validate(fc)
@registry.register
class ArtistFilter(RadioFilter):
code = 'artist'
label = 'Artist'
help_text = 'Select tracks for a given artist'
fields = [
{
'name': 'ids',
'type': 'list',
'subtype': 'number',
'autocomplete': reverse_lazy('api:v1:artists-search'),
'autocomplete_qs': 'query={query}',
'autocomplete_fields': {'name': 'name', 'value': 'id'},
'label': 'Artist',
'placeholder': 'Select artists'
}
]
def clean_config(self, filter_config):
filter_config = super().clean_config(filter_config)
filter_config['ids'] = sorted(filter_config['ids'])
names = models.Artist.objects.filter(
pk__in=filter_config['ids']
).order_by('id').values_list('name', flat=True)
filter_config['names'] = list(names)
return filter_config
def get_query(self, candidates, ids, **kwargs):
return Q(artist__pk__in=ids)
def validate(self, config):
super().validate(config)
try:
pks = models.Artist.objects.filter(
pk__in=config['ids']).values_list('pk', flat=True)
diff = set(config['ids']) - set(pks)
assert len(diff) == 0
except KeyError:
raise ValidationError('You must provide an id')
except AssertionError:
raise ValidationError(
'No artist matching ids "{}"'.format(diff))
@registry.register
class TagFilter(RadioFilter):
code = 'tag'
fields = [
{
'name': 'names',
'type': 'list',
'subtype': 'string',
'autocomplete': reverse_lazy('api:v1:tags-list'),
'autocomplete_qs': '',
'autocomplete_fields': {'remoteValues': 'results', 'name': 'name', 'value': 'slug'},
'autocomplete_qs': 'query={query}',
'label': 'Tags',
'placeholder': 'Select tags'
}
]
help_text = 'Select tracks with a given tag'
label = 'Tag'
def get_query(self, candidates, names, **kwargs):
return Q(tags__slug__in=names)

Wyświetl plik

@ -0,0 +1,12 @@
import django_filters
from . import models
class RadioFilter(django_filters.FilterSet):
class Meta:
model = models.Radio
fields = {
'name': ['exact', 'iexact', 'startswith', 'icontains']
}

Wyświetl plik

@ -1,11 +1,15 @@
import random
from rest_framework import serializers
from django.core.exceptions import ValidationError
from taggit.models import Tag
from funkwhale_api.users.models import User
from funkwhale_api.music.models import Track, Artist
from . import filters
from . import models
from .registries import registry
class SimpleRadio(object):
def clean(self, instance):
@ -50,7 +54,7 @@ class SessionRadio(SimpleRadio):
def filter_from_session(self, queryset):
already_played = self.session.session_tracks.all().values_list('track', flat=True)
queryset = queryset.exclude(pk__in=list(already_played))
queryset = queryset.exclude(pk__in=already_played)
return queryset
def pick(self, **kwargs):
@ -64,6 +68,10 @@ class SessionRadio(SimpleRadio):
self.session.add(choice)
return picked_choices
def validate_session(self, data, **context):
return data
@registry.register(name='random')
class RandomRadio(SessionRadio):
def get_queryset(self, **kwargs):
@ -83,6 +91,37 @@ class FavoritesRadio(SessionRadio):
return Track.objects.filter(pk__in=track_ids)
@registry.register(name='custom')
class CustomRadio(SessionRadio):
def get_queryset_kwargs(self):
kwargs = super().get_queryset_kwargs()
kwargs['user'] = self.session.user
kwargs['custom_radio'] = self.session.custom_radio
return kwargs
def get_queryset(self, **kwargs):
return filters.run(kwargs['custom_radio'].config)
def validate_session(self, data, **context):
data = super().validate_session(data, **context)
try:
user = data['user']
except KeyError:
user = context['user']
try:
assert (
data['custom_radio'].user == user or
data['custom_radio'].is_public)
except KeyError:
raise serializers.ValidationError(
'You must provide a custom radio')
except AssertionError:
raise serializers.ValidationError(
"You don't have access to this radio")
return data
class RelatedObjectRadio(SessionRadio):
"""Abstract radio related to an object (tag, artist, user...)"""

Wyświetl plik

@ -1,8 +1,39 @@
from rest_framework import serializers
from funkwhale_api.music.serializers import TrackSerializerNested
from . import models
from . import filters
from . import models
from .radios import registry
class FilterSerializer(serializers.Serializer):
type = serializers.CharField(source='code')
label = serializers.CharField()
help_text = serializers.CharField()
fields = serializers.ReadOnlyField()
class RadioSerializer(serializers.ModelSerializer):
class Meta:
model = models.Radio
fields = (
'id',
'is_public',
'name',
'creation_date',
'user',
'config',
'description')
read_only_fields = ('user', 'creation_date')
def save(self, **kwargs):
kwargs['config'] = [
filters.registry[f['type']].clean_config(f)
for f in self.validated_data['config']
]
return super().save(**kwargs)
class RadioSessionTrackSerializerCreate(serializers.ModelSerializer):
class Meta:
@ -21,7 +52,18 @@ class RadioSessionTrackSerializer(serializers.ModelSerializer):
class RadioSessionSerializer(serializers.ModelSerializer):
class Meta:
model = models.RadioSession
fields = ('id', 'radio_type', 'related_object_id', 'user', 'creation_date', 'session_key')
fields = (
'id',
'radio_type',
'related_object_id',
'user',
'creation_date',
'custom_radio',
'session_key')
def validate(self, data):
registry[data['radio_type']]().validate_session(data, **self.context)
return data
def create(self, validated_data):
if self.context.get('user'):
@ -29,7 +71,6 @@ class RadioSessionSerializer(serializers.ModelSerializer):
else:
validated_data['session_key'] = self.context['session_key']
if validated_data.get('related_object_id'):
from . import radios
radio = radios.registry[validated_data['radio_type']]()
radio = registry[validated_data['radio_type']]()
validated_data['related_object'] = radio.get_related_object(validated_data['related_object_id'])
return super().create(validated_data)

Wyświetl plik

@ -4,6 +4,7 @@ from . import views
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'sessions', views.RadioSessionViewSet, 'sessions')
router.register(r'radios', views.RadioViewSet, 'radios')
router.register(r'tracks', views.RadioSessionTrackViewSet, 'tracks')

Wyświetl plik

@ -1,14 +1,72 @@
from django.db.models import Q
from django.http import Http404
from rest_framework import generics, mixins, viewsets
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import detail_route
from rest_framework.decorators import detail_route, list_route
from funkwhale_api.music.serializers import TrackSerializerNested
from funkwhale_api.common.permissions import ConditionalAuthentication
from . import models
from . import filters
from . import filtersets
from . import serializers
class RadioViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
serializer_class = serializers.RadioSerializer
permission_classes = [ConditionalAuthentication]
filter_class = filtersets.RadioFilter
def get_queryset(self):
query = Q(is_public=True)
if self.request.user.is_authenticated:
query |= Q(user=self.request.user)
return models.Radio.objects.filter(query)
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
def perform_update(self, serializer):
if serializer.instance.user != self.request.user:
raise Http404
return serializer.save(user=self.request.user)
@list_route(methods=['get'])
def filters(self, request, *args, **kwargs):
serializer = serializers.FilterSerializer(
filters.registry.exposed_filters, many=True)
return Response(serializer.data)
@list_route(methods=['post'])
def validate(self, request, *args, **kwargs):
try:
f_list = request.data['filters']
except KeyError:
return Response(
{'error': 'You must provide a filters list'}, status=400)
data = {
'filters': []
}
for f in f_list:
results = filters.test(f)
if results['candidates']['sample']:
qs = results['candidates']['sample'].for_nested_serialization()
results['candidates']['sample'] = TrackSerializerNested(
qs, many=True).data
data['filters'].append(results)
return Response(data)
class RadioSessionViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):

Wyświetl plik

@ -27,11 +27,12 @@ class CeleryConfig(AppConfig):
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS, force=True)
def require_instance(model_or_qs, parameter_name):
def require_instance(model_or_qs, parameter_name, id_kwarg_name=None):
def decorator(function):
@functools.wraps(function)
def inner(*args, **kwargs):
pk = kwargs.pop('_'.join([parameter_name, 'id']))
kw = id_kwarg_name or '_'.join([parameter_name, 'id'])
pk = kwargs.pop(kw)
try:
instance = model_or_qs.get(pk=pk)
except AttributeError:

Wyświetl plik

@ -0,0 +1,159 @@
import json
import pytest
from django.urls import reverse
from funkwhale_api.music.serializers import TrackSerializerNested
from funkwhale_api.radios import filters
from funkwhale_api.radios import serializers
def test_can_list_config_options(logged_in_client):
url = reverse('api:v1:radios:radios-filters')
response = logged_in_client.get(url)
assert response.status_code == 200
payload = json.loads(response.content.decode('utf-8'))
expected = [f for f in filters.registry.values() if f.expose_in_api]
assert len(payload) == len(expected)
def test_can_validate_config(logged_in_client, factories):
artist1 = factories['music.Artist']()
artist2 = factories['music.Artist']()
factories['music.Track'].create_batch(3, artist=artist1)
factories['music.Track'].create_batch(3, artist=artist2)
candidates = artist1.tracks.order_by('pk')
f = {
'filters': [
{'type': 'artist', 'ids': [artist1.pk]}
]
}
url = reverse('api:v1:radios:radios-validate')
response = logged_in_client.post(
url,
json.dumps(f),
content_type="application/json")
assert response.status_code == 200
payload = json.loads(response.content.decode('utf-8'))
expected = {
'count': candidates.count(),
'sample': TrackSerializerNested(candidates, many=True).data
}
assert payload['filters'][0]['candidates'] == expected
assert payload['filters'][0]['errors'] == []
def test_can_validate_config_with_wrong_config(logged_in_client, factories):
f = {
'filters': [
{'type': 'artist', 'ids': [999]}
]
}
url = reverse('api:v1:radios:radios-validate')
response = logged_in_client.post(
url,
json.dumps(f),
content_type="application/json")
assert response.status_code == 200
payload = json.loads(response.content.decode('utf-8'))
expected = {
'count': None,
'sample': None
}
assert payload['filters'][0]['candidates'] == expected
assert len(payload['filters'][0]['errors']) == 1
def test_saving_radio_sets_user(logged_in_client, factories):
artist = factories['music.Artist']()
f = {
'name': 'Test',
'config': [
{'type': 'artist', 'ids': [artist.pk]}
]
}
url = reverse('api:v1:radios:radios-list')
response = logged_in_client.post(
url,
json.dumps(f),
content_type="application/json")
assert response.status_code == 201
radio = logged_in_client.user.radios.latest('id')
assert radio.name == 'Test'
assert radio.user == logged_in_client.user
def test_user_can_detail_his_radio(logged_in_client, factories):
radio = factories['radios.Radio'](user=logged_in_client.user)
url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
response = logged_in_client.get(url)
assert response.status_code == 200
def test_user_can_detail_public_radio(logged_in_client, factories):
radio = factories['radios.Radio'](is_public=True)
url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
response = logged_in_client.get(url)
assert response.status_code == 200
def test_user_cannot_detail_someone_else_radio(logged_in_client, factories):
radio = factories['radios.Radio'](is_public=False)
url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
response = logged_in_client.get(url)
assert response.status_code == 404
def test_user_can_edit_his_radio(logged_in_client, factories):
radio = factories['radios.Radio'](user=logged_in_client.user)
url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
response = logged_in_client.put(
url,
json.dumps({'name': 'new', 'config': []}),
content_type="application/json")
radio.refresh_from_db()
assert response.status_code == 200
assert radio.name == 'new'
def test_user_cannot_edit_someone_else_radio(logged_in_client, factories):
radio = factories['radios.Radio']()
url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
response = logged_in_client.put(
url,
json.dumps({'name': 'new', 'config': []}),
content_type="application/json")
assert response.status_code == 404
def test_clean_config_is_called_on_serializer_save(mocker, factories):
user = factories['users.User']()
artist = factories['music.Artist']()
data= {
'name': 'Test',
'config': [
{'type': 'artist', 'ids': [artist.pk]}
]
}
spied = mocker.spy(filters.registry['artist'], 'clean_config')
serializer = serializers.RadioSerializer(data=data)
assert serializer.is_valid()
instance = serializer.save(user=user)
spied.assert_called_once_with(data['config'][0])
assert instance.config[0]['names'] == [artist.name]

Wyświetl plik

@ -0,0 +1,161 @@
import pytest
from django.core.exceptions import ValidationError
from funkwhale_api.music.models import Track
from funkwhale_api.radios import filters
@filters.registry.register
class NoopFilter(filters.RadioFilter):
code = 'noop'
def get_query(self, candidates, **kwargs):
return
def test_most_simple_radio_does_not_filter_anything(factories):
tracks = factories['music.Track'].create_batch(3)
radio = factories['radios.Radio'](config=[{'type': 'noop'}])
assert radio.version == 0
assert radio.get_candidates().count() == 3
def test_filter_can_use_custom_queryset(factories):
tracks = factories['music.Track'].create_batch(3)
candidates = Track.objects.filter(pk=tracks[0].pk)
qs = filters.run([{'type': 'noop'}], candidates=candidates)
assert qs.count() == 1
assert qs.first() == tracks[0]
def test_filter_on_tag(factories):
tracks = factories['music.Track'].create_batch(3, tags=['metal'])
factories['music.Track'].create_batch(3, tags=['pop'])
expected = tracks
f = [
{'type': 'tag', 'names': ['metal']}
]
candidates = filters.run(f)
assert list(candidates.order_by('pk')) == expected
def test_filter_on_artist(factories):
artist1 = factories['music.Artist']()
artist2 = factories['music.Artist']()
factories['music.Track'].create_batch(3, artist=artist1)
factories['music.Track'].create_batch(3, artist=artist2)
expected = list(artist1.tracks.order_by('pk'))
f = [
{'type': 'artist', 'ids': [artist1.pk]}
]
candidates = filters.run(f)
assert list(candidates.order_by('pk')) == expected
def test_can_combine_with_or(factories):
artist1 = factories['music.Artist']()
artist2 = factories['music.Artist']()
artist3 = factories['music.Artist']()
factories['music.Track'].create_batch(3, artist=artist1)
factories['music.Track'].create_batch(3, artist=artist2)
factories['music.Track'].create_batch(3, artist=artist3)
expected = Track.objects.exclude(artist=artist3).order_by('pk')
f = [
{'type': 'artist', 'ids': [artist1.pk]},
{'type': 'artist', 'ids': [artist2.pk], 'operator': 'or'},
]
candidates = filters.run(f)
assert list(candidates.order_by('pk')) == list(expected)
def test_can_combine_with_and(factories):
artist1 = factories['music.Artist']()
artist2 = factories['music.Artist']()
metal_tracks = factories['music.Track'].create_batch(
2, artist=artist1, tags=['metal'])
factories['music.Track'].create_batch(2, artist=artist1, tags=['pop'])
factories['music.Track'].create_batch(3, artist=artist2)
expected = metal_tracks
f = [
{'type': 'artist', 'ids': [artist1.pk]},
{'type': 'tag', 'names': ['metal'], 'operator': 'and'},
]
candidates = filters.run(f)
assert list(candidates.order_by('pk')) == list(expected)
def test_can_negate(factories):
artist1 = factories['music.Artist']()
artist2 = factories['music.Artist']()
factories['music.Track'].create_batch(3, artist=artist1)
factories['music.Track'].create_batch(3, artist=artist2)
expected = artist2.tracks.order_by('pk')
f = [
{'type': 'artist', 'ids': [artist1.pk], 'not': True},
]
candidates = filters.run(f)
assert list(candidates.order_by('pk')) == list(expected)
def test_can_group(factories):
artist1 = factories['music.Artist']()
artist2 = factories['music.Artist']()
factories['music.Track'].create_batch(2, artist=artist1)
t1 = factories['music.Track'].create_batch(
2, artist=artist1, tags=['metal'])
factories['music.Track'].create_batch(2, artist=artist2)
t2 = factories['music.Track'].create_batch(
2, artist=artist2, tags=['metal'])
factories['music.Track'].create_batch(2, tags=['metal'])
expected = t1 + t2
f = [
{'type': 'tag', 'names': ['metal']},
{'type': 'group', 'operator': 'and', 'filters': [
{'type': 'artist', 'ids': [artist1.pk], 'operator': 'or'},
{'type': 'artist', 'ids': [artist2.pk], 'operator': 'or'},
]}
]
candidates = filters.run(f)
assert list(candidates.order_by('pk')) == list(expected)
def test_artist_filter_clean_config(factories):
artist1 = factories['music.Artist']()
artist2 = factories['music.Artist']()
config = filters.clean_config(
{'type': 'artist', 'ids': [artist2.pk, artist1.pk]})
expected = {
'type': 'artist',
'ids': [artist1.pk, artist2.pk],
'names': [artist1.name, artist2.name]
}
assert filters.clean_config(config) == expected
def test_can_check_artist_filter(factories):
artist = factories['music.Artist']()
assert filters.validate({'type': 'artist', 'ids': [artist.pk]})
with pytest.raises(ValidationError):
filters.validate({'type': 'artist', 'ids': [artist.pk + 1]})
def test_can_check_operator():
assert filters.validate(
{'type': 'group', 'operator': 'or', 'filters': []})
assert filters.validate(
{'type': 'group', 'operator': 'and', 'filters': []})
with pytest.raises(ValidationError):
assert filters.validate(
{'type': 'group', 'operator': 'nope', 'filters': []})

Wyświetl plik

@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
from funkwhale_api.radios import radios
from funkwhale_api.radios import models
from funkwhale_api.radios import serializers
from funkwhale_api.favorites.models import TrackFavorite
@ -50,9 +51,9 @@ def test_can_pick_by_weight():
def test_can_get_choices_for_favorites_radio(factories):
tracks = factories['music.Track'].create_batch(100)
tracks = factories['music.Track'].create_batch(10)
user = factories['users.User']()
for i in range(20):
for i in range(5):
TrackFavorite.add(track=random.choice(tracks), user=user)
radio = radios.FavoritesRadio()
@ -63,11 +64,54 @@ def test_can_get_choices_for_favorites_radio(factories):
for favorite in user.track_favorites.all():
assert favorite.track in choices
for i in range(20):
for i in range(5):
pick = radio.pick(user=user)
assert pick in choices
def test_can_get_choices_for_custom_radio(factories):
artist = factories['music.Artist']()
tracks = factories['music.Track'].create_batch(5, artist=artist)
wrong_tracks = factories['music.Track'].create_batch(5)
session = factories['radios.CustomRadioSession'](
custom_radio__config=[{'type': 'artist', 'ids': [artist.pk]}]
)
choices = session.radio.get_choices()
expected = [t.pk for t in tracks]
assert list(choices.values_list('id', flat=True)) == expected
def test_cannot_start_custom_radio_if_not_owner_or_not_public(factories):
user = factories['users.User']()
artist = factories['music.Artist']()
radio = factories['radios.Radio'](
config=[{'type': 'artist', 'ids': [artist.pk]}]
)
serializer = serializers.RadioSessionSerializer(
data={
'radio_type': 'custom', 'custom_radio': radio.pk, 'user': user.pk}
)
message = "You don't have access to this radio"
assert not serializer.is_valid()
assert message in serializer.errors['non_field_errors']
def test_can_start_custom_radio_from_api(logged_in_client, factories):
artist = factories['music.Artist']()
radio = factories['radios.Radio'](
config=[{'type': 'artist', 'ids': [artist.pk]}],
user=logged_in_client.user
)
url = reverse('api:v1:radios:sessions-list')
response = logged_in_client.post(
url, {'radio_type': 'custom', 'custom_radio': radio.pk})
assert response.status_code == 201
session = radio.sessions.latest('id')
assert session.radio_type == 'custom'
assert session.user == logged_in_client.user
def test_can_use_radio_session_to_filter_choices(factories):
tracks = factories['music.Track'].create_batch(30)
user = factories['users.User']()