Resolve "Per-user libraries" (use !368 instead)
This commit is contained in:
parent
b0ca181016
commit
2ea21994ee
144 changed files with 6709 additions and 5307 deletions
|
|
@ -56,3 +56,20 @@ class BearerTokenHeaderAuth(authentication.BaseJSONWebTokenAuthentication):
|
|||
|
||||
def authenticate_header(self, request):
|
||||
return '{0} realm="{1}"'.format("Bearer", self.www_authenticate_realm)
|
||||
|
||||
def authenticate(self, request):
|
||||
auth = super().authenticate(request)
|
||||
if auth:
|
||||
if not auth[0].actor:
|
||||
auth[0].create_actor()
|
||||
return auth
|
||||
|
||||
|
||||
class JSONWebTokenAuthentication(authentication.JSONWebTokenAuthentication):
|
||||
def authenticate(self, request):
|
||||
auth = super().authenticate(request)
|
||||
|
||||
if auth:
|
||||
if not auth[0].actor:
|
||||
auth[0].create_actor()
|
||||
return auth
|
||||
|
|
|
|||
|
|
@ -1,6 +1,25 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
logger = logging.getLogger(__file__)
|
||||
channel_layer = get_channel_layer()
|
||||
group_send = async_to_sync(channel_layer.group_send)
|
||||
group_add = async_to_sync(channel_layer.group_add)
|
||||
|
||||
|
||||
def group_send(group, event):
|
||||
# we serialize the payload ourselves and deserialize it to ensure it
|
||||
# works with msgpack. This is dirty, but we'll find a better solution
|
||||
# later
|
||||
s = json.dumps(event, cls=DjangoJSONEncoder)
|
||||
event = json.loads(s)
|
||||
logger.debug(
|
||||
"[channels] Dispatching %s to group %s: %s",
|
||||
event["type"],
|
||||
group,
|
||||
{"type": event["data"]["type"]},
|
||||
)
|
||||
async_to_sync(channel_layer.group_send)(group, event)
|
||||
|
|
|
|||
|
|
@ -16,3 +16,5 @@ class JsonAuthConsumer(JsonWebsocketConsumer):
|
|||
super().accept()
|
||||
for group in self.groups:
|
||||
channels.group_add(group, self.channel_name)
|
||||
for group in self.scope["user"].get_channels_groups():
|
||||
channels.group_add(group, self.channel_name)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from . import create_actors
|
||||
from . import create_image_variations
|
||||
from . import django_permissions_to_user_permissions
|
||||
from . import migrate_to_user_libraries
|
||||
from . import test
|
||||
|
||||
|
||||
|
|
@ -8,5 +9,6 @@ __all__ = [
|
|||
"create_actors",
|
||||
"create_image_variations",
|
||||
"django_permissions_to_user_permissions",
|
||||
"migrate_to_user_libraries",
|
||||
"test",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
Mirate instance files to a library #463. For each user that imported music on an
|
||||
instance, we will create a "default" library with related files and an instance-level
|
||||
visibility.
|
||||
|
||||
Files without any import job will be bounded to a "default" library on the first
|
||||
superuser account found. This should now happen though.
|
||||
"""
|
||||
|
||||
from funkwhale_api.music import models
|
||||
from funkwhale_api.users.models import User
|
||||
|
||||
|
||||
def main(command, **kwargs):
|
||||
importer_ids = set(
|
||||
models.ImportBatch.objects.values_list("submitted_by", flat=True)
|
||||
)
|
||||
importers = User.objects.filter(pk__in=importer_ids).order_by("id").select_related()
|
||||
command.stdout.write(
|
||||
"* {} users imported music on this instance".format(len(importers))
|
||||
)
|
||||
files = models.TrackFile.objects.filter(
|
||||
library__isnull=True, jobs__isnull=False
|
||||
).distinct()
|
||||
command.stdout.write(
|
||||
"* Reassigning {} files to importers libraries...".format(files.count())
|
||||
)
|
||||
for user in importers:
|
||||
command.stdout.write(
|
||||
" * Setting up @{}'s 'default' library".format(user.username)
|
||||
)
|
||||
library = user.actor.libraries.get_or_create(actor=user.actor, name="default")[
|
||||
0
|
||||
]
|
||||
user_files = files.filter(jobs__batch__submitted_by=user)
|
||||
total = user_files.count()
|
||||
command.stdout.write(
|
||||
" * Reassigning {} files to the user library...".format(total)
|
||||
)
|
||||
user_files.update(library=library)
|
||||
|
||||
files = models.TrackFile.objects.filter(
|
||||
library__isnull=True, jobs__isnull=True
|
||||
).distinct()
|
||||
command.stdout.write(
|
||||
"* Handling {} files with no import jobs...".format(files.count())
|
||||
)
|
||||
|
||||
user = User.objects.order_by("id").filter(is_superuser=True).first()
|
||||
|
||||
command.stdout.write(" * Setting up @{}'s 'default' library".format(user.username))
|
||||
library = user.actor.libraries.get_or_create(actor=user.actor, name="default")[0]
|
||||
total = files.count()
|
||||
command.stdout.write(
|
||||
" * Reassigning {} files to the user library...".format(total)
|
||||
)
|
||||
files.update(library=library)
|
||||
command.stdout.write(" * Done!")
|
||||
|
|
@ -1,5 +1,70 @@
|
|||
import collections
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class RelatedField(serializers.RelatedField):
|
||||
default_error_messages = {
|
||||
"does_not_exist": _("Object with {related_field_name}={value} does not exist."),
|
||||
"invalid": _("Invalid value."),
|
||||
}
|
||||
|
||||
def __init__(self, related_field_name, serializer, **kwargs):
|
||||
self.related_field_name = related_field_name
|
||||
self.serializer = serializer
|
||||
self.filters = kwargs.pop("filters", None)
|
||||
kwargs["queryset"] = kwargs.pop(
|
||||
"queryset", self.serializer.Meta.model.objects.all()
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def get_filters(self, data):
|
||||
filters = {self.related_field_name: data}
|
||||
if self.filters:
|
||||
filters.update(self.filters(self.context))
|
||||
return filters
|
||||
|
||||
def to_internal_value(self, data):
|
||||
try:
|
||||
queryset = self.get_queryset()
|
||||
filters = self.get_filters(data)
|
||||
return queryset.get(**filters)
|
||||
except ObjectDoesNotExist:
|
||||
self.fail(
|
||||
"does_not_exist",
|
||||
related_field_name=self.related_field_name,
|
||||
value=smart_text(data),
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
self.fail("invalid")
|
||||
|
||||
def to_representation(self, obj):
|
||||
return self.serializer.to_representation(obj)
|
||||
|
||||
def get_choices(self, cutoff=None):
|
||||
queryset = self.get_queryset()
|
||||
if queryset is None:
|
||||
# Ensure that field.choices returns something sensible
|
||||
# even when accessed with a read-only field.
|
||||
return {}
|
||||
|
||||
if cutoff is not None:
|
||||
queryset = queryset[:cutoff]
|
||||
|
||||
return collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
self.to_representation(item)[self.related_field_name],
|
||||
self.display_value(item),
|
||||
)
|
||||
for item in queryset
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class Action(object):
|
||||
def __init__(self, name, allow_all=False, qs_filter=None):
|
||||
|
|
@ -21,6 +86,7 @@ class ActionSerializer(serializers.Serializer):
|
|||
objects = serializers.JSONField(required=True)
|
||||
filters = serializers.DictField(required=False)
|
||||
actions = None
|
||||
pk_field = "pk"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.actions_by_name = {a.name: a for a in self.actions}
|
||||
|
|
@ -51,7 +117,9 @@ class ActionSerializer(serializers.Serializer):
|
|||
if value == "all":
|
||||
return self.queryset.all().order_by("id")
|
||||
if type(value) in [list, tuple]:
|
||||
return self.queryset.filter(pk__in=value).order_by("id")
|
||||
return self.queryset.filter(
|
||||
**{"{}__in".format(self.pk_field): value}
|
||||
).order_by("id")
|
||||
|
||||
raise serializers.ValidationError(
|
||||
"{} is not a valid value for objects. You must provide either a "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue