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

See #272: updated API to return upload data on tracks

parent 8489c79c
No related branches found
No related tags found
No related merge requests found
Showing
with 97 additions and 142 deletions
...@@ -51,7 +51,7 @@ class TrackFavoriteViewSet( ...@@ -51,7 +51,7 @@ class TrackFavoriteViewSet(
queryset = queryset.filter( queryset = queryset.filter(
fields.privacy_level_query(self.request.user, "user__privacy_level") fields.privacy_level_query(self.request.user, "user__privacy_level")
) )
tracks = Track.objects.annotate_playable_by_actor( tracks = Track.objects.with_playable_uploads(
music_utils.get_actor_from_request(self.request) music_utils.get_actor_from_request(self.request)
).select_related("artist", "album__artist") ).select_related("artist", "album__artist")
queryset = queryset.prefetch_related(Prefetch("track", queryset=tracks)) queryset = queryset.prefetch_related(Prefetch("track", queryset=tracks))
......
...@@ -41,7 +41,7 @@ class ListeningViewSet( ...@@ -41,7 +41,7 @@ class ListeningViewSet(
queryset = queryset.filter( queryset = queryset.filter(
fields.privacy_level_query(self.request.user, "user__privacy_level") fields.privacy_level_query(self.request.user, "user__privacy_level")
) )
tracks = Track.objects.annotate_playable_by_actor( tracks = Track.objects.with_playable_uploads(
music_utils.get_actor_from_request(self.request) music_utils.get_actor_from_request(self.request)
).select_related("artist", "album__artist") ).select_related("artist", "album__artist")
return queryset.prefetch_related(Prefetch("track", queryset=tracks)) return queryset.prefetch_related(Prefetch("track", queryset=tracks))
......
...@@ -124,8 +124,8 @@ class ArtistQuerySet(models.QuerySet): ...@@ -124,8 +124,8 @@ class ArtistQuerySet(models.QuerySet):
def annotate_playable_by_actor(self, actor): def annotate_playable_by_actor(self, actor):
tracks = ( tracks = (
Track.objects.playable_by(actor) Upload.objects.playable_by(actor)
.filter(artist=models.OuterRef("id")) .filter(track__artist=models.OuterRef("id"))
.order_by("id") .order_by("id")
.values("id")[:1] .values("id")[:1]
) )
...@@ -192,8 +192,8 @@ class AlbumQuerySet(models.QuerySet): ...@@ -192,8 +192,8 @@ class AlbumQuerySet(models.QuerySet):
def annotate_playable_by_actor(self, actor): def annotate_playable_by_actor(self, actor):
tracks = ( tracks = (
Track.objects.playable_by(actor) Upload.objects.playable_by(actor)
.filter(album=models.OuterRef("id")) .filter(track__album=models.OuterRef("id"))
.order_by("id") .order_by("id")
.values("id")[:1] .values("id")[:1]
) )
...@@ -207,6 +207,15 @@ class AlbumQuerySet(models.QuerySet): ...@@ -207,6 +207,15 @@ class AlbumQuerySet(models.QuerySet):
else: else:
return self.exclude(tracks__in=tracks).distinct() return self.exclude(tracks__in=tracks).distinct()
def with_prefetched_tracks_and_playable_uploads(self, actor):
tracks = Track.objects.with_playable_uploads(actor)
return self.prefetch_related(
models.Prefetch(
'tracks',
queryset=tracks,
)
)
class Album(APIModelMixin): class Album(APIModelMixin):
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
...@@ -403,18 +412,14 @@ class TrackQuerySet(models.QuerySet): ...@@ -403,18 +412,14 @@ class TrackQuerySet(models.QuerySet):
else: else:
return self.exclude(uploads__in=files).distinct() return self.exclude(uploads__in=files).distinct()
def annotate_duration(self): def with_playable_uploads(self, actor):
first_upload = Upload.objects.filter(track=models.OuterRef("pk")).order_by("pk") uploads = Upload.objects.playable_by(actor).select_related('track')
return self.annotate( return self.prefetch_related(
duration=models.Subquery(first_upload.values("duration")[:1]) models.Prefetch(
'uploads',
queryset=uploads,
to_attr='playable_uploads'
) )
def annotate_file_data(self):
first_upload = Upload.objects.filter(track=models.OuterRef("pk")).order_by("pk")
return self.annotate(
bitrate=models.Subquery(first_upload.values("bitrate")[:1]),
size=models.Subquery(first_upload.values("size")[:1]),
mimetype=models.Subquery(first_upload.values("mimetype")[:1]),
) )
......
...@@ -59,7 +59,7 @@ class ArtistSimpleSerializer(serializers.ModelSerializer): ...@@ -59,7 +59,7 @@ class ArtistSimpleSerializer(serializers.ModelSerializer):
class AlbumTrackSerializer(serializers.ModelSerializer): class AlbumTrackSerializer(serializers.ModelSerializer):
artist = ArtistSimpleSerializer(read_only=True) artist = ArtistSimpleSerializer(read_only=True)
is_playable = serializers.SerializerMethodField() uploads = serializers.SerializerMethodField()
listen_url = serializers.SerializerMethodField() listen_url = serializers.SerializerMethodField()
duration = serializers.SerializerMethodField() duration = serializers.SerializerMethodField()
...@@ -73,16 +73,14 @@ class AlbumTrackSerializer(serializers.ModelSerializer): ...@@ -73,16 +73,14 @@ class AlbumTrackSerializer(serializers.ModelSerializer):
"artist", "artist",
"creation_date", "creation_date",
"position", "position",
"is_playable", "uploads",
"listen_url", "listen_url",
"duration", "duration",
) )
def get_is_playable(self, obj): def get_uploads(self, obj):
try: uploads = getattr(obj, "playable_uploads", [])
return bool(obj.is_playable_by_actor) return TrackUploadSerializer(uploads, many=True).data
except AttributeError:
return None
def get_listen_url(self, obj): def get_listen_url(self, obj):
return obj.listen_url return obj.listen_url
...@@ -123,7 +121,9 @@ class AlbumSerializer(serializers.ModelSerializer): ...@@ -123,7 +121,9 @@ class AlbumSerializer(serializers.ModelSerializer):
def get_is_playable(self, obj): def get_is_playable(self, obj):
try: try:
return any([bool(t.is_playable_by_actor) for t in obj.tracks.all()]) return any(
[bool(getattr(t, "playable_uploads", [])) for t in obj.tracks.all()]
)
except AttributeError: except AttributeError:
return None return None
...@@ -145,16 +145,26 @@ class TrackAlbumSerializer(serializers.ModelSerializer): ...@@ -145,16 +145,26 @@ class TrackAlbumSerializer(serializers.ModelSerializer):
) )
class TrackUploadSerializer(serializers.ModelSerializer):
class Meta:
model = models.Upload
fields = (
"uuid",
"listen_url",
"size",
"duration",
"bitrate",
"mimetype",
"extension",
)
class TrackSerializer(serializers.ModelSerializer): class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSimpleSerializer(read_only=True) artist = ArtistSimpleSerializer(read_only=True)
album = TrackAlbumSerializer(read_only=True) album = TrackAlbumSerializer(read_only=True)
lyrics = serializers.SerializerMethodField() lyrics = serializers.SerializerMethodField()
is_playable = serializers.SerializerMethodField() uploads = serializers.SerializerMethodField()
listen_url = serializers.SerializerMethodField() listen_url = serializers.SerializerMethodField()
duration = serializers.SerializerMethodField()
bitrate = serializers.SerializerMethodField()
size = serializers.SerializerMethodField()
mimetype = serializers.SerializerMethodField()
class Meta: class Meta:
model = models.Track model = models.Track
...@@ -167,12 +177,8 @@ class TrackSerializer(serializers.ModelSerializer): ...@@ -167,12 +177,8 @@ class TrackSerializer(serializers.ModelSerializer):
"creation_date", "creation_date",
"position", "position",
"lyrics", "lyrics",
"is_playable", "uploads",
"listen_url", "listen_url",
"duration",
"bitrate",
"size",
"mimetype",
) )
def get_lyrics(self, obj): def get_lyrics(self, obj):
...@@ -181,35 +187,9 @@ class TrackSerializer(serializers.ModelSerializer): ...@@ -181,35 +187,9 @@ class TrackSerializer(serializers.ModelSerializer):
def get_listen_url(self, obj): def get_listen_url(self, obj):
return obj.listen_url return obj.listen_url
def get_is_playable(self, obj): def get_uploads(self, obj):
try: uploads = getattr(obj, "playable_uploads", [])
return bool(obj.is_playable_by_actor) return TrackUploadSerializer(uploads, many=True).data
except AttributeError:
return None
def get_duration(self, obj):
try:
return obj.duration
except AttributeError:
return None
def get_bitrate(self, obj):
try:
return obj.bitrate
except AttributeError:
return None
def get_size(self, obj):
try:
return obj.size
except AttributeError:
return None
def get_mimetype(self, obj):
try:
return obj.mimetype
except AttributeError:
return None
class LibraryForOwnerSerializer(serializers.ModelSerializer): class LibraryForOwnerSerializer(serializers.ModelSerializer):
......
...@@ -5,7 +5,6 @@ import mutagen ...@@ -5,7 +5,6 @@ import mutagen
import pydub import pydub
from funkwhale_api.common.search import normalize_query, get_query # noqa from funkwhale_api.common.search import normalize_query, get_query # noqa
from funkwhale_api.common import utils
def guess_mimetype(f): def guess_mimetype(f):
......
...@@ -93,17 +93,9 @@ class AlbumViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -93,17 +93,9 @@ class AlbumViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
tracks = models.Track.objects.annotate_playable_by_actor( tracks = models.Track.objects.select_related("artist").with_playable_uploads(
utils.get_actor_from_request(self.request) utils.get_actor_from_request(self.request)
).select_related("artist") )
if (
hasattr(self, "kwargs")
and self.kwargs
and self.request.method.lower() == "get"
):
# we are detailing a single album, so we can add the overhead
# to fetch additional data
tracks = tracks.annotate_duration()
qs = queryset.prefetch_related(Prefetch("tracks", queryset=tracks)) qs = queryset.prefetch_related(Prefetch("tracks", queryset=tracks))
return qs.distinct() return qs.distinct()
...@@ -194,18 +186,10 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet): ...@@ -194,18 +186,10 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet):
if user.is_authenticated and filter_favorites == "true": if user.is_authenticated and filter_favorites == "true":
queryset = queryset.filter(track_favorites__user=user) queryset = queryset.filter(track_favorites__user=user)
queryset = queryset.annotate_playable_by_actor( queryset = queryset.with_playable_uploads(
utils.get_actor_from_request(self.request) utils.get_actor_from_request(self.request)
).annotate_duration() )
if ( return queryset
hasattr(self, "kwargs")
and self.kwargs
and self.request.method.lower() == "get"
):
# we are detailing a single track, so we can add the overhead
# to fetch additional data
queryset = queryset.annotate_file_data()
return queryset.distinct()
@detail_route(methods=["get"]) @detail_route(methods=["get"])
@transaction.non_atomic_requests @transaction.non_atomic_requests
......
...@@ -38,15 +38,14 @@ class PlaylistQuerySet(models.QuerySet): ...@@ -38,15 +38,14 @@ class PlaylistQuerySet(models.QuerySet):
) )
return self.prefetch_related(plt_prefetch) return self.prefetch_related(plt_prefetch)
def annotate_playable_by_actor(self, actor): def with_playable_plts(self, actor):
plts = ( return self.prefetch_related(
PlaylistTrack.objects.playable_by(actor) models.Prefetch(
.filter(playlist=models.OuterRef("id")) "playlist_tracks",
.order_by("id") queryset=PlaylistTrack.objects.playable_by(actor),
.values("id")[:1] to_attr="playable_plts",
)
) )
subquery = models.Subquery(plts)
return self.annotate(is_playable_by_actor=subquery)
def playable_by(self, actor, include=True): def playable_by(self, actor, include=True):
plts = PlaylistTrack.objects.playable_by(actor, include) plts = PlaylistTrack.objects.playable_by(actor, include)
...@@ -148,7 +147,7 @@ class Playlist(models.Model): ...@@ -148,7 +147,7 @@ class Playlist(models.Model):
class PlaylistTrackQuerySet(models.QuerySet): class PlaylistTrackQuerySet(models.QuerySet):
def for_nested_serialization(self, actor=None): def for_nested_serialization(self, actor=None):
tracks = music_models.Track.objects.annotate_playable_by_actor(actor) tracks = music_models.Track.objects.with_playable_uploads(actor)
tracks = tracks.select_related("artist", "album__artist") tracks = tracks.select_related("artist", "album__artist")
return self.prefetch_related( return self.prefetch_related(
models.Prefetch("track", queryset=tracks, to_attr="_prefetched_track") models.Prefetch("track", queryset=tracks, to_attr="_prefetched_track")
...@@ -156,8 +155,8 @@ class PlaylistTrackQuerySet(models.QuerySet): ...@@ -156,8 +155,8 @@ class PlaylistTrackQuerySet(models.QuerySet):
def annotate_playable_by_actor(self, actor): def annotate_playable_by_actor(self, actor):
tracks = ( tracks = (
music_models.Track.objects.playable_by(actor) music_models.Upload.objects.playable_by(actor)
.filter(pk=models.OuterRef("track")) .filter(track__pk=models.OuterRef("track"))
.order_by("id") .order_by("id")
.values("id")[:1] .values("id")[:1]
) )
......
...@@ -93,7 +93,7 @@ class PlaylistSerializer(serializers.ModelSerializer): ...@@ -93,7 +93,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
def get_is_playable(self, obj): def get_is_playable(self, obj):
try: try:
return bool(obj.is_playable_by_actor) return bool(obj.playable_plts)
except AttributeError: except AttributeError:
return None return None
......
...@@ -78,7 +78,7 @@ class PlaylistViewSet( ...@@ -78,7 +78,7 @@ class PlaylistViewSet(
def get_queryset(self): def get_queryset(self):
return self.queryset.filter( return self.queryset.filter(
fields.privacy_level_query(self.request.user) fields.privacy_level_query(self.request.user)
).annotate_playable_by_actor(music_utils.get_actor_from_request(self.request)) ).with_playable_plts(music_utils.get_actor_from_request(self.request))
def perform_create(self, serializer): def perform_create(self, serializer):
return serializer.save( return serializer.save(
......
...@@ -35,7 +35,6 @@ def test_user_can_get_his_favorites(api_request, factories, logged_in_client, cl ...@@ -35,7 +35,6 @@ def test_user_can_get_his_favorites(api_request, factories, logged_in_client, cl
"creation_date": favorite.creation_date.isoformat().replace("+00:00", "Z"), "creation_date": favorite.creation_date.isoformat().replace("+00:00", "Z"),
} }
] ]
expected[0]["track"]["is_playable"] = False
assert response.status_code == 200 assert response.status_code == 200
assert response.data["results"] == expected assert response.data["results"] == expected
......
...@@ -464,24 +464,6 @@ def test_library_queryset_with_follows(factories): ...@@ -464,24 +464,6 @@ def test_library_queryset_with_follows(factories):
assert l2._follows == [follow] assert l2._follows == [follow]
def test_annotate_duration(factories):
tf = factories["music.Upload"](duration=32)
track = models.Track.objects.annotate_duration().get(pk=tf.track.pk)
assert track.duration == 32
def test_annotate_file_data(factories):
tf = factories["music.Upload"](size=42, bitrate=55, mimetype="audio/ogg")
track = models.Track.objects.annotate_file_data().get(pk=tf.track.pk)
assert track.size == 42
assert track.bitrate == 55
assert track.mimetype == "audio/ogg"
@pytest.mark.parametrize( @pytest.mark.parametrize(
"model,factory_args,namespace", "model,factory_args,namespace",
[ [
......
...@@ -48,6 +48,7 @@ def test_artist_with_albums_serializer(factories, to_api_date): ...@@ -48,6 +48,7 @@ def test_artist_with_albums_serializer(factories, to_api_date):
def test_album_track_serializer(factories, to_api_date): def test_album_track_serializer(factories, to_api_date):
upload = factories["music.Upload"]() upload = factories["music.Upload"]()
track = upload.track track = upload.track
setattr(track, "playable_uploads", [upload])
expected = { expected = {
"id": track.id, "id": track.id,
...@@ -56,7 +57,7 @@ def test_album_track_serializer(factories, to_api_date): ...@@ -56,7 +57,7 @@ def test_album_track_serializer(factories, to_api_date):
"mbid": str(track.mbid), "mbid": str(track.mbid),
"title": track.title, "title": track.title,
"position": track.position, "position": track.position,
"is_playable": None, "uploads": [serializers.TrackUploadSerializer(upload).data],
"creation_date": to_api_date(track.creation_date), "creation_date": to_api_date(track.creation_date),
"listen_url": track.listen_url, "listen_url": track.listen_url,
"duration": None, "duration": None,
...@@ -127,7 +128,7 @@ def test_album_serializer(factories, to_api_date): ...@@ -127,7 +128,7 @@ def test_album_serializer(factories, to_api_date):
"title": album.title, "title": album.title,
"artist": serializers.ArtistSimpleSerializer(album.artist).data, "artist": serializers.ArtistSimpleSerializer(album.artist).data,
"creation_date": to_api_date(album.creation_date), "creation_date": to_api_date(album.creation_date),
"is_playable": None, "is_playable": False,
"cover": { "cover": {
"original": album.cover.url, "original": album.cover.url,
"square_crop": album.cover.crop["400x400"].url, "square_crop": album.cover.crop["400x400"].url,
...@@ -145,7 +146,7 @@ def test_album_serializer(factories, to_api_date): ...@@ -145,7 +146,7 @@ def test_album_serializer(factories, to_api_date):
def test_track_serializer(factories, to_api_date): def test_track_serializer(factories, to_api_date):
upload = factories["music.Upload"]() upload = factories["music.Upload"]()
track = upload.track track = upload.track
setattr(track, "playable_uploads", [upload])
expected = { expected = {
"id": track.id, "id": track.id,
"artist": serializers.ArtistSimpleSerializer(track.artist).data, "artist": serializers.ArtistSimpleSerializer(track.artist).data,
...@@ -153,14 +154,10 @@ def test_track_serializer(factories, to_api_date): ...@@ -153,14 +154,10 @@ def test_track_serializer(factories, to_api_date):
"mbid": str(track.mbid), "mbid": str(track.mbid),
"title": track.title, "title": track.title,
"position": track.position, "position": track.position,
"is_playable": None, "uploads": [serializers.TrackUploadSerializer(upload).data],
"creation_date": to_api_date(track.creation_date), "creation_date": to_api_date(track.creation_date),
"lyrics": track.get_lyrics_url(), "lyrics": track.get_lyrics_url(),
"listen_url": track.listen_url, "listen_url": track.listen_url,
"duration": None,
"size": None,
"bitrate": None,
"mimetype": None,
} }
serializer = serializers.TrackSerializer(track) serializer = serializers.TrackSerializer(track)
assert serializer.data == expected assert serializer.data == expected
...@@ -260,3 +257,20 @@ def test_manage_upload_action_relaunch_import(factories, mocker): ...@@ -260,3 +257,20 @@ def test_manage_upload_action_relaunch_import(factories, mocker):
finished.refresh_from_db() finished.refresh_from_db()
assert finished.import_status == "finished" assert finished.import_status == "finished"
assert m.call_count == 3 assert m.call_count == 3
def test_track_upload_serializer(factories):
upload = factories["music.Upload"]()
expected = {
"listen_url": upload.listen_url,
"uuid": str(upload.uuid),
"size": upload.size,
"bitrate": upload.bitrate,
"mimetype": upload.mimetype,
"extension": upload.extension,
"duration": upload.duration,
}
serializer = serializers.TrackUploadSerializer(upload)
assert serializer.data == expected
...@@ -549,11 +549,11 @@ def test_scan_page_trigger_next_page_scan_skip_if_same(mocker, factories, r_mock ...@@ -549,11 +549,11 @@ def test_scan_page_trigger_next_page_scan_skip_if_same(mocker, factories, r_mock
def test_clean_transcoding_cache(preferences, now, factories): def test_clean_transcoding_cache(preferences, now, factories):
preferences['music__transcoding_cache_duration'] = 60 preferences["music__transcoding_cache_duration"] = 60
u1 = factories['music.UploadVersion']( u1 = factories["music.UploadVersion"](
accessed_date=now - datetime.timedelta(minutes=61) accessed_date=now - datetime.timedelta(minutes=61)
) )
u2 = factories['music.UploadVersion']( u2 = factories["music.UploadVersion"](
accessed_date=now - datetime.timedelta(minutes=59) accessed_date=now - datetime.timedelta(minutes=59)
) )
......
...@@ -39,13 +39,11 @@ def test_album_list_serializer(api_request, factories, logged_in_api_client): ...@@ -39,13 +39,11 @@ def test_album_list_serializer(api_request, factories, logged_in_api_client):
).track ).track
album = track.album album = track.album
request = api_request.get("/") request = api_request.get("/")
qs = album.__class__.objects.all() qs = album.__class__.objects.with_prefetched_tracks_and_playable_uploads(None)
serializer = serializers.AlbumSerializer( serializer = serializers.AlbumSerializer(
qs, many=True, context={"request": request} qs, many=True, context={"request": request}
) )
expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
expected["results"][0]["is_playable"] = True
expected["results"][0]["tracks"][0]["is_playable"] = True
url = reverse("api:v1:albums-list") url = reverse("api:v1:albums-list")
response = logged_in_api_client.get(url) response = logged_in_api_client.get(url)
...@@ -58,12 +56,11 @@ def test_track_list_serializer(api_request, factories, logged_in_api_client): ...@@ -58,12 +56,11 @@ def test_track_list_serializer(api_request, factories, logged_in_api_client):
library__privacy_level="everyone", import_status="finished" library__privacy_level="everyone", import_status="finished"
).track ).track
request = api_request.get("/") request = api_request.get("/")
qs = track.__class__.objects.all() qs = track.__class__.objects.with_playable_uploads(None)
serializer = serializers.TrackSerializer( serializer = serializers.TrackSerializer(
qs, many=True, context={"request": request} qs, many=True, context={"request": request}
) )
expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
expected["results"][0]["is_playable"] = True
url = reverse("api:v1:tracks-list") url = reverse("api:v1:tracks-list")
response = logged_in_api_client.get(url) response = logged_in_api_client.get(url)
......
...@@ -150,10 +150,6 @@ def test_playlist_playable_by_anonymous(privacy_level, expected, factories): ...@@ -150,10 +150,6 @@ def test_playlist_playable_by_anonymous(privacy_level, expected, factories):
factories["music.Upload"]( factories["music.Upload"](
track=track, library__privacy_level=privacy_level, import_status="finished" track=track, library__privacy_level=privacy_level, import_status="finished"
) )
queryset = playlist.__class__.objects.playable_by(None).annotate_playable_by_actor( queryset = playlist.__class__.objects.playable_by(None).with_playable_plts(None)
None
)
match = playlist in list(queryset) match = playlist in list(queryset)
assert match is expected assert match is expected
if expected:
assert bool(queryset.first().is_playable_by_actor) is expected
...@@ -145,7 +145,7 @@ def test_can_list_tracks_from_playlist(level, factories, logged_in_api_client): ...@@ -145,7 +145,7 @@ def test_can_list_tracks_from_playlist(level, factories, logged_in_api_client):
url = reverse("api:v1:playlists-tracks", kwargs={"pk": plt.playlist.pk}) url = reverse("api:v1:playlists-tracks", kwargs={"pk": plt.playlist.pk})
response = logged_in_api_client.get(url) response = logged_in_api_client.get(url)
serialized_plt = serializers.PlaylistTrackSerializer(plt).data serialized_plt = serializers.PlaylistTrackSerializer(plt).data
serialized_plt["track"]["is_playable"] = False
assert response.data["count"] == 1 assert response.data["count"] == 1
assert response.data["results"][0] == serialized_plt assert response.data["results"][0] == serialized_plt
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment