Merge branch 'api-serializers-enhancements' into 'master'

Api serializers enhancements

See merge request funkwhale/funkwhale!941
This commit is contained in:
Eliot Berriot 2019-10-24 11:33:02 +02:00
commit d224f74f5d
4 changed files with 124 additions and 83 deletions

View File

@ -38,10 +38,26 @@ EMAIL_PORT = 1025
DEBUG_TOOLBAR_CONFIG = { DEBUG_TOOLBAR_CONFIG = {
"DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"],
"SHOW_TEMPLATE_CONTEXT": True, "SHOW_TEMPLATE_CONTEXT": True,
"SHOW_TOOLBAR_CALLBACK": lambda request: True, "SHOW_TOOLBAR_CALLBACK": lambda request: "debug" in request.GET,
"JQUERY_URL": "/staticfiles/admin/js/vendor/jquery/jquery.js", "JQUERY_URL": "/staticfiles/admin/js/vendor/jquery/jquery.js",
} }
DEBUG_TOOLBAR_PANELS = [
# 'debug_toolbar.panels.versions.VersionsPanel',
"debug_toolbar.panels.timer.TimerPanel",
"debug_toolbar.panels.settings.SettingsPanel",
"debug_toolbar.panels.headers.HeadersPanel",
# 'debug_toolbar.panels.request.RequestPanel',
"debug_toolbar.panels.sql.SQLPanel",
# 'debug_toolbar.panels.staticfiles.StaticFilesPanel',
# 'debug_toolbar.panels.templates.TemplatesPanel',
"debug_toolbar.panels.cache.CachePanel",
# 'debug_toolbar.panels.signals.SignalsPanel',
# 'debug_toolbar.panels.logging.LoggingPanel',
# 'debug_toolbar.panels.redirects.RedirectsPanel',
# 'debug_toolbar.panels.profiling.ProfilingPanel',
]
# django-extensions # django-extensions
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# INSTALLED_APPS += ('django_extensions', ) # INSTALLED_APPS += ('django_extensions', )
@ -69,4 +85,7 @@ if env.bool("WEAK_PASSWORDS", default=False):
# Faster during tests # Faster during tests
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
MIDDLEWARE = ("funkwhale_api.common.middleware.DevHttpsMiddleware",) + MIDDLEWARE MIDDLEWARE = (
"funkwhale_api.common.middleware.DevHttpsMiddleware",
"funkwhale_api.common.middleware.ProfilerMiddleware",
) + MIDDLEWARE

View File

@ -1,4 +1,5 @@
import html import html
import io
import requests import requests
import time import time
import xml.sax.saxutils import xml.sax.saxutils
@ -242,3 +243,53 @@ class ThrottleStatusMiddleware:
response["X-RateLimit-ResetSeconds"] = str(remaining) response["X-RateLimit-ResetSeconds"] = str(remaining)
return response return response
class ProfilerMiddleware:
"""
from https://github.com/omarish/django-cprofile-middleware/blob/master/django_cprofile_middleware/middleware.py
Simple profile middleware to profile django views. To run it, add ?prof to
the URL like this:
http://localhost:8000/view/?prof
Optionally pass the following to modify the output:
?sort => Sort the output by a given metric. Default is time.
See
http://docs.python.org/2/library/profile.html#pstats.Stats.sort_stats
for all sort options.
?count => The number of rows to display. Default is 100.
?download => Download profile file suitable for visualization. For example
in snakeviz or RunSnakeRun
This is adapted from an example found here:
http://www.slideshare.net/zeeg/django-con-high-performance-django-presentation.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if "prof" not in request.GET:
return self.get_response(request)
import profile
import pstats
profiler = profile.Profile()
response = profiler.runcall(self.get_response, request)
profiler.create_stats()
if "prof-download" in request.GET:
import marshal
output = marshal.dumps(profiler.stats)
response = http.HttpResponse(
output, content_type="application/octet-stream"
)
response["Content-Disposition"] = "attachment; filename=view.prof"
response["Content-Length"] = len(output)
stream = io.StringIO()
stats = pstats.Stats(profiler, stream=stream)
stats.sort_stats(request.GET.get("prof-sort", "cumtime"))
stats.print_stats(int(request.GET.get("count", 100)))
response = http.HttpResponse("<pre>%s</pre>" % stream.getvalue())
return response

View File

@ -436,7 +436,7 @@ class TrackQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet):
return self.exclude(pk__in=matches) return self.exclude(pk__in=matches)
def with_playable_uploads(self, actor): def with_playable_uploads(self, actor):
uploads = Upload.objects.playable_by(actor).select_related("track") uploads = Upload.objects.playable_by(actor)
return self.prefetch_related( return self.prefetch_related(
models.Prefetch("uploads", queryset=uploads, to_attr="playable_uploads") models.Prefetch("uploads", queryset=uploads, to_attr="playable_uploads")
) )
@ -594,7 +594,8 @@ class Track(APIModelMixin):
@property @property
def listen_url(self): def listen_url(self):
return reverse("api:v1:listen-detail", kwargs={"uuid": self.uuid}) # Not using reverse because this is slow
return "/api/v1/listen/{}/".format(self.uuid)
@property @property
def local_license(self): def local_license(self):

View File

@ -45,26 +45,21 @@ class LicenseSerializer(serializers.Serializer):
return obj["identifiers"][0] return obj["identifiers"][0]
class ArtistAlbumSerializer(serializers.ModelSerializer): class ArtistAlbumSerializer(serializers.Serializer):
tracks_count = serializers.SerializerMethodField() tracks_count = serializers.SerializerMethodField()
cover = cover_field cover = cover_field
is_playable = serializers.SerializerMethodField() is_playable = serializers.SerializerMethodField()
is_local = serializers.BooleanField()
id = serializers.IntegerField()
fid = serializers.URLField()
mbid = serializers.UUIDField()
title = serializers.CharField()
artist = serializers.SerializerMethodField()
release_date = serializers.DateField()
creation_date = serializers.DateTimeField()
class Meta: def get_artist(self, o):
model = models.Album return o.artist_id
fields = (
"id",
"fid",
"mbid",
"title",
"artist",
"release_date",
"cover",
"creation_date",
"tracks_count",
"is_playable",
"is_local",
)
def get_tracks_count(self, o): def get_tracks_count(self, o):
return o._tracks_count return o._tracks_count
@ -76,26 +71,20 @@ class ArtistAlbumSerializer(serializers.ModelSerializer):
return None return None
class ArtistWithAlbumsSerializer(serializers.ModelSerializer): DATETIME_FIELD = serializers.DateTimeField()
albums = ArtistAlbumSerializer(many=True, read_only=True)
class ArtistWithAlbumsSerializer(serializers.Serializer):
albums = ArtistAlbumSerializer(many=True)
tags = serializers.SerializerMethodField() tags = serializers.SerializerMethodField()
attributed_to = serializers.SerializerMethodField() attributed_to = serializers.SerializerMethodField()
tracks_count = serializers.SerializerMethodField() tracks_count = serializers.SerializerMethodField()
id = serializers.IntegerField()
class Meta: fid = serializers.URLField()
model = models.Artist mbid = serializers.UUIDField()
fields = ( name = serializers.CharField()
"id", creation_date = serializers.DateTimeField()
"fid", is_local = serializers.BooleanField()
"mbid",
"name",
"creation_date",
"albums",
"is_local",
"tags",
"attributed_to",
"tracks_count",
)
def get_tags(self, obj): def get_tags(self, obj):
tagged_items = getattr(obj, "_prefetched_tagged_items", []) tagged_items = getattr(obj, "_prefetched_tagged_items", [])
@ -114,9 +103,7 @@ def serialize_artist_simple(artist):
"fid": artist.fid, "fid": artist.fid,
"mbid": str(artist.mbid), "mbid": str(artist.mbid),
"name": artist.name, "name": artist.name,
"creation_date": serializers.DateTimeField().to_representation( "creation_date": DATETIME_FIELD.to_representation(artist.creation_date),
artist.creation_date
),
"is_local": artist.is_local, "is_local": artist.is_local,
} }
@ -129,9 +116,7 @@ def serialize_album_track(track):
"title": track.title, "title": track.title,
"artist": serialize_artist_simple(track.artist), "artist": serialize_artist_simple(track.artist),
"album": track.album_id, "album": track.album_id,
"creation_date": serializers.DateTimeField().to_representation( "creation_date": DATETIME_FIELD.to_representation(track.creation_date),
track.creation_date
),
"position": track.position, "position": track.position,
"disc_number": track.disc_number, "disc_number": track.disc_number,
"uploads": [ "uploads": [
@ -145,31 +130,22 @@ def serialize_album_track(track):
} }
class AlbumSerializer(serializers.ModelSerializer): class AlbumSerializer(serializers.Serializer):
tracks = serializers.SerializerMethodField() tracks = serializers.SerializerMethodField()
artist = serializers.SerializerMethodField() artist = serializers.SerializerMethodField()
cover = cover_field cover = cover_field
is_playable = serializers.SerializerMethodField() is_playable = serializers.SerializerMethodField()
tags = serializers.SerializerMethodField() tags = serializers.SerializerMethodField()
attributed_to = serializers.SerializerMethodField() attributed_to = serializers.SerializerMethodField()
id = serializers.IntegerField()
class Meta: fid = serializers.URLField()
model = models.Album mbid = serializers.UUIDField()
fields = ( title = serializers.CharField()
"id", artist = serializers.SerializerMethodField()
"fid", release_date = serializers.DateField()
"mbid", creation_date = serializers.DateTimeField()
"title", is_local = serializers.BooleanField()
"artist", is_playable = serializers.SerializerMethodField()
"tracks",
"release_date",
"cover",
"creation_date",
"is_playable",
"is_local",
"tags",
"attributed_to",
)
get_attributed_to = serialize_attributed_to get_attributed_to = serialize_attributed_to
@ -227,7 +203,7 @@ def serialize_upload(upload):
} }
class TrackSerializer(serializers.ModelSerializer): class TrackSerializer(serializers.Serializer):
artist = serializers.SerializerMethodField() artist = serializers.SerializerMethodField()
album = TrackAlbumSerializer(read_only=True) album = TrackAlbumSerializer(read_only=True)
uploads = serializers.SerializerMethodField() uploads = serializers.SerializerMethodField()
@ -235,26 +211,17 @@ class TrackSerializer(serializers.ModelSerializer):
tags = serializers.SerializerMethodField() tags = serializers.SerializerMethodField()
attributed_to = serializers.SerializerMethodField() attributed_to = serializers.SerializerMethodField()
class Meta: id = serializers.IntegerField()
model = models.Track fid = serializers.URLField()
fields = ( mbid = serializers.UUIDField()
"id", title = serializers.CharField()
"fid", artist = serializers.SerializerMethodField()
"mbid", creation_date = serializers.DateTimeField()
"title", is_local = serializers.BooleanField()
"album", position = serializers.IntegerField()
"artist", disc_number = serializers.IntegerField()
"creation_date", copyright = serializers.CharField()
"position", license = serializers.SerializerMethodField()
"disc_number",
"uploads",
"listen_url",
"copyright",
"license",
"is_local",
"tags",
"attributed_to",
)
get_attributed_to = serialize_attributed_to get_attributed_to = serialize_attributed_to
@ -271,6 +238,9 @@ class TrackSerializer(serializers.ModelSerializer):
tagged_items = getattr(obj, "_prefetched_tagged_items", []) tagged_items = getattr(obj, "_prefetched_tagged_items", [])
return [ti.tag.name for ti in tagged_items] return [ti.tag.name for ti in tagged_items]
def get_license(self, o):
return o.license_id
@common_serializers.track_fields_for_update("name", "description", "privacy_level") @common_serializers.track_fields_for_update("name", "description", "privacy_level")
class LibraryForOwnerSerializer(serializers.ModelSerializer): class LibraryForOwnerSerializer(serializers.ModelSerializer):