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

Merge branch 'black' into 'develop'

Blacked the code

See merge request funkwhale/funkwhale!241
parents 76c94f41 62ca3bd7
No related branches found
No related tags found
No related merge requests found
Showing
with 438 additions and 484 deletions
...@@ -12,70 +12,70 @@ from dynamic_preferences.api.viewsets import GlobalPreferencesViewSet ...@@ -12,70 +12,70 @@ from dynamic_preferences.api.viewsets import GlobalPreferencesViewSet
from dynamic_preferences.users.viewsets import UserPreferencesViewSet from dynamic_preferences.users.viewsets import UserPreferencesViewSet
router = routers.SimpleRouter() router = routers.SimpleRouter()
router.register(r'settings', GlobalPreferencesViewSet, base_name='settings') router.register(r"settings", GlobalPreferencesViewSet, base_name="settings")
router.register(r'activity', activity_views.ActivityViewSet, 'activity') router.register(r"activity", activity_views.ActivityViewSet, "activity")
router.register(r'tags', views.TagViewSet, 'tags') router.register(r"tags", views.TagViewSet, "tags")
router.register(r'tracks', views.TrackViewSet, 'tracks') router.register(r"tracks", views.TrackViewSet, "tracks")
router.register(r'trackfiles', views.TrackFileViewSet, 'trackfiles') router.register(r"trackfiles", views.TrackFileViewSet, "trackfiles")
router.register(r'artists', views.ArtistViewSet, 'artists') router.register(r"artists", views.ArtistViewSet, "artists")
router.register(r'albums', views.AlbumViewSet, 'albums') router.register(r"albums", views.AlbumViewSet, "albums")
router.register(r'import-batches', views.ImportBatchViewSet, 'import-batches') router.register(r"import-batches", views.ImportBatchViewSet, "import-batches")
router.register(r'import-jobs', views.ImportJobViewSet, 'import-jobs') router.register(r"import-jobs", views.ImportJobViewSet, "import-jobs")
router.register(r'submit', views.SubmitViewSet, 'submit') router.register(r"submit", views.SubmitViewSet, "submit")
router.register(r'playlists', playlists_views.PlaylistViewSet, 'playlists') router.register(r"playlists", playlists_views.PlaylistViewSet, "playlists")
router.register( router.register(
r'playlist-tracks', r"playlist-tracks", playlists_views.PlaylistTrackViewSet, "playlist-tracks"
playlists_views.PlaylistTrackViewSet, )
'playlist-tracks')
v1_patterns = router.urls v1_patterns = router.urls
subsonic_router = routers.SimpleRouter(trailing_slash=False) subsonic_router = routers.SimpleRouter(trailing_slash=False)
subsonic_router.register(r'subsonic/rest', SubsonicViewSet, base_name='subsonic') subsonic_router.register(r"subsonic/rest", SubsonicViewSet, base_name="subsonic")
v1_patterns += [ v1_patterns += [
url(r'^instance/', url(
r"^instance/",
include(("funkwhale_api.instance.urls", "instance"), namespace="instance"),
),
url(
r"^manage/",
include(("funkwhale_api.manage.urls", "manage"), namespace="manage"),
),
url(
r"^federation/",
include( include(
('funkwhale_api.instance.urls', 'instance'), ("funkwhale_api.federation.api_urls", "federation"), namespace="federation"
namespace='instance')), ),
url(r'^manage/', ),
include( url(
('funkwhale_api.manage.urls', 'manage'), r"^providers/",
namespace='manage')), include(("funkwhale_api.providers.urls", "providers"), namespace="providers"),
url(r'^federation/', ),
include( url(
('funkwhale_api.federation.api_urls', 'federation'), r"^favorites/",
namespace='federation')), include(("funkwhale_api.favorites.urls", "favorites"), namespace="favorites"),
url(r'^providers/', ),
include( url(r"^search$", views.Search.as_view(), name="search"),
('funkwhale_api.providers.urls', 'providers'), url(
namespace='providers')), r"^radios/",
url(r'^favorites/', include(("funkwhale_api.radios.urls", "radios"), namespace="radios"),
include( ),
('funkwhale_api.favorites.urls', 'favorites'), url(
namespace='favorites')), r"^history/",
url(r'^search$', include(("funkwhale_api.history.urls", "history"), namespace="history"),
views.Search.as_view(), name='search'), ),
url(r'^radios/', url(
include( r"^users/",
('funkwhale_api.radios.urls', 'radios'), include(("funkwhale_api.users.api_urls", "users"), namespace="users"),
namespace='radios')), ),
url(r'^history/', url(
include( r"^requests/",
('funkwhale_api.history.urls', 'history'), include(("funkwhale_api.requests.api_urls", "requests"), namespace="requests"),
namespace='history')), ),
url(r'^users/', url(r"^token/$", jwt_views.obtain_jwt_token, name="token"),
include( url(r"^token/refresh/$", jwt_views.refresh_jwt_token, name="token_refresh"),
('funkwhale_api.users.api_urls', 'users'),
namespace='users')),
url(r'^requests/',
include(
('funkwhale_api.requests.api_urls', 'requests'),
namespace='requests')),
url(r'^token/$', jwt_views.obtain_jwt_token, name='token'),
url(r'^token/refresh/$', jwt_views.refresh_jwt_token, name='token_refresh'),
] ]
urlpatterns = [ urlpatterns = [
url(r'^v1/', include((v1_patterns, 'v1'), namespace='v1')) url(r"^v1/", include((v1_patterns, "v1"), namespace="v1"))
] + format_suffix_patterns(subsonic_router.urls, allowed=['view']) ] + format_suffix_patterns(subsonic_router.urls, allowed=["view"])
...@@ -7,12 +7,13 @@ from funkwhale_api.common.auth import TokenAuthMiddleware ...@@ -7,12 +7,13 @@ from funkwhale_api.common.auth import TokenAuthMiddleware
from funkwhale_api.instance import consumers from funkwhale_api.instance import consumers
application = ProtocolTypeRouter({ application = ProtocolTypeRouter(
# Empty for now (http->django views is added by default) {
"websocket": TokenAuthMiddleware( # Empty for now (http->django views is added by default)
URLRouter([ "websocket": TokenAuthMiddleware(
url("^api/v1/instance/activity$", URLRouter(
consumers.InstanceActivityConsumer), [url("^api/v1/instance/activity$", consumers.InstanceActivityConsumer)]
]) )
), )
}) }
)
This diff is collapsed.
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
Local settings Local settings
- Run in Debug mode - Run in Debug mode
- Use console backend for emails - Use console backend for emails
- Add Django Debug Toolbar - Add Django Debug Toolbar
- Add django-extensions as app - Add django-extensions as app
''' """
from .common import * # noqa from .common import * # noqa
# DEBUG # DEBUG
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
DEBUG = env.bool('DJANGO_DEBUG', default=True) DEBUG = env.bool("DJANGO_DEBUG", default=True)
TEMPLATES[0]['OPTIONS']['debug'] = DEBUG TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG
# SECRET CONFIGURATION # SECRET CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
# Note: This key only used for development and testing. # Note: This key only used for development and testing.
SECRET_KEY = env("DJANGO_SECRET_KEY", default='mc$&b=5j#6^bv7tld1gyjp2&+^-qrdy=0sw@r5sua*1zp4fmxc') SECRET_KEY = env(
"DJANGO_SECRET_KEY", default="mc$&b=5j#6^bv7tld1gyjp2&+^-qrdy=0sw@r5sua*1zp4fmxc"
)
# Mail settings # Mail settings
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
EMAIL_HOST = 'localhost' EMAIL_HOST = "localhost"
EMAIL_PORT = 1025 EMAIL_PORT = 1025
# django-debug-toolbar # django-debug-toolbar
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',) MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",)
# INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',) # INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',)
DEBUG_TOOLBAR_CONFIG = { DEBUG_TOOLBAR_CONFIG = {
'DISABLE_PANELS': [ "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"],
'debug_toolbar.panels.redirects.RedirectsPanel', "SHOW_TEMPLATE_CONTEXT": True,
], "SHOW_TOOLBAR_CALLBACK": lambda request: True,
'SHOW_TEMPLATE_CONTEXT': True,
'SHOW_TOOLBAR_CALLBACK': lambda request: True,
} }
# django-extensions # django-extensions
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# INSTALLED_APPS += ('django_extensions', ) # INSTALLED_APPS += ('django_extensions', )
INSTALLED_APPS += ('debug_toolbar', ) INSTALLED_APPS += ("debug_toolbar",)
# TESTING # TESTING
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
TEST_RUNNER = 'django.test.runner.DiscoverRunner' TEST_RUNNER = "django.test.runner.DiscoverRunner"
########## CELERY ########## CELERY
# In development, all tasks will be executed locally by blocking until the task returns # In development, all tasks will be executed locally by blocking until the task returns
...@@ -57,23 +57,15 @@ CELERY_TASK_ALWAYS_EAGER = False ...@@ -57,23 +57,15 @@ CELERY_TASK_ALWAYS_EAGER = False
# Your local stuff: Below this line define 3rd party library settings # Your local stuff: Below this line define 3rd party library settings
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'handlers': { "handlers": {"console": {"level": "DEBUG", "class": "logging.StreamHandler"}},
'console':{ "loggers": {
'level':'DEBUG', "django.request": {
'class':'logging.StreamHandler', "handlers": ["console"],
}, "propagate": True,
}, "level": "DEBUG",
'loggers': {
'django.request': {
'handlers':['console'],
'propagate': True,
'level':'DEBUG',
},
'': {
'level': 'DEBUG',
'handlers': ['console'],
}, },
"": {"level": "DEBUG", "handlers": ["console"]},
}, },
} }
CSRF_TRUSTED_ORIGINS = [o for o in ALLOWED_HOSTS] CSRF_TRUSTED_ORIGINS = [o for o in ALLOWED_HOSTS]
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
Production Configurations Production Configurations
- Use djangosecure - Use djangosecure
...@@ -8,7 +8,7 @@ Production Configurations ...@@ -8,7 +8,7 @@ Production Configurations
- Use Redis on Heroku - Use Redis on Heroku
''' """
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.utils import six from django.utils import six
...@@ -58,19 +58,24 @@ CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS ...@@ -58,19 +58,24 @@ CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Uploaded Media Files # Uploaded Media Files
# ------------------------ # ------------------------
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
# Static Assets # Static Assets
# ------------------------ # ------------------------
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
# TEMPLATE CONFIGURATION # TEMPLATE CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# See: # See:
# https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader # https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader
TEMPLATES[0]['OPTIONS']['loaders'] = [ TEMPLATES[0]["OPTIONS"]["loaders"] = [
('django.template.loaders.cached.Loader', [ (
'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ]), "django.template.loaders.cached.Loader",
[
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
)
] ]
# CACHING # CACHING
...@@ -78,7 +83,6 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [ ...@@ -78,7 +83,6 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
# Heroku URL does not pass the DB number, so we parse it in # Heroku URL does not pass the DB number, so we parse it in
# LOGGING CONFIGURATION # LOGGING CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
...@@ -88,43 +92,39 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [ ...@@ -88,43 +92,39 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
# See http://docs.djangoproject.com/en/dev/topics/logging for # See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration. # more details on how to customize your logging configuration.
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'filters': { "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
'require_debug_false': { "formatters": {
'()': 'django.utils.log.RequireDebugFalse' "verbose": {
"format": "%(levelname)s %(asctime)s %(module)s "
"%(process)d %(thread)d %(message)s"
} }
}, },
'formatters': { "handlers": {
'verbose': { "mail_admins": {
'format': '%(levelname)s %(asctime)s %(module)s ' "level": "ERROR",
'%(process)d %(thread)d %(message)s' "filters": ["require_debug_false"],
"class": "django.utils.log.AdminEmailHandler",
},
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",
}, },
}, },
'handlers': { "loggers": {
'mail_admins': { "django.request": {
'level': 'ERROR', "handlers": ["mail_admins"],
'filters': ['require_debug_false'], "level": "ERROR",
'class': 'django.utils.log.AdminEmailHandler' "propagate": True,
}, },
'console': { "django.security.DisallowedHost": {
'level': 'DEBUG', "level": "ERROR",
'class': 'logging.StreamHandler', "handlers": ["console", "mail_admins"],
'formatter': 'verbose', "propagate": True,
}, },
}, },
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True
},
'django.security.DisallowedHost': {
'level': 'ERROR',
'handlers': ['console', 'mail_admins'],
'propagate': True
}
}
} }
......
...@@ -11,32 +11,30 @@ from django.views import defaults as default_views ...@@ -11,32 +11,30 @@ from django.views import defaults as default_views
urlpatterns = [ urlpatterns = [
# Django Admin, use {% url 'admin:index' %} # Django Admin, use {% url 'admin:index' %}
url(settings.ADMIN_URL, admin.site.urls), url(settings.ADMIN_URL, admin.site.urls),
url(r"^api/", include(("config.api_urls", "api"), namespace="api")),
url(r'^api/', include(("config.api_urls", 'api'), namespace="api")), url(
url(r'^', include( r"^",
('funkwhale_api.federation.urls', 'federation'), include(
namespace="federation")), ("funkwhale_api.federation.urls", "federation"), namespace="federation"
url(r'^api/v1/auth/', include('rest_auth.urls')), ),
url(r'^api/v1/auth/registration/', include('funkwhale_api.users.rest_auth_urls')), ),
url(r'^accounts/', include('allauth.urls')), url(r"^api/v1/auth/", include("rest_auth.urls")),
url(r"^api/v1/auth/registration/", include("funkwhale_api.users.rest_auth_urls")),
url(r"^accounts/", include("allauth.urls")),
# Your stuff: custom urls includes go here # Your stuff: custom urls includes go here
] ]
if settings.DEBUG: if settings.DEBUG:
# This allows the error pages to be debugged during development, just visit # This allows the error pages to be debugged during development, just visit
# these url in browser to see how these error pages look like. # these url in browser to see how these error pages look like.
urlpatterns += [ urlpatterns += [
url(r'^400/$', default_views.bad_request), url(r"^400/$", default_views.bad_request),
url(r'^403/$', default_views.permission_denied), url(r"^403/$", default_views.permission_denied),
url(r'^404/$', default_views.page_not_found), url(r"^404/$", default_views.page_not_found),
url(r'^500/$', default_views.server_error), url(r"^500/$", default_views.server_error),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if 'debug_toolbar' in settings.INSTALLED_APPS: if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar
urlpatterns += [
url(r'^__debug__/', include(debug_toolbar.urls)), urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))]
]
from funkwhale_api.users.models import User from funkwhale_api.users.models import User
u = User.objects.create(email='demo@demo.com', username='demo', is_staff=True) u = User.objects.create(email="demo@demo.com", username="demo", is_staff=True)
u.set_password('demo') u.set_password("demo")
u.subsonic_api_token = 'demo' u.subsonic_api_token = "demo"
u.save() u.save()
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = '0.14.1' __version__ = "0.14.1"
__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(".")
]
)
...@@ -2,8 +2,9 @@ from django.apps import AppConfig, apps ...@@ -2,8 +2,9 @@ from django.apps import AppConfig, apps
from . import record from . import record
class ActivityConfig(AppConfig): class ActivityConfig(AppConfig):
name = 'funkwhale_api.activity' name = "funkwhale_api.activity"
def ready(self): def ready(self):
super(ActivityConfig, self).ready() super(ActivityConfig, self).ready()
......
...@@ -2,37 +2,36 @@ import persisting_theory ...@@ -2,37 +2,36 @@ import persisting_theory
class ActivityRegistry(persisting_theory.Registry): class ActivityRegistry(persisting_theory.Registry):
look_into = 'activities' look_into = "activities"
def _register_for_model(self, model, attr, value): def _register_for_model(self, model, attr, value):
key = model._meta.label key = model._meta.label
d = self.setdefault(key, {'consumers': []}) d = self.setdefault(key, {"consumers": []})
d[attr] = value d[attr] = value
def register_serializer(self, serializer_class): def register_serializer(self, serializer_class):
model = serializer_class.Meta.model model = serializer_class.Meta.model
self._register_for_model(model, 'serializer', serializer_class) self._register_for_model(model, "serializer", serializer_class)
return serializer_class return serializer_class
def register_consumer(self, label): def register_consumer(self, label):
def decorator(func): def decorator(func):
consumers = self[label]['consumers'] consumers = self[label]["consumers"]
if func not in consumers: if func not in consumers:
consumers.append(func) consumers.append(func)
return func return func
return decorator return decorator
registry = ActivityRegistry() registry = ActivityRegistry()
def send(obj): def send(obj):
conf = registry[obj.__class__._meta.label] conf = registry[obj.__class__._meta.label]
consumers = conf['consumers'] consumers = conf["consumers"]
if not consumers: if not consumers:
return return
serializer = conf['serializer'](obj) serializer = conf["serializer"](obj)
for consumer in consumers: for consumer in consumers:
consumer(data=serializer.data, obj=obj) consumer(data=serializer.data, obj=obj)
...@@ -4,8 +4,8 @@ from funkwhale_api.activity import record ...@@ -4,8 +4,8 @@ from funkwhale_api.activity import record
class ModelSerializer(serializers.ModelSerializer): class ModelSerializer(serializers.ModelSerializer):
id = serializers.CharField(source='get_activity_url') id = serializers.CharField(source="get_activity_url")
local_id = serializers.IntegerField(source='id') local_id = serializers.IntegerField(source="id")
# url = serializers.SerializerMethodField() # url = serializers.SerializerMethodField()
def get_url(self, obj): def get_url(self, obj):
...@@ -17,8 +17,7 @@ class AutoSerializer(serializers.Serializer): ...@@ -17,8 +17,7 @@ class AutoSerializer(serializers.Serializer):
A serializer that will automatically use registered activity serializers A serializer that will automatically use registered activity serializers
to serialize an henerogeneous list of objects (favorites, listenings, etc.) to serialize an henerogeneous list of objects (favorites, listenings, etc.)
""" """
def to_representation(self, instance): def to_representation(self, instance):
serializer = record.registry[instance._meta.label]['serializer']( serializer = record.registry[instance._meta.label]["serializer"](instance)
instance
)
return serializer.data return serializer.data
...@@ -6,31 +6,25 @@ from funkwhale_api.history.models import Listening ...@@ -6,31 +6,25 @@ from funkwhale_api.history.models import Listening
def combined_recent(limit, **kwargs): def combined_recent(limit, **kwargs):
datetime_field = kwargs.pop('datetime_field', 'creation_date') datetime_field = kwargs.pop("datetime_field", "creation_date")
source_querysets = { source_querysets = {qs.model._meta.label: qs for qs in kwargs.pop("querysets")}
qs.model._meta.label: qs for qs in kwargs.pop('querysets')
}
querysets = { querysets = {
k: qs.annotate( k: qs.annotate(
__type=models.Value( __type=models.Value(qs.model._meta.label, output_field=models.CharField())
qs.model._meta.label, output_field=models.CharField() ).values("pk", datetime_field, "__type")
)
).values('pk', datetime_field, '__type')
for k, qs in source_querysets.items() for k, qs in source_querysets.items()
} }
_qs_list = list(querysets.values()) _qs_list = list(querysets.values())
union_qs = _qs_list[0].union(*_qs_list[1:]) union_qs = _qs_list[0].union(*_qs_list[1:])
records = [] records = []
for row in union_qs.order_by('-{}'.format(datetime_field))[:limit]: for row in union_qs.order_by("-{}".format(datetime_field))[:limit]:
records.append({ records.append(
'type': row['__type'], {"type": row["__type"], "when": row[datetime_field], "pk": row["pk"]}
'when': row[datetime_field], )
'pk': row['pk']
})
# Now we bulk-load each object type in turn # Now we bulk-load each object type in turn
to_load = {} to_load = {}
for record in records: for record in records:
to_load.setdefault(record['type'], []).append(record['pk']) to_load.setdefault(record["type"], []).append(record["pk"])
fetched = {} fetched = {}
for key, pks in to_load.items(): for key, pks in to_load.items():
...@@ -39,26 +33,19 @@ def combined_recent(limit, **kwargs): ...@@ -39,26 +33,19 @@ def combined_recent(limit, **kwargs):
# Annotate 'records' with loaded objects # Annotate 'records' with loaded objects
for record in records: for record in records:
record['object'] = fetched[(record['type'], record['pk'])] record["object"] = fetched[(record["type"], record["pk"])]
return records return records
def get_activity(user, limit=20): def get_activity(user, limit=20):
query = fields.privacy_level_query( query = fields.privacy_level_query(user, lookup_field="user__privacy_level")
user, lookup_field='user__privacy_level')
querysets = [ querysets = [
Listening.objects.filter(query).select_related( Listening.objects.filter(query).select_related(
'track', "track", "user", "track__artist", "track__album__artist"
'user',
'track__artist',
'track__album__artist',
), ),
TrackFavorite.objects.filter(query).select_related( TrackFavorite.objects.filter(query).select_related(
'track', "track", "user", "track__artist", "track__album__artist"
'user',
'track__artist',
'track__album__artist',
), ),
] ]
records = combined_recent(limit=limit, querysets=querysets) records = combined_recent(limit=limit, querysets=querysets)
return [r['object'] for r in records] return [r["object"] for r in records]
...@@ -17,4 +17,4 @@ class ActivityViewSet(viewsets.GenericViewSet): ...@@ -17,4 +17,4 @@ class ActivityViewSet(viewsets.GenericViewSet):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
activity = utils.get_activity(user=request.user) activity = utils.get_activity(user=request.user)
serializer = self.serializer_class(activity, many=True) serializer = self.serializer_class(activity, many=True)
return Response({'results': serializer.data}, status=200) return Response({"results": serializer.data}, status=200)
...@@ -16,20 +16,19 @@ class TokenHeaderAuth(BaseJSONWebTokenAuthentication): ...@@ -16,20 +16,19 @@ class TokenHeaderAuth(BaseJSONWebTokenAuthentication):
def get_jwt_value(self, request): def get_jwt_value(self, request):
try: try:
qs = request.get('query_string', b'').decode('utf-8') qs = request.get("query_string", b"").decode("utf-8")
parsed = parse_qs(qs) parsed = parse_qs(qs)
token = parsed['token'][0] token = parsed["token"][0]
except KeyError: except KeyError:
raise exceptions.AuthenticationFailed('No token') raise exceptions.AuthenticationFailed("No token")
if not token: if not token:
raise exceptions.AuthenticationFailed('Empty token') raise exceptions.AuthenticationFailed("Empty token")
return token return token
class TokenAuthMiddleware: class TokenAuthMiddleware:
def __init__(self, inner): def __init__(self, inner):
# Store the ASGI application we were passed # Store the ASGI application we were passed
self.inner = inner self.inner = inner
...@@ -41,5 +40,5 @@ class TokenAuthMiddleware: ...@@ -41,5 +40,5 @@ class TokenAuthMiddleware:
except (User.DoesNotExist, exceptions.AuthenticationFailed): except (User.DoesNotExist, exceptions.AuthenticationFailed):
user = AnonymousUser() user = AnonymousUser()
scope['user'] = user scope["user"] = user
return self.inner(scope) return self.inner(scope)
...@@ -6,34 +6,34 @@ from rest_framework_jwt import authentication ...@@ -6,34 +6,34 @@ from rest_framework_jwt import authentication
from rest_framework_jwt.settings import api_settings from rest_framework_jwt.settings import api_settings
class JSONWebTokenAuthenticationQS( class JSONWebTokenAuthenticationQS(authentication.BaseJSONWebTokenAuthentication):
authentication.BaseJSONWebTokenAuthentication):
www_authenticate_realm = 'api' www_authenticate_realm = "api"
def get_jwt_value(self, request): def get_jwt_value(self, request):
token = request.query_params.get('jwt') token = request.query_params.get("jwt")
if 'jwt' in request.query_params and not token: if "jwt" in request.query_params and not token:
msg = _('Invalid Authorization header. No credentials provided.') msg = _("Invalid Authorization header. No credentials provided.")
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
return token return token
def authenticate_header(self, request): def authenticate_header(self, request):
return '{0} realm="{1}"'.format( return '{0} realm="{1}"'.format(
api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm) api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm
)
class BearerTokenHeaderAuth( class BearerTokenHeaderAuth(authentication.BaseJSONWebTokenAuthentication):
authentication.BaseJSONWebTokenAuthentication):
""" """
For backward compatibility purpose, we used Authorization: JWT <token> For backward compatibility purpose, we used Authorization: JWT <token>
but Authorization: Bearer <token> is probably better. but Authorization: Bearer <token> is probably better.
""" """
www_authenticate_realm = 'api'
www_authenticate_realm = "api"
def get_jwt_value(self, request): def get_jwt_value(self, request):
auth = authentication.get_authorization_header(request).split() auth = authentication.get_authorization_header(request).split()
auth_header_prefix = 'bearer' auth_header_prefix = "bearer"
if not auth: if not auth:
if api_settings.JWT_AUTH_COOKIE: if api_settings.JWT_AUTH_COOKIE:
...@@ -44,14 +44,16 @@ class BearerTokenHeaderAuth( ...@@ -44,14 +44,16 @@ class BearerTokenHeaderAuth(
return None return None
if len(auth) == 1: if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.') msg = _("Invalid Authorization header. No credentials provided.")
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2: elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string ' msg = _(
'should not contain spaces.') "Invalid Authorization header. Credentials string "
"should not contain spaces."
)
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
return auth[1] return auth[1]
def authenticate_header(self, request): def authenticate_header(self, request):
return '{0} realm="{1}"'.format('Bearer', self.www_authenticate_realm) return '{0} realm="{1}"'.format("Bearer", self.www_authenticate_realm)
...@@ -5,7 +5,7 @@ from funkwhale_api.common import channels ...@@ -5,7 +5,7 @@ from funkwhale_api.common import channels
class JsonAuthConsumer(JsonWebsocketConsumer): class JsonAuthConsumer(JsonWebsocketConsumer):
def connect(self): def connect(self):
try: try:
assert self.scope['user'].pk is not None assert self.scope["user"].pk is not None
except (AssertionError, AttributeError, KeyError): except (AssertionError, AttributeError, KeyError):
return self.close() return self.close()
......
...@@ -3,18 +3,19 @@ from dynamic_preferences.registries import global_preferences_registry ...@@ -3,18 +3,19 @@ from dynamic_preferences.registries import global_preferences_registry
from funkwhale_api.common import preferences from funkwhale_api.common import preferences
common = types.Section('common') common = types.Section("common")
@global_preferences_registry.register @global_preferences_registry.register
class APIAutenticationRequired( class APIAutenticationRequired(
preferences.DefaultFromSettingMixin, types.BooleanPreference): preferences.DefaultFromSettingMixin, types.BooleanPreference
):
section = common section = common
name = 'api_authentication_required' name = "api_authentication_required"
verbose_name = 'API Requires authentication' verbose_name = "API Requires authentication"
setting = 'API_AUTHENTICATION_REQUIRED' setting = "API_AUTHENTICATION_REQUIRED"
help_text = ( help_text = (
'If disabled, anonymous users will be able to query the API' "If disabled, anonymous users will be able to query the API"
'and access music data (as well as other data exposed in the API ' "and access music data (as well as other data exposed in the API "
'without specific permissions).' "without specific permissions)."
) )
...@@ -6,34 +6,31 @@ from funkwhale_api.music import utils ...@@ -6,34 +6,31 @@ from funkwhale_api.music import utils
PRIVACY_LEVEL_CHOICES = [ PRIVACY_LEVEL_CHOICES = [
('me', 'Only me'), ("me", "Only me"),
('followers', 'Me and my followers'), ("followers", "Me and my followers"),
('instance', 'Everyone on my instance, and my followers'), ("instance", "Everyone on my instance, and my followers"),
('everyone', 'Everyone, including people on other instances'), ("everyone", "Everyone, including people on other instances"),
] ]
def get_privacy_field(): def get_privacy_field():
return models.CharField( return models.CharField(
max_length=30, choices=PRIVACY_LEVEL_CHOICES, default='instance') max_length=30, choices=PRIVACY_LEVEL_CHOICES, default="instance"
)
def privacy_level_query(user, lookup_field='privacy_level'): def privacy_level_query(user, lookup_field="privacy_level"):
if user.is_anonymous: if user.is_anonymous:
return models.Q(**{ return models.Q(**{lookup_field: "everyone"})
lookup_field: 'everyone',
})
return models.Q(**{ return models.Q(
'{}__in'.format(lookup_field): [ **{"{}__in".format(lookup_field): ["followers", "instance", "everyone"]}
'followers', 'instance', 'everyone' )
]
})
class SearchFilter(django_filters.CharFilter): class SearchFilter(django_filters.CharFilter):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.search_fields = kwargs.pop('search_fields') self.search_fields = kwargs.pop("search_fields")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def filter(self, qs, value): def filter(self, qs, value):
......
...@@ -4,17 +4,20 @@ from funkwhale_api.common import scripts ...@@ -4,17 +4,20 @@ from funkwhale_api.common import scripts
class Command(BaseCommand): class Command(BaseCommand):
help = 'Run a specific script from funkwhale_api/common/scripts/' help = "Run a specific script from funkwhale_api/common/scripts/"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('script_name', nargs='?', type=str) parser.add_argument("script_name", nargs="?", type=str)
parser.add_argument( parser.add_argument(
'--noinput', '--no-input', action='store_false', dest='interactive', "--noinput",
"--no-input",
action="store_false",
dest="interactive",
help="Do NOT prompt the user for input of any kind.", help="Do NOT prompt the user for input of any kind.",
) )
def handle(self, *args, **options): def handle(self, *args, **options):
name = options['script_name'] name = options["script_name"]
if not name: if not name:
self.show_help() self.show_help()
...@@ -23,44 +26,44 @@ class Command(BaseCommand): ...@@ -23,44 +26,44 @@ class Command(BaseCommand):
script = available_scripts[name] script = available_scripts[name]
except KeyError: except KeyError:
raise CommandError( raise CommandError(
'{} is not a valid script. Run python manage.py script for a ' "{} is not a valid script. Run python manage.py script for a "
'list of available scripts'.format(name)) "list of available scripts".format(name)
)
self.stdout.write('') self.stdout.write("")
if options['interactive']: if options["interactive"]:
message = ( message = (
'Are you sure you want to execute the script {}?\n\n' "Are you sure you want to execute the script {}?\n\n"
"Type 'yes' to continue, or 'no' to cancel: " "Type 'yes' to continue, or 'no' to cancel: "
).format(name) ).format(name)
if input(''.join(message)) != 'yes': if input("".join(message)) != "yes":
raise CommandError("Script cancelled.") raise CommandError("Script cancelled.")
script['entrypoint'](self, **options) script["entrypoint"](self, **options)
def show_help(self): def show_help(self):
indentation = 4 indentation = 4
self.stdout.write('') self.stdout.write("")
self.stdout.write('Available scripts:') self.stdout.write("Available scripts:")
self.stdout.write('Launch with: python manage.py <script_name>') self.stdout.write("Launch with: python manage.py <script_name>")
available_scripts = self.get_scripts() available_scripts = self.get_scripts()
for name, script in sorted(available_scripts.items()): for name, script in sorted(available_scripts.items()):
self.stdout.write('') self.stdout.write("")
self.stdout.write(self.style.SUCCESS(name)) self.stdout.write(self.style.SUCCESS(name))
self.stdout.write('') self.stdout.write("")
for line in script['help'].splitlines(): for line in script["help"].splitlines():
self.stdout.write(' {}'.format(line)) self.stdout.write(" {}".format(line))
self.stdout.write('') self.stdout.write("")
def get_scripts(self): def get_scripts(self):
available_scripts = [ available_scripts = [
k for k in sorted(scripts.__dict__.keys()) k for k in sorted(scripts.__dict__.keys()) if not k.startswith("__")
if not k.startswith('__')
] ]
data = {} data = {}
for name in available_scripts: for name in available_scripts:
module = getattr(scripts, name) module = getattr(scripts, name)
data[name] = { data[name] = {
'name': name, "name": name,
'help': module.__doc__.strip(), "help": module.__doc__.strip(),
'entrypoint': module.main "entrypoint": module.main,
} }
return data return data
...@@ -7,6 +7,4 @@ class Migration(migrations.Migration): ...@@ -7,6 +7,4 @@ class Migration(migrations.Migration):
dependencies = [] dependencies = []
operations = [ operations = [UnaccentExtension()]
UnaccentExtension()
]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment