Update Black & run for whole repository
This commit is contained in:
parent
68210d5330
commit
850dc69091
|
@ -102,12 +102,13 @@ black:
|
||||||
variables:
|
variables:
|
||||||
GIT_STRATEGY: fetch
|
GIT_STRATEGY: fetch
|
||||||
before_script:
|
before_script:
|
||||||
- pip install black==19.10b0
|
- pip install black
|
||||||
script:
|
script:
|
||||||
- black --check --diff api/
|
- black --check --diff .
|
||||||
only:
|
only:
|
||||||
changes:
|
changes:
|
||||||
- api/**/*
|
- "**/*.py"
|
||||||
|
- .gitlab-ci.yml
|
||||||
|
|
||||||
flake8:
|
flake8:
|
||||||
interruptible: true
|
interruptible: true
|
||||||
|
|
|
@ -73,7 +73,10 @@ v1_patterns += [
|
||||||
r"^history/",
|
r"^history/",
|
||||||
include(("funkwhale_api.history.urls", "history"), namespace="history"),
|
include(("funkwhale_api.history.urls", "history"), namespace="history"),
|
||||||
),
|
),
|
||||||
url(r"^", include(("funkwhale_api.users.api_urls", "users"), namespace="users"),),
|
url(
|
||||||
|
r"^",
|
||||||
|
include(("funkwhale_api.users.api_urls", "users"), namespace="users"),
|
||||||
|
),
|
||||||
# XXX: remove if Funkwhale 1.1
|
# XXX: remove if Funkwhale 1.1
|
||||||
url(
|
url(
|
||||||
r"^users/",
|
r"^users/",
|
||||||
|
|
|
@ -180,7 +180,9 @@ def set_conf(name, conf, user=None, registry=_plugins):
|
||||||
if not registry[name]["conf"] and not registry[name]["source"]:
|
if not registry[name]["conf"] and not registry[name]["source"]:
|
||||||
return
|
return
|
||||||
conf_serializer = get_serializer_from_conf_template(
|
conf_serializer = get_serializer_from_conf_template(
|
||||||
registry[name]["conf"], user=user, source=registry[name]["source"],
|
registry[name]["conf"],
|
||||||
|
user=user,
|
||||||
|
source=registry[name]["source"],
|
||||||
)(data=conf)
|
)(data=conf)
|
||||||
conf_serializer.is_valid(raise_exception=True)
|
conf_serializer.is_valid(raise_exception=True)
|
||||||
if "library" in conf_serializer.validated_data:
|
if "library" in conf_serializer.validated_data:
|
||||||
|
|
|
@ -135,7 +135,8 @@ class ChannelCreateSerializer(serializers.Serializer):
|
||||||
metadata=validated_data["metadata"],
|
metadata=validated_data["metadata"],
|
||||||
)
|
)
|
||||||
channel.actor = models.generate_actor(
|
channel.actor = models.generate_actor(
|
||||||
validated_data["username"], name=validated_data["name"],
|
validated_data["username"],
|
||||||
|
name=validated_data["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
channel.library = music_models.Library.objects.create(
|
channel.library = music_models.Library.objects.create(
|
||||||
|
@ -571,7 +572,8 @@ class RssFeedSerializer(serializers.Serializer):
|
||||||
|
|
||||||
# create/update the channel
|
# create/update the channel
|
||||||
channel, created = models.Channel.objects.update_or_create(
|
channel, created = models.Channel.objects.update_or_create(
|
||||||
pk=existing.pk if existing else None, defaults=channel_defaults,
|
pk=existing.pk if existing else None,
|
||||||
|
defaults=channel_defaults,
|
||||||
)
|
)
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
|
@ -773,7 +775,8 @@ class RssFeedItemSerializer(serializers.Serializer):
|
||||||
|
|
||||||
# create/update the track
|
# create/update the track
|
||||||
track, created = music_models.Track.objects.update_or_create(
|
track, created = music_models.Track.objects.update_or_create(
|
||||||
**track_kwargs, defaults=track_defaults,
|
**track_kwargs,
|
||||||
|
defaults=track_defaults,
|
||||||
)
|
)
|
||||||
# optimisation for reducing SQL queries, because we cannot use select_related with
|
# optimisation for reducing SQL queries, because we cannot use select_related with
|
||||||
# update or create, so we restore the cache by hand
|
# update or create, so we restore the cache by hand
|
||||||
|
|
|
@ -27,7 +27,10 @@ from funkwhale_api.users.oauth import permissions as oauth_permissions
|
||||||
from . import categories, filters, models, renderers, serializers
|
from . import categories, filters, models, renderers, serializers
|
||||||
|
|
||||||
ARTIST_PREFETCH_QS = (
|
ARTIST_PREFETCH_QS = (
|
||||||
music_models.Artist.objects.select_related("description", "attachment_cover",)
|
music_models.Artist.objects.select_related(
|
||||||
|
"description",
|
||||||
|
"attachment_cover",
|
||||||
|
)
|
||||||
.prefetch_related(music_views.TAG_PREFETCH)
|
.prefetch_related(music_views.TAG_PREFETCH)
|
||||||
.annotate(_tracks_count=Count("tracks"))
|
.annotate(_tracks_count=Count("tracks"))
|
||||||
)
|
)
|
||||||
|
@ -192,7 +195,9 @@ class ChannelViewSet(
|
||||||
"track",
|
"track",
|
||||||
queryset=music_models.Track.objects.select_related(
|
queryset=music_models.Track.objects.select_related(
|
||||||
"attachment_cover", "description"
|
"attachment_cover", "description"
|
||||||
).prefetch_related(music_views.TAG_PREFETCH,),
|
).prefetch_related(
|
||||||
|
music_views.TAG_PREFETCH,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.select_related("track__attachment_cover", "track__description")
|
.select_related("track__attachment_cover", "track__description")
|
||||||
|
@ -232,7 +237,9 @@ class ChannelViewSet(
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
return response.Response(serializer.errors, status=400)
|
return response.Response(serializer.errors, status=400)
|
||||||
channel = (
|
channel = (
|
||||||
models.Channel.objects.filter(rss_url=serializer.validated_data["url"],)
|
models.Channel.objects.filter(
|
||||||
|
rss_url=serializer.validated_data["url"],
|
||||||
|
)
|
||||||
.order_by("id")
|
.order_by("id")
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
@ -243,7 +250,10 @@ class ChannelViewSet(
|
||||||
serializer.validated_data["url"]
|
serializer.validated_data["url"]
|
||||||
)
|
)
|
||||||
except serializers.FeedFetchException as e:
|
except serializers.FeedFetchException as e:
|
||||||
return response.Response({"detail": str(e)}, status=400,)
|
return response.Response(
|
||||||
|
{"detail": str(e)},
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
subscription = federation_models.Follow(actor=request.user.actor)
|
subscription = federation_models.Follow(actor=request.user.actor)
|
||||||
subscription.fid = subscription.get_federation_id()
|
subscription.fid = subscription.get_federation_id()
|
||||||
|
|
|
@ -6,7 +6,8 @@ from . import base
|
||||||
|
|
||||||
|
|
||||||
def handler_add_tags_from_tracks(
|
def handler_add_tags_from_tracks(
|
||||||
artists=False, albums=False,
|
artists=False,
|
||||||
|
albums=False,
|
||||||
):
|
):
|
||||||
result = None
|
result = None
|
||||||
if artists:
|
if artists:
|
||||||
|
|
|
@ -157,13 +157,16 @@ def users():
|
||||||
type=click.INT,
|
type=click.INT,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--superuser/--no-superuser", default=False,
|
"--superuser/--no-superuser",
|
||||||
|
default=False,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--staff/--no-staff", default=False,
|
"--staff/--no-staff",
|
||||||
|
default=False,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--permission", multiple=True,
|
"--permission",
|
||||||
|
multiple=True,
|
||||||
)
|
)
|
||||||
def create(username, password, email, superuser, staff, permission, upload_quota):
|
def create(username, password, email, superuser, staff, permission, upload_quota):
|
||||||
"""Create a new user"""
|
"""Create a new user"""
|
||||||
|
@ -210,7 +213,9 @@ def delete(username, hard):
|
||||||
@click.option("--permission-settings/--no-permission-settings", default=None)
|
@click.option("--permission-settings/--no-permission-settings", default=None)
|
||||||
@click.option("--password", default=None, envvar="FUNKWHALE_CLI_USER_UPDATE_PASSWORD")
|
@click.option("--password", default=None, envvar="FUNKWHALE_CLI_USER_UPDATE_PASSWORD")
|
||||||
@click.option(
|
@click.option(
|
||||||
"-q", "--upload-quota", type=click.INT,
|
"-q",
|
||||||
|
"--upload-quota",
|
||||||
|
type=click.INT,
|
||||||
)
|
)
|
||||||
def update(username, **kwargs):
|
def update(username, **kwargs):
|
||||||
"""Update attributes for given users"""
|
"""Update attributes for given users"""
|
||||||
|
|
|
@ -227,7 +227,8 @@ class ActorScopeFilter(filters.CharFilter):
|
||||||
username, domain = full_username.split("@")
|
username, domain = full_username.split("@")
|
||||||
try:
|
try:
|
||||||
actor = federation_models.Actor.objects.get(
|
actor = federation_models.Actor.objects.get(
|
||||||
preferred_username__iexact=username, domain_id=domain,
|
preferred_username__iexact=username,
|
||||||
|
domain_id=domain,
|
||||||
)
|
)
|
||||||
except federation_models.Actor.DoesNotExist:
|
except federation_models.Actor.DoesNotExist:
|
||||||
raise EmptyQuerySet()
|
raise EmptyQuerySet()
|
||||||
|
|
|
@ -126,7 +126,9 @@ def get_spa_file(spa_url, name):
|
||||||
if cached:
|
if cached:
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
response = session.get_session().get(utils.join_url(spa_url, name),)
|
response = session.get_session().get(
|
||||||
|
utils.join_url(spa_url, name),
|
||||||
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
content = response.text
|
content = response.text
|
||||||
caches["local"].set(cache_key, content, settings.FUNKWHALE_SPA_HTML_CACHE_DURATION)
|
caches["local"].set(cache_key, content, settings.FUNKWHALE_SPA_HTML_CACHE_DURATION)
|
||||||
|
|
|
@ -222,7 +222,8 @@ class Attachment(models.Model):
|
||||||
validators=[
|
validators=[
|
||||||
validators.ImageDimensionsValidator(min_width=50, min_height=50),
|
validators.ImageDimensionsValidator(min_width=50, min_height=50),
|
||||||
validators.FileValidator(
|
validators.FileValidator(
|
||||||
allowed_extensions=["png", "jpg", "jpeg"], max_size=1024 * 1024 * 5,
|
allowed_extensions=["png", "jpg", "jpeg"],
|
||||||
|
max_size=1024 * 1024 * 5,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,20 +26,20 @@ def normalize_query(
|
||||||
findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
|
findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
|
||||||
normspace=re.compile(r"\s{2,}").sub,
|
normspace=re.compile(r"\s{2,}").sub,
|
||||||
):
|
):
|
||||||
""" Splits the query string in invidual keywords, getting rid of unecessary spaces
|
"""Splits the query string in invidual keywords, getting rid of unecessary spaces
|
||||||
and grouping quoted words together.
|
and grouping quoted words together.
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
>>> normalize_query(' some random words "with quotes " and spaces')
|
>>> normalize_query(' some random words "with quotes " and spaces')
|
||||||
['some', 'random', 'words', 'with quotes', 'and', 'spaces']
|
['some', 'random', 'words', 'with quotes', 'and', 'spaces']
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return [normspace(" ", (t[0] or t[1]).strip()) for t in findterms(query_string)]
|
return [normspace(" ", (t[0] or t[1]).strip()) for t in findterms(query_string)]
|
||||||
|
|
||||||
|
|
||||||
def get_query(query_string, search_fields):
|
def get_query(query_string, search_fields):
|
||||||
""" Returns a query, that is a combination of Q objects. That combination
|
"""Returns a query, that is a combination of Q objects. That combination
|
||||||
aims to search keywords within a model by testing the given search fields.
|
aims to search keywords within a model by testing the given search fields.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
query = None # Query to search for every search term
|
query = None # Query to search for every search term
|
||||||
|
|
|
@ -310,7 +310,9 @@ class ContentSerializer(serializers.Serializer):
|
||||||
text = serializers.CharField(
|
text = serializers.CharField(
|
||||||
max_length=models.CONTENT_TEXT_MAX_LENGTH, allow_null=True
|
max_length=models.CONTENT_TEXT_MAX_LENGTH, allow_null=True
|
||||||
)
|
)
|
||||||
content_type = serializers.ChoiceField(choices=models.CONTENT_TEXT_SUPPORTED_TYPES,)
|
content_type = serializers.ChoiceField(
|
||||||
|
choices=models.CONTENT_TEXT_SUPPORTED_TYPES,
|
||||||
|
)
|
||||||
html = serializers.SerializerMethodField()
|
html = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_html(self, o):
|
def get_html(self, o):
|
||||||
|
|
|
@ -139,7 +139,13 @@ def handshake_v2(username, password, session, api_key, api_secret, scrobble_url)
|
||||||
|
|
||||||
|
|
||||||
def submit_scrobble_v2(
|
def submit_scrobble_v2(
|
||||||
session, track, scrobble_time, session_key, scrobble_url, api_key, api_secret,
|
session,
|
||||||
|
track,
|
||||||
|
scrobble_time,
|
||||||
|
session_key,
|
||||||
|
scrobble_url,
|
||||||
|
api_key,
|
||||||
|
api_secret,
|
||||||
):
|
):
|
||||||
params = {
|
params = {
|
||||||
"method": "track.scrobble",
|
"method": "track.scrobble",
|
||||||
|
|
|
@ -15,7 +15,8 @@ logger = logging.getLogger(__name__)
|
||||||
def get_actor_data(actor_url):
|
def get_actor_data(actor_url):
|
||||||
logger.debug("Fetching actor %s", actor_url)
|
logger.debug("Fetching actor %s", actor_url)
|
||||||
response = session.get_session().get(
|
response = session.get_session().get(
|
||||||
actor_url, headers={"Accept": "application/activity+json"},
|
actor_url,
|
||||||
|
headers={"Accept": "application/activity+json"},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -53,7 +53,9 @@ class SignatureAuthentication(authentication.BaseAuthentication):
|
||||||
actor = actors.get_actor(actor_url)
|
actor = actors.get_actor(actor_url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Discarding HTTP request from actor/domain %s, %s", actor_url, str(e),
|
"Discarding HTTP request from actor/domain %s, %s",
|
||||||
|
actor_url,
|
||||||
|
str(e),
|
||||||
)
|
)
|
||||||
raise rest_exceptions.AuthenticationFailed(
|
raise rest_exceptions.AuthenticationFailed(
|
||||||
"Cannot fetch remote actor to authenticate signature"
|
"Cannot fetch remote actor to authenticate signature"
|
||||||
|
|
|
@ -128,7 +128,9 @@ class ActorFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
|
||||||
model = models.Actor
|
model = models.Actor
|
||||||
|
|
||||||
class Params:
|
class Params:
|
||||||
with_real_keys = factory.Trait(keys=factory.LazyFunction(keys.get_key_pair),)
|
with_real_keys = factory.Trait(
|
||||||
|
keys=factory.LazyFunction(keys.get_key_pair),
|
||||||
|
)
|
||||||
|
|
||||||
@factory.post_generation
|
@factory.post_generation
|
||||||
def local(self, create, extracted, **kwargs):
|
def local(self, create, extracted, **kwargs):
|
||||||
|
|
|
@ -192,7 +192,9 @@ def prepare_for_serializer(payload, config, fallbacks={}):
|
||||||
for a in aliases:
|
for a in aliases:
|
||||||
try:
|
try:
|
||||||
value = get_value(
|
value = get_value(
|
||||||
payload[a["property"]], keep=a.get("keep"), attr=a.get("attr"),
|
payload[a["property"]],
|
||||||
|
keep=a.get("keep"),
|
||||||
|
attr=a.get("attr"),
|
||||||
)
|
)
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -9,7 +9,9 @@ def get_library_data(library_url, actor):
|
||||||
auth = signing.get_auth(actor.private_key, actor.private_key_id)
|
auth = signing.get_auth(actor.private_key, actor.private_key_id)
|
||||||
try:
|
try:
|
||||||
response = session.get_session().get(
|
response = session.get_session().get(
|
||||||
library_url, auth=auth, headers={"Accept": "application/activity+json"},
|
library_url,
|
||||||
|
auth=auth,
|
||||||
|
headers={"Accept": "application/activity+json"},
|
||||||
)
|
)
|
||||||
except requests.ConnectionError:
|
except requests.ConnectionError:
|
||||||
return {"errors": ["This library is not reachable"]}
|
return {"errors": ["This library is not reachable"]}
|
||||||
|
@ -30,7 +32,9 @@ def get_library_data(library_url, actor):
|
||||||
def get_library_page(library, page_url, actor):
|
def get_library_page(library, page_url, actor):
|
||||||
auth = signing.get_auth(actor.private_key, actor.private_key_id)
|
auth = signing.get_auth(actor.private_key, actor.private_key_id)
|
||||||
response = session.get_session().get(
|
response = session.get_session().get(
|
||||||
page_url, auth=auth, headers={"Accept": "application/activity+json"},
|
page_url,
|
||||||
|
auth=auth,
|
||||||
|
headers={"Accept": "application/activity+json"},
|
||||||
)
|
)
|
||||||
serializer = serializers.CollectionPageSerializer(
|
serializer = serializers.CollectionPageSerializer(
|
||||||
data=response.json(),
|
data=response.json(),
|
||||||
|
|
|
@ -196,7 +196,8 @@ def inbox_create_audio(payload, context):
|
||||||
if is_channel:
|
if is_channel:
|
||||||
channel = context["actor"].get_channel()
|
channel = context["actor"].get_channel()
|
||||||
serializer = serializers.ChannelCreateUploadSerializer(
|
serializer = serializers.ChannelCreateUploadSerializer(
|
||||||
data=payload, context={"channel": channel},
|
data=payload,
|
||||||
|
context={"channel": channel},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
serializer = serializers.UploadSerializer(
|
serializer = serializers.UploadSerializer(
|
||||||
|
|
|
@ -500,7 +500,10 @@ def create_or_update_channel(actor, rss_url, attributed_to_fid, **validated_data
|
||||||
reverse("federation:music:libraries-detail", kwargs={"uuid": uid})
|
reverse("federation:music:libraries-detail", kwargs={"uuid": uid})
|
||||||
)
|
)
|
||||||
library = attributed_to.libraries.create(
|
library = attributed_to.libraries.create(
|
||||||
privacy_level="everyone", name=artist_defaults["name"], fid=fid, uuid=uid,
|
privacy_level="everyone",
|
||||||
|
name=artist_defaults["name"],
|
||||||
|
fid=fid,
|
||||||
|
uuid=uid,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
library = artist.channel.library
|
library = artist.channel.library
|
||||||
|
@ -512,7 +515,9 @@ def create_or_update_channel(actor, rss_url, attributed_to_fid, **validated_data
|
||||||
"library": library,
|
"library": library,
|
||||||
}
|
}
|
||||||
channel, created = audio_models.Channel.objects.update_or_create(
|
channel, created = audio_models.Channel.objects.update_or_create(
|
||||||
actor=actor, attributed_to=attributed_to, defaults=channel_defaults,
|
actor=actor,
|
||||||
|
attributed_to=attributed_to,
|
||||||
|
defaults=channel_defaults,
|
||||||
)
|
)
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
|
@ -1734,7 +1739,8 @@ class FlagSerializer(jsonld.JsonLdSerializer):
|
||||||
}
|
}
|
||||||
|
|
||||||
report, created = moderation_models.Report.objects.update_or_create(
|
report, created = moderation_models.Report.objects.update_or_create(
|
||||||
fid=validated_data["id"], defaults=kwargs,
|
fid=validated_data["id"],
|
||||||
|
defaults=kwargs,
|
||||||
)
|
)
|
||||||
moderation_signals.report_created.send(sender=None, report=report)
|
moderation_signals.report_created.send(sender=None, report=report)
|
||||||
return report
|
return report
|
||||||
|
|
|
@ -338,7 +338,9 @@ def fetch(fetch_obj):
|
||||||
if not payload:
|
if not payload:
|
||||||
return error("blocked", message="Blocked by MRF")
|
return error("blocked", message="Blocked by MRF")
|
||||||
response = session.get_session().get(
|
response = session.get_session().get(
|
||||||
auth=auth, url=url, headers={"Accept": "application/activity+json"},
|
auth=auth,
|
||||||
|
url=url,
|
||||||
|
headers={"Accept": "application/activity+json"},
|
||||||
)
|
)
|
||||||
logger.debug("Remote answered with %s: %s", response.status_code, response.text)
|
logger.debug("Remote answered with %s: %s", response.status_code, response.text)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
@ -425,7 +427,9 @@ def fetch(fetch_obj):
|
||||||
# first page fetch is synchronous, so that at least some data is available
|
# first page fetch is synchronous, so that at least some data is available
|
||||||
# in the UI after subscription
|
# in the UI after subscription
|
||||||
result = fetch_collection(
|
result = fetch_collection(
|
||||||
obj.actor.outbox_url, channel_id=obj.pk, max_pages=1,
|
obj.actor.outbox_url,
|
||||||
|
channel_id=obj.pk,
|
||||||
|
max_pages=1,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
@ -473,7 +477,8 @@ class PreserveSomeDataCollector(Collector):
|
||||||
@celery.app.task(name="federation.remove_actor")
|
@celery.app.task(name="federation.remove_actor")
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@celery.require_instance(
|
@celery.require_instance(
|
||||||
models.Actor.objects.all(), "actor",
|
models.Actor.objects.all(),
|
||||||
|
"actor",
|
||||||
)
|
)
|
||||||
def remove_actor(actor):
|
def remove_actor(actor):
|
||||||
# Then we broadcast the info over federation. We do this *before* deleting objects
|
# Then we broadcast the info over federation. We do this *before* deleting objects
|
||||||
|
@ -531,7 +536,9 @@ def match_serializer(payload, conf):
|
||||||
|
|
||||||
@celery.app.task(name="federation.fetch_collection")
|
@celery.app.task(name="federation.fetch_collection")
|
||||||
@celery.require_instance(
|
@celery.require_instance(
|
||||||
audio_models.Channel.objects.all(), "channel", allow_null=True,
|
audio_models.Channel.objects.all(),
|
||||||
|
"channel",
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
def fetch_collection(url, max_pages, channel, is_page=False):
|
def fetch_collection(url, max_pages, channel, is_page=False):
|
||||||
actor = actors.get_service_actor()
|
actor = actors.get_service_actor()
|
||||||
|
@ -564,7 +571,11 @@ def fetch_collection(url, max_pages, channel, is_page=False):
|
||||||
for i in range(max_pages):
|
for i in range(max_pages):
|
||||||
page_url = results["next_page"]
|
page_url = results["next_page"]
|
||||||
logger.debug("Handling page %s on max %s, at %s", i + 1, max_pages, page_url)
|
logger.debug("Handling page %s on max %s, at %s", i + 1, max_pages, page_url)
|
||||||
page = utils.retrieve_ap_object(page_url, actor=actor, serializer_class=None,)
|
page = utils.retrieve_ap_object(
|
||||||
|
page_url,
|
||||||
|
actor=actor,
|
||||||
|
serializer_class=None,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
items = page["orderedItems"]
|
items = page["orderedItems"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -470,7 +470,8 @@ class IndexViewSet(FederationMixin, viewsets.GenericViewSet):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
@action(
|
@action(
|
||||||
methods=["get"], detail=False,
|
methods=["get"],
|
||||||
|
detail=False,
|
||||||
)
|
)
|
||||||
def libraries(self, request, *args, **kwargs):
|
def libraries(self, request, *args, **kwargs):
|
||||||
libraries = (
|
libraries = (
|
||||||
|
@ -497,7 +498,8 @@ class IndexViewSet(FederationMixin, viewsets.GenericViewSet):
|
||||||
return response.Response({}, status=200)
|
return response.Response({}, status=200)
|
||||||
|
|
||||||
@action(
|
@action(
|
||||||
methods=["get"], detail=False,
|
methods=["get"],
|
||||||
|
detail=False,
|
||||||
)
|
)
|
||||||
def channels(self, request, *args, **kwargs):
|
def channels(self, request, *args, **kwargs):
|
||||||
actors = (
|
actors = (
|
||||||
|
|
|
@ -686,7 +686,10 @@ class ManageChannelViewSet(
|
||||||
queryset = (
|
queryset = (
|
||||||
audio_models.Channel.objects.all()
|
audio_models.Channel.objects.all()
|
||||||
.order_by("-id")
|
.order_by("-id")
|
||||||
.select_related("attributed_to", "actor",)
|
.select_related(
|
||||||
|
"attributed_to",
|
||||||
|
"actor",
|
||||||
|
)
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
"artist",
|
"artist",
|
||||||
|
|
|
@ -82,7 +82,8 @@ class Command(BaseCommand):
|
||||||
content = models.Activity.objects.get(uuid=input).payload
|
content = models.Activity.objects.get(uuid=input).payload
|
||||||
elif is_url(input):
|
elif is_url(input):
|
||||||
response = session.get_session().get(
|
response = session.get_session().get(
|
||||||
input, headers={"Accept": "application/activity+json"},
|
input,
|
||||||
|
headers={"Accept": "application/activity+json"},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
content = response.json()
|
content = response.json()
|
||||||
|
|
|
@ -205,7 +205,9 @@ class UserRequest(models.Model):
|
||||||
max_length=40, choices=USER_REQUEST_STATUSES, default="pending"
|
max_length=40, choices=USER_REQUEST_STATUSES, default="pending"
|
||||||
)
|
)
|
||||||
submitter = models.ForeignKey(
|
submitter = models.ForeignKey(
|
||||||
"federation.Actor", related_name="requests", on_delete=models.CASCADE,
|
"federation.Actor",
|
||||||
|
related_name="requests",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
assigned_to = models.ForeignKey(
|
assigned_to = models.ForeignKey(
|
||||||
"federation.Actor",
|
"federation.Actor",
|
||||||
|
|
|
@ -211,7 +211,9 @@ class UploadFilter(audio_filters.IncludeChannelsFilterSet):
|
||||||
library = filters.UUIDFilter("library__uuid")
|
library = filters.UUIDFilter("library__uuid")
|
||||||
playable = filters.BooleanFilter(field_name="_", method="filter_playable")
|
playable = filters.BooleanFilter(field_name="_", method="filter_playable")
|
||||||
scope = common_filters.ActorScopeFilter(
|
scope = common_filters.ActorScopeFilter(
|
||||||
actor_field="library__actor", distinct=True, library_field="library",
|
actor_field="library__actor",
|
||||||
|
distinct=True,
|
||||||
|
library_field="library",
|
||||||
)
|
)
|
||||||
import_status = common_filters.MultipleQueryFilter(coerce=str)
|
import_status = common_filters.MultipleQueryFilter(coerce=str)
|
||||||
q = fields.SmartSearchFilter(
|
q = fields.SmartSearchFilter(
|
||||||
|
@ -291,9 +293,13 @@ class AlbumFilter(
|
||||||
|
|
||||||
|
|
||||||
class LibraryFilter(filters.FilterSet):
|
class LibraryFilter(filters.FilterSet):
|
||||||
q = fields.SearchFilter(search_fields=["name"],)
|
q = fields.SearchFilter(
|
||||||
|
search_fields=["name"],
|
||||||
|
)
|
||||||
scope = common_filters.ActorScopeFilter(
|
scope = common_filters.ActorScopeFilter(
|
||||||
actor_field="actor", distinct=True, library_field="pk",
|
actor_field="actor",
|
||||||
|
distinct=True,
|
||||||
|
library_field="pk",
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -364,12 +364,15 @@ class Command(BaseCommand):
|
||||||
time_stats = ""
|
time_stats = ""
|
||||||
if i > 0:
|
if i > 0:
|
||||||
time_stats = " - running for {}s, previous batch took {}s".format(
|
time_stats = " - running for {}s, previous batch took {}s".format(
|
||||||
int(time.time() - start_time), int(batch_duration),
|
int(time.time() - start_time),
|
||||||
|
int(batch_duration),
|
||||||
)
|
)
|
||||||
if entries:
|
if entries:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
"Handling batch {} ({} items){}".format(
|
"Handling batch {} ({} items){}".format(
|
||||||
i + 1, len(entries), time_stats,
|
i + 1,
|
||||||
|
len(entries),
|
||||||
|
time_stats,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
batch_errors = self.handle_batch(
|
batch_errors = self.handle_batch(
|
||||||
|
@ -536,7 +539,8 @@ class Command(BaseCommand):
|
||||||
watchdog_queue = queue.Queue()
|
watchdog_queue = queue.Queue()
|
||||||
# Set up a worker thread to process database load
|
# Set up a worker thread to process database load
|
||||||
worker = threading.Thread(
|
worker = threading.Thread(
|
||||||
target=process_load_queue(self.stdout, **kwargs), args=(watchdog_queue,),
|
target=process_load_queue(self.stdout, **kwargs),
|
||||||
|
args=(watchdog_queue,),
|
||||||
)
|
)
|
||||||
worker.setDaemon(True)
|
worker.setDaemon(True)
|
||||||
worker.start()
|
worker.start()
|
||||||
|
@ -544,7 +548,9 @@ class Command(BaseCommand):
|
||||||
# setup watchdog to monitor directory for trigger files
|
# setup watchdog to monitor directory for trigger files
|
||||||
patterns = ["*.{}".format(e) for e in extensions]
|
patterns = ["*.{}".format(e) for e in extensions]
|
||||||
event_handler = Watcher(
|
event_handler = Watcher(
|
||||||
stdout=self.stdout, queue=watchdog_queue, patterns=patterns,
|
stdout=self.stdout,
|
||||||
|
queue=watchdog_queue,
|
||||||
|
patterns=patterns,
|
||||||
)
|
)
|
||||||
observer = watchdog.observers.Observer()
|
observer = watchdog.observers.Observer()
|
||||||
observer.schedule(event_handler, path, recursive=recursive)
|
observer.schedule(event_handler, path, recursive=recursive)
|
||||||
|
@ -581,7 +587,14 @@ def prune():
|
||||||
|
|
||||||
|
|
||||||
def create_upload(
|
def create_upload(
|
||||||
path, reference, library, async_, replace, in_place, dispatch_outbox, broadcast,
|
path,
|
||||||
|
reference,
|
||||||
|
library,
|
||||||
|
async_,
|
||||||
|
replace,
|
||||||
|
in_place,
|
||||||
|
dispatch_outbox,
|
||||||
|
broadcast,
|
||||||
):
|
):
|
||||||
import_handler = tasks.process_upload.delay if async_ else tasks.process_upload
|
import_handler = tasks.process_upload.delay if async_ else tasks.process_upload
|
||||||
upload = models.Upload(library=library, import_reference=reference)
|
upload = models.Upload(library=library, import_reference=reference)
|
||||||
|
@ -692,7 +705,9 @@ def handle_modified(event, stdout, library, in_place, **kwargs):
|
||||||
existing_candidates.in_place()
|
existing_candidates.in_place()
|
||||||
.filter(source=source)
|
.filter(source=source)
|
||||||
.select_related(
|
.select_related(
|
||||||
"track__attributed_to", "track__artist", "track__album__artist",
|
"track__attributed_to",
|
||||||
|
"track__artist",
|
||||||
|
"track__album__artist",
|
||||||
)
|
)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
@ -792,7 +807,12 @@ def check_updates(stdout, library, extensions, paths, batch_size):
|
||||||
stdout.write("Found {} files to check in database!".format(total))
|
stdout.write("Found {} files to check in database!".format(total))
|
||||||
uploads = existing.order_by("source")
|
uploads = existing.order_by("source")
|
||||||
for i, rows in enumerate(batch(uploads.iterator(), batch_size)):
|
for i, rows in enumerate(batch(uploads.iterator(), batch_size)):
|
||||||
stdout.write("Handling batch {} ({} items)".format(i + 1, len(rows),))
|
stdout.write(
|
||||||
|
"Handling batch {} ({} items)".format(
|
||||||
|
i + 1,
|
||||||
|
len(rows),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
for upload in rows:
|
for upload in rows:
|
||||||
|
|
||||||
|
|
|
@ -331,7 +331,8 @@ class AlbumQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet):
|
||||||
)
|
)
|
||||||
return self.annotate(
|
return self.annotate(
|
||||||
duration=models.Sum(
|
duration=models.Sum(
|
||||||
"tracks__uploads__duration", filter=Q(tracks__uploads=subquery),
|
"tracks__uploads__duration",
|
||||||
|
filter=Q(tracks__uploads=subquery),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1177,7 +1178,10 @@ class LibraryQuerySet(models.QuerySet):
|
||||||
).values_list("target", flat=True)
|
).values_list("target", flat=True)
|
||||||
followed_channels_libraries = (
|
followed_channels_libraries = (
|
||||||
Follow.objects.exclude(target__channel=None)
|
Follow.objects.exclude(target__channel=None)
|
||||||
.filter(actor=actor, approved=True,)
|
.filter(
|
||||||
|
actor=actor,
|
||||||
|
approved=True,
|
||||||
|
)
|
||||||
.values_list("target__channel__library", flat=True)
|
.values_list("target__channel__library", flat=True)
|
||||||
)
|
)
|
||||||
return self.filter(
|
return self.filter(
|
||||||
|
|
|
@ -173,7 +173,8 @@ def fail_import(upload, error_code, detail=None, **fields):
|
||||||
@celery.app.task(name="music.process_upload")
|
@celery.app.task(name="music.process_upload")
|
||||||
@celery.require_instance(
|
@celery.require_instance(
|
||||||
models.Upload.objects.filter(import_status="pending").select_related(
|
models.Upload.objects.filter(import_status="pending").select_related(
|
||||||
"library__actor__user", "library__channel__artist",
|
"library__actor__user",
|
||||||
|
"library__channel__artist",
|
||||||
),
|
),
|
||||||
"upload",
|
"upload",
|
||||||
)
|
)
|
||||||
|
@ -239,7 +240,9 @@ def process_upload(upload, update_denormalization=True):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
final_metadata = collections.ChainMap(
|
final_metadata = collections.ChainMap(
|
||||||
additional_data, forced_values, internal_config,
|
additional_data,
|
||||||
|
forced_values,
|
||||||
|
internal_config,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
track = get_track_from_import_metadata(
|
track = get_track_from_import_metadata(
|
||||||
|
@ -310,7 +313,8 @@ def process_upload(upload, update_denormalization=True):
|
||||||
# update album cover, if needed
|
# update album cover, if needed
|
||||||
if track.album and not track.album.attachment_cover:
|
if track.album and not track.album.attachment_cover:
|
||||||
populate_album_cover(
|
populate_album_cover(
|
||||||
track.album, source=final_metadata.get("upload_source"),
|
track.album,
|
||||||
|
source=final_metadata.get("upload_source"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if broadcast:
|
if broadcast:
|
||||||
|
@ -793,7 +797,9 @@ def albums_set_tags_from_tracks(ids=None, dry_run=False):
|
||||||
if ids is not None:
|
if ids is not None:
|
||||||
qs = qs.filter(pk__in=ids)
|
qs = qs.filter(pk__in=ids)
|
||||||
data = tags_tasks.get_tags_from_foreign_key(
|
data = tags_tasks.get_tags_from_foreign_key(
|
||||||
ids=qs, foreign_key_model=models.Track, foreign_key_attr="album",
|
ids=qs,
|
||||||
|
foreign_key_model=models.Track,
|
||||||
|
foreign_key_attr="album",
|
||||||
)
|
)
|
||||||
logger.info("Found automatic tags for %s albums…", len(data))
|
logger.info("Found automatic tags for %s albums…", len(data))
|
||||||
if dry_run:
|
if dry_run:
|
||||||
|
@ -801,7 +807,8 @@ def albums_set_tags_from_tracks(ids=None, dry_run=False):
|
||||||
return
|
return
|
||||||
|
|
||||||
tags_tasks.add_tags_batch(
|
tags_tasks.add_tags_batch(
|
||||||
data, model=models.Album,
|
data,
|
||||||
|
model=models.Album,
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -815,7 +822,9 @@ def artists_set_tags_from_tracks(ids=None, dry_run=False):
|
||||||
if ids is not None:
|
if ids is not None:
|
||||||
qs = qs.filter(pk__in=ids)
|
qs = qs.filter(pk__in=ids)
|
||||||
data = tags_tasks.get_tags_from_foreign_key(
|
data = tags_tasks.get_tags_from_foreign_key(
|
||||||
ids=qs, foreign_key_model=models.Track, foreign_key_attr="artist",
|
ids=qs,
|
||||||
|
foreign_key_model=models.Track,
|
||||||
|
foreign_key_attr="artist",
|
||||||
)
|
)
|
||||||
logger.info("Found automatic tags for %s artists…", len(data))
|
logger.info("Found automatic tags for %s artists…", len(data))
|
||||||
if dry_run:
|
if dry_run:
|
||||||
|
@ -823,7 +832,8 @@ def artists_set_tags_from_tracks(ids=None, dry_run=False):
|
||||||
return
|
return
|
||||||
|
|
||||||
tags_tasks.add_tags_batch(
|
tags_tasks.add_tags_batch(
|
||||||
data, model=models.Artist,
|
data,
|
||||||
|
model=models.Artist,
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,8 @@ def get_libraries(filter_uploads):
|
||||||
uploads = filter_uploads(obj, uploads)
|
uploads = filter_uploads(obj, uploads)
|
||||||
uploads = uploads.playable_by(actor)
|
uploads = uploads.playable_by(actor)
|
||||||
qs = models.Library.objects.filter(
|
qs = models.Library.objects.filter(
|
||||||
pk__in=uploads.values_list("library", flat=True), channel=None,
|
pk__in=uploads.values_list("library", flat=True),
|
||||||
|
channel=None,
|
||||||
).annotate(_uploads_count=Count("uploads"))
|
).annotate(_uploads_count=Count("uploads"))
|
||||||
qs = qs.prefetch_related("actor")
|
qs = qs.prefetch_related("actor")
|
||||||
page = self.paginate_queryset(qs)
|
page = self.paginate_queryset(qs)
|
||||||
|
|
|
@ -316,7 +316,9 @@ class LibraryRadio(RelatedObjectRadio):
|
||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self, **kwargs):
|
||||||
qs = super().get_queryset(**kwargs)
|
qs = super().get_queryset(**kwargs)
|
||||||
actor_uploads = Upload.objects.filter(library=self.session.related_object,)
|
actor_uploads = Upload.objects.filter(
|
||||||
|
library=self.session.related_object,
|
||||||
|
)
|
||||||
return qs.filter(pk__in=actor_uploads.values("track"))
|
return qs.filter(pk__in=actor_uploads.values("track"))
|
||||||
|
|
||||||
def get_related_object_id_repr(self, obj):
|
def get_related_object_id_repr(self, obj):
|
||||||
|
|
|
@ -27,7 +27,9 @@ def generate_scoped_token(user_id, user_secret, scopes):
|
||||||
def authenticate_scoped_token(token):
|
def authenticate_scoped_token(token):
|
||||||
try:
|
try:
|
||||||
payload = signing.loads(
|
payload = signing.loads(
|
||||||
token, salt="scoped_tokens", max_age=settings.SCOPED_TOKENS_MAX_AGE,
|
token,
|
||||||
|
salt="scoped_tokens",
|
||||||
|
max_age=settings.SCOPED_TOKENS_MAX_AGE,
|
||||||
)
|
)
|
||||||
except signing.BadSignature:
|
except signing.BadSignature:
|
||||||
raise exceptions.AuthenticationFailed("Invalid token signature")
|
raise exceptions.AuthenticationFailed("Invalid token signature")
|
||||||
|
|
|
@ -52,7 +52,8 @@ def test_channel_create(logged_in_api_client):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"field", ["uuid", "actor.preferred_username", "actor.full_username"],
|
"field",
|
||||||
|
["uuid", "actor.preferred_username", "actor.full_username"],
|
||||||
)
|
)
|
||||||
def test_channel_detail(field, factories, logged_in_api_client):
|
def test_channel_detail(field, factories, logged_in_api_client):
|
||||||
channel = factories["audio.Channel"](
|
channel = factories["audio.Channel"](
|
||||||
|
@ -423,7 +424,10 @@ def test_subscribe_to_rss_creates_channel(factories, logged_in_api_client, mocke
|
||||||
|
|
||||||
|
|
||||||
def test_refresh_channel_when_param_is_true(
|
def test_refresh_channel_when_param_is_true(
|
||||||
factories, mocker, logged_in_api_client, queryset_equal_queries,
|
factories,
|
||||||
|
mocker,
|
||||||
|
logged_in_api_client,
|
||||||
|
queryset_equal_queries,
|
||||||
):
|
):
|
||||||
obj = factories["audio.Channel"]()
|
obj = factories["audio.Channel"]()
|
||||||
refetch_obj = mocker.patch(
|
refetch_obj = mocker.patch(
|
||||||
|
|
|
@ -57,7 +57,12 @@ from funkwhale_api.cli import users
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
("users", "rm"),
|
("users", "rm"),
|
||||||
("testuser1", "testuser2", "--no-input", "--hard",),
|
(
|
||||||
|
"testuser1",
|
||||||
|
"testuser2",
|
||||||
|
"--no-input",
|
||||||
|
"--hard",
|
||||||
|
),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
users,
|
users,
|
||||||
|
|
|
@ -301,13 +301,17 @@ def test_rewrite_manifest_json_url(link, new_url, expected, mocker, settings):
|
||||||
request = mocker.Mock(path="/", META={})
|
request = mocker.Mock(path="/", META={})
|
||||||
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
||||||
mocker.patch.object(
|
mocker.patch.object(
|
||||||
middleware, "get_default_head_tags", return_value=[],
|
middleware,
|
||||||
|
"get_default_head_tags",
|
||||||
|
return_value=[],
|
||||||
)
|
)
|
||||||
response = middleware.serve_spa(request)
|
response = middleware.serve_spa(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
expected_html = "<html><head><link rel=before>{}<link rel=after>\n\n</head></html>".format(
|
expected_html = (
|
||||||
expected
|
"<html><head><link rel=before>{}<link rel=after>\n\n</head></html>".format(
|
||||||
|
expected
|
||||||
|
)
|
||||||
)
|
)
|
||||||
assert response.content == expected_html.encode()
|
assert response.content == expected_html.encode()
|
||||||
|
|
||||||
|
@ -319,7 +323,9 @@ def test_rewrite_manifest_json_url_rewrite_disabled(mocker, settings):
|
||||||
request = mocker.Mock(path="/", META={})
|
request = mocker.Mock(path="/", META={})
|
||||||
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
||||||
mocker.patch.object(
|
mocker.patch.object(
|
||||||
middleware, "get_default_head_tags", return_value=[],
|
middleware,
|
||||||
|
"get_default_head_tags",
|
||||||
|
return_value=[],
|
||||||
)
|
)
|
||||||
response = middleware.serve_spa(request)
|
response = middleware.serve_spa(request)
|
||||||
|
|
||||||
|
@ -338,13 +344,17 @@ def test_rewrite_manifest_json_url_rewrite_default_url(mocker, settings):
|
||||||
request = mocker.Mock(path="/", META={})
|
request = mocker.Mock(path="/", META={})
|
||||||
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
||||||
mocker.patch.object(
|
mocker.patch.object(
|
||||||
middleware, "get_default_head_tags", return_value=[],
|
middleware,
|
||||||
|
"get_default_head_tags",
|
||||||
|
return_value=[],
|
||||||
)
|
)
|
||||||
response = middleware.serve_spa(request)
|
response = middleware.serve_spa(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
expected_html = '<html><head><link rel=manifest href="{}">\n\n</head></html>'.format(
|
expected_html = (
|
||||||
expected_url
|
'<html><head><link rel=manifest href="{}">\n\n</head></html>'.format(
|
||||||
|
expected_url
|
||||||
|
)
|
||||||
)
|
)
|
||||||
assert response.content == expected_html.encode()
|
assert response.content == expected_html.encode()
|
||||||
|
|
||||||
|
@ -410,10 +420,34 @@ def test_get_request_head_tags_calls_view_with_proper_arg_when_accept_header_set
|
||||||
"username",
|
"username",
|
||||||
"actor.preferred_username",
|
"actor.preferred_username",
|
||||||
),
|
),
|
||||||
("music.Artist", {}, "library_artist", "pk", "pk",),
|
(
|
||||||
("music.Album", {}, "library_album", "pk", "pk",),
|
"music.Artist",
|
||||||
("music.Track", {}, "library_track", "pk", "pk",),
|
{},
|
||||||
("music.Library", {}, "library_library", "uuid", "uuid",),
|
"library_artist",
|
||||||
|
"pk",
|
||||||
|
"pk",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"music.Album",
|
||||||
|
{},
|
||||||
|
"library_album",
|
||||||
|
"pk",
|
||||||
|
"pk",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"music.Track",
|
||||||
|
{},
|
||||||
|
"library_track",
|
||||||
|
"pk",
|
||||||
|
"pk",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"music.Library",
|
||||||
|
{},
|
||||||
|
"library_library",
|
||||||
|
"uuid",
|
||||||
|
"uuid",
|
||||||
|
),
|
||||||
# when a track as a public upload, we should redirect to the upload instead
|
# when a track as a public upload, we should redirect to the upload instead
|
||||||
("music.Upload", {"playable": True}, "library_track", "pk", "track.pk"),
|
("music.Upload", {"playable": True}, "library_track", "pk", "track.pk"),
|
||||||
],
|
],
|
||||||
|
|
|
@ -257,7 +257,9 @@ def test_update_library_follow_approved_create_entries(
|
||||||
assert list(music_models.Track.objects.playable_by(actor)) == expected_tracks
|
assert list(music_models.Track.objects.playable_by(actor)) == expected_tracks
|
||||||
|
|
||||||
|
|
||||||
def test_update_library_follow_delete_delete_denormalization_entries(factories,):
|
def test_update_library_follow_delete_delete_denormalization_entries(
|
||||||
|
factories,
|
||||||
|
):
|
||||||
updated_playable_tracks = {"owner": [0], "follower": []}
|
updated_playable_tracks = {"owner": [0], "follower": []}
|
||||||
actors = {
|
actors = {
|
||||||
"owner": factories["federation.Actor"](local=True),
|
"owner": factories["federation.Actor"](local=True),
|
||||||
|
|
|
@ -354,7 +354,10 @@ def test_inbox_create_audio_channel(factories, mocker):
|
||||||
activity = factories["federation.Activity"]()
|
activity = factories["federation.Activity"]()
|
||||||
channel = factories["audio.Channel"]()
|
channel = factories["audio.Channel"]()
|
||||||
album = factories["music.Album"](artist=channel.artist)
|
album = factories["music.Album"](artist=channel.artist)
|
||||||
upload = factories["music.Upload"](track__album=album, library=channel.library,)
|
upload = factories["music.Upload"](
|
||||||
|
track__album=album,
|
||||||
|
library=channel.library,
|
||||||
|
)
|
||||||
payload = {
|
payload = {
|
||||||
"@context": jsonld.get_default_context(),
|
"@context": jsonld.get_default_context(),
|
||||||
"type": "Create",
|
"type": "Create",
|
||||||
|
|
|
@ -805,7 +805,9 @@ def test_activity_pub_album_serializer_to_ap(factories):
|
||||||
|
|
||||||
def test_activity_pub_album_serializer_to_ap_channel_artist(factories):
|
def test_activity_pub_album_serializer_to_ap_channel_artist(factories):
|
||||||
channel = factories["audio.Channel"]()
|
channel = factories["audio.Channel"]()
|
||||||
album = factories["music.Album"](artist=channel.artist,)
|
album = factories["music.Album"](
|
||||||
|
artist=channel.artist,
|
||||||
|
)
|
||||||
|
|
||||||
serializer = serializers.AlbumSerializer(album)
|
serializer = serializers.AlbumSerializer(album)
|
||||||
|
|
||||||
|
|
|
@ -530,7 +530,9 @@ def test_fetch_channel_actor_returns_channel_and_fetch_outbox(
|
||||||
assert fetch.status == "finished"
|
assert fetch.status == "finished"
|
||||||
assert fetch.object == obj
|
assert fetch.object == obj
|
||||||
fetch_collection.assert_called_once_with(
|
fetch_collection.assert_called_once_with(
|
||||||
obj.actor.outbox_url, channel_id=obj.pk, max_pages=1,
|
obj.actor.outbox_url,
|
||||||
|
channel_id=obj.pk,
|
||||||
|
max_pages=1,
|
||||||
)
|
)
|
||||||
fetch_collection_delayed.assert_called_once_with(
|
fetch_collection_delayed.assert_called_once_with(
|
||||||
"http://outbox.url/page2",
|
"http://outbox.url/page2",
|
||||||
|
@ -655,7 +657,10 @@ def test_fetch_collection(mocker, r_mock):
|
||||||
r_mock.get(payloads["outbox"]["id"], json=payloads["outbox"])
|
r_mock.get(payloads["outbox"]["id"], json=payloads["outbox"])
|
||||||
r_mock.get(payloads["outbox"]["first"], json=payloads["page1"])
|
r_mock.get(payloads["outbox"]["first"], json=payloads["page1"])
|
||||||
r_mock.get(payloads["page1"]["next"], json=payloads["page2"])
|
r_mock.get(payloads["page1"]["next"], json=payloads["page2"])
|
||||||
result = tasks.fetch_collection(payloads["outbox"]["id"], max_pages=2,)
|
result = tasks.fetch_collection(
|
||||||
|
payloads["outbox"]["id"],
|
||||||
|
max_pages=2,
|
||||||
|
)
|
||||||
assert result["items"] == [
|
assert result["items"] == [
|
||||||
payloads["page1"]["orderedItems"][2],
|
payloads["page1"]["orderedItems"][2],
|
||||||
payloads["page2"]["orderedItems"][1],
|
payloads["page2"]["orderedItems"][1],
|
||||||
|
|
|
@ -296,7 +296,8 @@ def test_reel2bits_upload_delete(factories):
|
||||||
}
|
}
|
||||||
|
|
||||||
routes.inbox_delete(
|
routes.inbox_delete(
|
||||||
payload, context={"actor": actor, "raise_exception": True, "activity": payload},
|
payload,
|
||||||
|
context={"actor": actor, "raise_exception": True, "activity": payload},
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(upload.track.DoesNotExist):
|
with pytest.raises(upload.track.DoesNotExist):
|
||||||
|
|
|
@ -31,7 +31,10 @@ def test_authenticate_skips_anonymous_fetch_when_allow_list_enabled(
|
||||||
):
|
):
|
||||||
preferences["moderation__allow_list_enabled"] = True
|
preferences["moderation__allow_list_enabled"] = True
|
||||||
library = factories["music.Library"]()
|
library = factories["music.Library"]()
|
||||||
url = reverse("federation:music:libraries-detail", kwargs={"uuid": library.uuid},)
|
url = reverse(
|
||||||
|
"federation:music:libraries-detail",
|
||||||
|
kwargs={"uuid": library.uuid},
|
||||||
|
)
|
||||||
response = api_client.get(url)
|
response = api_client.get(url)
|
||||||
|
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
@ -470,7 +473,10 @@ def test_upload_retrieve_redirects_to_html_if_header_set(
|
||||||
):
|
):
|
||||||
upload = factories["music.Upload"](library__local=True, playable=True)
|
upload = factories["music.Upload"](library__local=True, playable=True)
|
||||||
|
|
||||||
url = reverse("federation:music:uploads-detail", kwargs={"uuid": upload.uuid},)
|
url = reverse(
|
||||||
|
"federation:music:uploads-detail",
|
||||||
|
kwargs={"uuid": upload.uuid},
|
||||||
|
)
|
||||||
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
||||||
expected_url = utils.join_url(
|
expected_url = utils.join_url(
|
||||||
settings.FUNKWHALE_URL,
|
settings.FUNKWHALE_URL,
|
||||||
|
@ -485,7 +491,10 @@ def test_track_retrieve_redirects_to_html_if_header_set(
|
||||||
):
|
):
|
||||||
track = factories["music.Track"](local=True)
|
track = factories["music.Track"](local=True)
|
||||||
|
|
||||||
url = reverse("federation:music:tracks-detail", kwargs={"uuid": track.uuid},)
|
url = reverse(
|
||||||
|
"federation:music:tracks-detail",
|
||||||
|
kwargs={"uuid": track.uuid},
|
||||||
|
)
|
||||||
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
||||||
expected_url = utils.join_url(
|
expected_url = utils.join_url(
|
||||||
settings.FUNKWHALE_URL,
|
settings.FUNKWHALE_URL,
|
||||||
|
@ -500,7 +509,10 @@ def test_album_retrieve_redirects_to_html_if_header_set(
|
||||||
):
|
):
|
||||||
album = factories["music.Album"](local=True)
|
album = factories["music.Album"](local=True)
|
||||||
|
|
||||||
url = reverse("federation:music:albums-detail", kwargs={"uuid": album.uuid},)
|
url = reverse(
|
||||||
|
"federation:music:albums-detail",
|
||||||
|
kwargs={"uuid": album.uuid},
|
||||||
|
)
|
||||||
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
||||||
expected_url = utils.join_url(
|
expected_url = utils.join_url(
|
||||||
settings.FUNKWHALE_URL,
|
settings.FUNKWHALE_URL,
|
||||||
|
@ -515,7 +527,10 @@ def test_artist_retrieve_redirects_to_html_if_header_set(
|
||||||
):
|
):
|
||||||
artist = factories["music.Artist"](local=True)
|
artist = factories["music.Artist"](local=True)
|
||||||
|
|
||||||
url = reverse("federation:music:artists-detail", kwargs={"uuid": artist.uuid},)
|
url = reverse(
|
||||||
|
"federation:music:artists-detail",
|
||||||
|
kwargs={"uuid": artist.uuid},
|
||||||
|
)
|
||||||
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
response = api_client.get(url, HTTP_ACCEPT="text/html")
|
||||||
expected_url = utils.join_url(
|
expected_url = utils.join_url(
|
||||||
settings.FUNKWHALE_URL,
|
settings.FUNKWHALE_URL,
|
||||||
|
@ -548,7 +563,9 @@ def test_index_channels_retrieve(factories, api_client):
|
||||||
},
|
},
|
||||||
).data
|
).data
|
||||||
|
|
||||||
url = reverse("federation:index:index-channels",)
|
url = reverse(
|
||||||
|
"federation:index:index-channels",
|
||||||
|
)
|
||||||
response = api_client.get(url)
|
response = api_client.get(url)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -66,7 +66,8 @@ def test_signup_request_pending_sends_email_to_mods(factories, mailoutbox, setti
|
||||||
for i, mod in enumerate([mod1, mod2]):
|
for i, mod in enumerate([mod1, mod2]):
|
||||||
m = mailoutbox[i]
|
m = mailoutbox[i]
|
||||||
assert m.subject == "[{} moderation] New sign-up request from {}".format(
|
assert m.subject == "[{} moderation] New sign-up request from {}".format(
|
||||||
settings.FUNKWHALE_HOSTNAME, signup_request.submitter.preferred_username,
|
settings.FUNKWHALE_HOSTNAME,
|
||||||
|
signup_request.submitter.preferred_username,
|
||||||
)
|
)
|
||||||
assert detail_url in m.body
|
assert detail_url in m.body
|
||||||
assert unresolved_requests_url in m.body
|
assert unresolved_requests_url in m.body
|
||||||
|
@ -91,7 +92,8 @@ def test_approved_request_sends_email_to_submitter_and_set_active(
|
||||||
m = mailoutbox[-1]
|
m = mailoutbox[-1]
|
||||||
login_url = federation_utils.full_url("/login")
|
login_url = federation_utils.full_url("/login")
|
||||||
assert m.subject == "Welcome to {}, {}!".format(
|
assert m.subject == "Welcome to {}, {}!".format(
|
||||||
settings.FUNKWHALE_HOSTNAME, signup_request.submitter.preferred_username,
|
settings.FUNKWHALE_HOSTNAME,
|
||||||
|
signup_request.submitter.preferred_username,
|
||||||
)
|
)
|
||||||
assert login_url in m.body
|
assert login_url in m.body
|
||||||
assert list(m.to) == [user.email]
|
assert list(m.to) == [user.email]
|
||||||
|
|
|
@ -213,7 +213,8 @@ def test_mutation_set_attachment_cover(factory_name, factories, now, mocker):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"factory_name", ["music.Track", "music.Album", "music.Artist"],
|
"factory_name",
|
||||||
|
["music.Track", "music.Album", "music.Artist"],
|
||||||
)
|
)
|
||||||
def test_album_mutation_description(factory_name, factories, mocker):
|
def test_album_mutation_description(factory_name, factories, mocker):
|
||||||
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||||
|
@ -236,7 +237,8 @@ def test_album_mutation_description(factory_name, factories, mocker):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"factory_name", ["music.Track", "music.Album", "music.Artist"],
|
"factory_name",
|
||||||
|
["music.Track", "music.Album", "music.Artist"],
|
||||||
)
|
)
|
||||||
def test_mutation_description_keep_tags(factory_name, factories, mocker):
|
def test_mutation_description_keep_tags(factory_name, factories, mocker):
|
||||||
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||||
|
@ -256,14 +258,17 @@ def test_mutation_description_keep_tags(factory_name, factories, mocker):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"factory_name", ["music.Track", "music.Album", "music.Artist"],
|
"factory_name",
|
||||||
|
["music.Track", "music.Album", "music.Artist"],
|
||||||
)
|
)
|
||||||
def test_mutation_tags_keep_descriptions(factory_name, factories, mocker):
|
def test_mutation_tags_keep_descriptions(factory_name, factories, mocker):
|
||||||
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||||
content = factories["common.Content"]()
|
content = factories["common.Content"]()
|
||||||
obj = factories[factory_name](description=content)
|
obj = factories[factory_name](description=content)
|
||||||
mutation = factories["common.Mutation"](
|
mutation = factories["common.Mutation"](
|
||||||
type="update", target=obj, payload={"tags": ["punk", "rock"]},
|
type="update",
|
||||||
|
target=obj,
|
||||||
|
payload={"tags": ["punk", "rock"]},
|
||||||
)
|
)
|
||||||
mutation.apply()
|
mutation.apply()
|
||||||
obj.refresh_from_db()
|
obj.refresh_from_db()
|
||||||
|
|
|
@ -427,7 +427,8 @@ def test_upload_with_channel(factories, uploaded_audio_file):
|
||||||
"import_status": "draft",
|
"import_status": "draft",
|
||||||
}
|
}
|
||||||
serializer = serializers.UploadForOwnerSerializer(
|
serializer = serializers.UploadForOwnerSerializer(
|
||||||
data=data, context={"user": user},
|
data=data,
|
||||||
|
context={"user": user},
|
||||||
)
|
)
|
||||||
assert serializer.is_valid(raise_exception=True) is True
|
assert serializer.is_valid(raise_exception=True) is True
|
||||||
upload = serializer.save()
|
upload = serializer.save()
|
||||||
|
@ -443,7 +444,8 @@ def test_upload_with_not_owned_channel_fails(factories, uploaded_audio_file):
|
||||||
"audio_file": uploaded_audio_file,
|
"audio_file": uploaded_audio_file,
|
||||||
}
|
}
|
||||||
serializer = serializers.UploadForOwnerSerializer(
|
serializer = serializers.UploadForOwnerSerializer(
|
||||||
data=data, context={"user": user},
|
data=data,
|
||||||
|
context={"user": user},
|
||||||
)
|
)
|
||||||
assert serializer.is_valid() is False
|
assert serializer.is_valid() is False
|
||||||
assert "channel" in serializer.errors
|
assert "channel" in serializer.errors
|
||||||
|
@ -457,7 +459,8 @@ def test_upload_with_not_owned_library_fails(factories, uploaded_audio_file):
|
||||||
"audio_file": uploaded_audio_file,
|
"audio_file": uploaded_audio_file,
|
||||||
}
|
}
|
||||||
serializer = serializers.UploadForOwnerSerializer(
|
serializer = serializers.UploadForOwnerSerializer(
|
||||||
data=data, context={"user": user},
|
data=data,
|
||||||
|
context={"user": user},
|
||||||
)
|
)
|
||||||
assert serializer.is_valid() is False
|
assert serializer.is_valid() is False
|
||||||
assert "library" in serializer.errors
|
assert "library" in serializer.errors
|
||||||
|
@ -469,7 +472,8 @@ def test_upload_requires_library_or_channel(factories, uploaded_audio_file):
|
||||||
"audio_file": uploaded_audio_file,
|
"audio_file": uploaded_audio_file,
|
||||||
}
|
}
|
||||||
serializer = serializers.UploadForOwnerSerializer(
|
serializer = serializers.UploadForOwnerSerializer(
|
||||||
data=data, context={"user": user},
|
data=data,
|
||||||
|
context={"user": user},
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
|
@ -491,7 +495,8 @@ def test_upload_requires_library_or_channel_but_not_both(
|
||||||
"channel": channel.uuid,
|
"channel": channel.uuid,
|
||||||
}
|
}
|
||||||
serializer = serializers.UploadForOwnerSerializer(
|
serializer = serializers.UploadForOwnerSerializer(
|
||||||
data=data, context={"user": user},
|
data=data,
|
||||||
|
context={"user": user},
|
||||||
)
|
)
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
serializers.serializers.ValidationError,
|
serializers.serializers.ValidationError,
|
||||||
|
@ -547,7 +552,8 @@ def test_upload_with_channel_keeps_import_metadata(factories, uploaded_audio_fil
|
||||||
"import_metadata": {"title": "hello"},
|
"import_metadata": {"title": "hello"},
|
||||||
}
|
}
|
||||||
serializer = serializers.UploadForOwnerSerializer(
|
serializer = serializers.UploadForOwnerSerializer(
|
||||||
data=data, context={"user": user},
|
data=data,
|
||||||
|
context={"user": user},
|
||||||
)
|
)
|
||||||
assert serializer.is_valid(raise_exception=True) is True
|
assert serializer.is_valid(raise_exception=True) is True
|
||||||
upload = serializer.save()
|
upload = serializer.save()
|
||||||
|
@ -564,7 +570,8 @@ def test_upload_with_channel_validates_import_metadata(factories, uploaded_audio
|
||||||
"import_metadata": {"title": None},
|
"import_metadata": {"title": None},
|
||||||
}
|
}
|
||||||
serializer = serializers.UploadForOwnerSerializer(
|
serializer = serializers.UploadForOwnerSerializer(
|
||||||
data=data, context={"user": user},
|
data=data,
|
||||||
|
context={"user": user},
|
||||||
)
|
)
|
||||||
with pytest.raises(serializers.serializers.ValidationError):
|
with pytest.raises(serializers.serializers.ValidationError):
|
||||||
assert serializer.is_valid(raise_exception=True)
|
assert serializer.is_valid(raise_exception=True)
|
||||||
|
|
|
@ -1082,7 +1082,9 @@ def test_process_channel_upload_forces_artist_and_attributed_to(
|
||||||
upload.refresh_from_db()
|
upload.refresh_from_db()
|
||||||
|
|
||||||
expected_final_metadata = tasks.collections.ChainMap(
|
expected_final_metadata = tasks.collections.ChainMap(
|
||||||
{"upload_source": None}, expected_forced_values, {"funkwhale": {}},
|
{"upload_source": None},
|
||||||
|
expected_forced_values,
|
||||||
|
{"funkwhale": {}},
|
||||||
)
|
)
|
||||||
assert upload.import_status == "finished"
|
assert upload.import_status == "finished"
|
||||||
get_track_from_import_metadata.assert_called_once_with(
|
get_track_from_import_metadata.assert_called_once_with(
|
||||||
|
@ -1175,7 +1177,8 @@ def test_tag_albums_from_tracks(queryset_equal_queries, factories, mocker):
|
||||||
)
|
)
|
||||||
|
|
||||||
add_tags_batch.assert_called_once_with(
|
add_tags_batch.assert_called_once_with(
|
||||||
get_tags_from_foreign_key.return_value, model=models.Album,
|
get_tags_from_foreign_key.return_value,
|
||||||
|
model=models.Album,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1200,7 +1203,8 @@ def test_tag_artists_from_tracks(queryset_equal_queries, factories, mocker):
|
||||||
)
|
)
|
||||||
|
|
||||||
add_tags_batch.assert_called_once_with(
|
add_tags_batch.assert_called_once_with(
|
||||||
get_tags_from_foreign_key.return_value, model=models.Artist,
|
get_tags_from_foreign_key.return_value,
|
||||||
|
model=models.Artist,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ def test_playlist_inherits_user_privacy(logged_in_api_client):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"name,method", [("api:v1:playlists-list", "post")],
|
"name,method",
|
||||||
|
[("api:v1:playlists-list", "post")],
|
||||||
)
|
)
|
||||||
def test_url_requires_login(name, method, factories, api_client):
|
def test_url_requires_login(name, method, factories, api_client):
|
||||||
url = reverse(name)
|
url = reverse(name)
|
||||||
|
|
|
@ -137,8 +137,12 @@ def test_get_confs_user(factories):
|
||||||
|
|
||||||
|
|
||||||
def test_filter_is_called_with_plugin_conf(mocker, factories):
|
def test_filter_is_called_with_plugin_conf(mocker, factories):
|
||||||
plugins.get_plugin_config("test1",)
|
plugins.get_plugin_config(
|
||||||
plugins.get_plugin_config("test2",)
|
"test1",
|
||||||
|
)
|
||||||
|
plugins.get_plugin_config(
|
||||||
|
"test2",
|
||||||
|
)
|
||||||
factories["common.PluginConfiguration"](code="test1", enabled=True)
|
factories["common.PluginConfiguration"](code="test1", enabled=True)
|
||||||
factories["common.PluginConfiguration"](
|
factories["common.PluginConfiguration"](
|
||||||
code="test2", conf={"foo": "baz"}, enabled=True
|
code="test2", conf={"foo": "baz"}, enabled=True
|
||||||
|
@ -411,7 +415,10 @@ def test_set_plugin_source_conf_valid(factories):
|
||||||
def test_can_trigger_scan(logged_in_api_client, mocker, factories):
|
def test_can_trigger_scan(logged_in_api_client, mocker, factories):
|
||||||
library = factories["music.Library"](actor=logged_in_api_client.user.create_actor())
|
library = factories["music.Library"](actor=logged_in_api_client.user.create_actor())
|
||||||
plugin = plugins.get_plugin_config(
|
plugin = plugins.get_plugin_config(
|
||||||
name="test_plugin", description="Hello world", conf=[], source=True,
|
name="test_plugin",
|
||||||
|
description="Hello world",
|
||||||
|
conf=[],
|
||||||
|
source=True,
|
||||||
)
|
)
|
||||||
handler = mocker.Mock()
|
handler = mocker.Mock()
|
||||||
plugins.register_hook(plugins.SCAN, plugin)(handler)
|
plugins.register_hook(plugins.SCAN, plugin)(handler)
|
||||||
|
|
|
@ -29,7 +29,8 @@ def test_add_tags_batch(factories):
|
||||||
data = {artist.pk: [rock_tag.pk, rap_tag.pk]}
|
data = {artist.pk: [rock_tag.pk, rap_tag.pk]}
|
||||||
|
|
||||||
tasks.add_tags_batch(
|
tasks.add_tags_batch(
|
||||||
data, model=artist.__class__,
|
data,
|
||||||
|
model=artist.__class__,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert artist.get_tags() == ["Rap", "Rock"]
|
assert artist.get_tags() == ["Rap", "Rock"]
|
||||||
|
|
|
@ -297,7 +297,10 @@ def test_handle_modified_skips_existing_checksum(tmpfile, factories, mocker):
|
||||||
import_status="finished",
|
import_status="finished",
|
||||||
)
|
)
|
||||||
import_files.handle_modified(
|
import_files.handle_modified(
|
||||||
event=event, stdout=stdout, library=library, in_place=True,
|
event=event,
|
||||||
|
stdout=stdout,
|
||||||
|
library=library,
|
||||||
|
in_place=True,
|
||||||
)
|
)
|
||||||
assert library.uploads.count() == 1
|
assert library.uploads.count() == 1
|
||||||
|
|
||||||
|
@ -322,10 +325,14 @@ def test_handle_modified_update_existing_path_if_found(tmpfile, factories, mocke
|
||||||
audio_file=None,
|
audio_file=None,
|
||||||
)
|
)
|
||||||
import_files.handle_modified(
|
import_files.handle_modified(
|
||||||
event=event, stdout=stdout, library=library, in_place=True,
|
event=event,
|
||||||
|
stdout=stdout,
|
||||||
|
library=library,
|
||||||
|
in_place=True,
|
||||||
)
|
)
|
||||||
update_track_metadata.assert_called_once_with(
|
update_track_metadata.assert_called_once_with(
|
||||||
get_metadata.return_value, upload.track,
|
get_metadata.return_value,
|
||||||
|
upload.track,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -349,7 +356,10 @@ def test_handle_modified_update_existing_path_if_found_and_attributed_to(
|
||||||
audio_file=None,
|
audio_file=None,
|
||||||
)
|
)
|
||||||
import_files.handle_modified(
|
import_files.handle_modified(
|
||||||
event=event, stdout=stdout, library=library, in_place=True,
|
event=event,
|
||||||
|
stdout=stdout,
|
||||||
|
library=library,
|
||||||
|
in_place=True,
|
||||||
)
|
)
|
||||||
update_track_metadata.assert_not_called()
|
update_track_metadata.assert_not_called()
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,9 @@ def test_generate_scoped_token(mocker):
|
||||||
dumps = mocker.patch("django.core.signing.dumps")
|
dumps = mocker.patch("django.core.signing.dumps")
|
||||||
|
|
||||||
result = authentication.generate_scoped_token(
|
result = authentication.generate_scoped_token(
|
||||||
user_id=42, user_secret="hello", scopes=["read"],
|
user_id=42,
|
||||||
|
user_secret="hello",
|
||||||
|
scopes=["read"],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result == dumps.return_value
|
assert result == dumps.return_value
|
||||||
|
|
18
docs/conf.py
18
docs/conf.py
|
@ -47,7 +47,13 @@ for key, value in FUNKWHALE_CONFIG.items():
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
extensions = ["sphinx.ext.graphviz", "sphinx.ext.autodoc"]
|
extensions = ["sphinx.ext.graphviz", "sphinx.ext.autodoc"]
|
||||||
autodoc_mock_imports = ["celery", "django_auth_ldap", "ldap", "persisting_theory", "rest_framework"]
|
autodoc_mock_imports = [
|
||||||
|
"celery",
|
||||||
|
"django_auth_ldap",
|
||||||
|
"ldap",
|
||||||
|
"persisting_theory",
|
||||||
|
"rest_framework",
|
||||||
|
]
|
||||||
add_module_names = False
|
add_module_names = False
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ["_templates"]
|
templates_path = ["_templates"]
|
||||||
|
@ -107,7 +113,7 @@ html_theme = "sphinx_rtd_theme"
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#
|
#
|
||||||
#html_theme_options = {}
|
# html_theme_options = {}
|
||||||
html_context = {
|
html_context = {
|
||||||
"display_gitlab": True,
|
"display_gitlab": True,
|
||||||
"gitlab_host": "dev.funkwhale.audio",
|
"gitlab_host": "dev.funkwhale.audio",
|
||||||
|
@ -152,7 +158,13 @@ latex_elements = {
|
||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
(root_doc, "funkwhale.tex", "funkwhale Documentation", "The Funkwhale Collective", "manual")
|
(
|
||||||
|
root_doc,
|
||||||
|
"funkwhale.tex",
|
||||||
|
"funkwhale Documentation",
|
||||||
|
"The Funkwhale Collective",
|
||||||
|
"manual",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ def discard_unused_icons(rule):
|
||||||
".gitlab",
|
".gitlab",
|
||||||
".chevron",
|
".chevron",
|
||||||
".right",
|
".right",
|
||||||
".left"
|
".left",
|
||||||
]
|
]
|
||||||
if ":before" not in rule["lines"][0]:
|
if ":before" not in rule["lines"][0]:
|
||||||
return False
|
return False
|
||||||
|
@ -355,7 +355,9 @@ REPLACEMENTS = {
|
||||||
".ui.checkbox input:focus ~ label",
|
".ui.checkbox input:focus ~ label",
|
||||||
".ui.toggle.checkbox input:focus:checked ~ label",
|
".ui.toggle.checkbox input:focus:checked ~ label",
|
||||||
".ui.checkbox input:active ~ label",
|
".ui.checkbox input:active ~ label",
|
||||||
): [("color", "var(--form-label-color)"),],
|
): [
|
||||||
|
("color", "var(--form-label-color)"),
|
||||||
|
],
|
||||||
(".ui.toggle.checkbox label:before",): [
|
(".ui.toggle.checkbox label:before",): [
|
||||||
("background", "var(--input-background)"),
|
("background", "var(--input-background)"),
|
||||||
],
|
],
|
||||||
|
@ -365,7 +367,9 @@ REPLACEMENTS = {
|
||||||
("border-top", "var(--divider)"),
|
("border-top", "var(--divider)"),
|
||||||
("border-bottom", "var(--divider)"),
|
("border-bottom", "var(--divider)"),
|
||||||
],
|
],
|
||||||
(".ui.divider",): [("color", "var(--text-color)"),],
|
(".ui.divider",): [
|
||||||
|
("color", "var(--text-color)"),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"dimmer": {
|
"dimmer": {
|
||||||
(".ui.inverted.dimmer",): [
|
(".ui.inverted.dimmer",): [
|
||||||
|
@ -374,7 +378,12 @@ REPLACEMENTS = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"dropdown": {
|
"dropdown": {
|
||||||
"skip": [".error", ".info", ".success", ".warning",],
|
"skip": [
|
||||||
|
".error",
|
||||||
|
".info",
|
||||||
|
".success",
|
||||||
|
".warning",
|
||||||
|
],
|
||||||
(
|
(
|
||||||
".ui.selection.dropdown",
|
".ui.selection.dropdown",
|
||||||
".ui.selection.visible.dropdown > .text:not(.default)",
|
".ui.selection.visible.dropdown > .text:not(.default)",
|
||||||
|
@ -383,7 +392,9 @@ REPLACEMENTS = {
|
||||||
("background", "var(--dropdown-background)"),
|
("background", "var(--dropdown-background)"),
|
||||||
("color", "var(--dropdown-color)"),
|
("color", "var(--dropdown-color)"),
|
||||||
],
|
],
|
||||||
(".ui.dropdown .menu > .item",): [("color", "var(--dropdown-item-color)"),],
|
(".ui.dropdown .menu > .item",): [
|
||||||
|
("color", "var(--dropdown-item-color)"),
|
||||||
|
],
|
||||||
(".ui.dropdown .menu > .item:hover",): [
|
(".ui.dropdown .menu > .item:hover",): [
|
||||||
("color", "var(--dropdown-item-hover-color)"),
|
("color", "var(--dropdown-item-hover-color)"),
|
||||||
("background", "var(--dropdown-item-hover-background)"),
|
("background", "var(--dropdown-item-hover-background)"),
|
||||||
|
@ -395,10 +406,18 @@ REPLACEMENTS = {
|
||||||
(".ui.dropdown .menu > .header:not(.ui)",): [
|
(".ui.dropdown .menu > .header:not(.ui)",): [
|
||||||
("color", "var(--dropdown-header-color)"),
|
("color", "var(--dropdown-header-color)"),
|
||||||
],
|
],
|
||||||
(".ui.dropdown .menu > .divider",): [("border-top", "var(--divider)"),],
|
(".ui.dropdown .menu > .divider",): [
|
||||||
|
("border-top", "var(--divider)"),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"skip": [".inverted", ".success", ".warning", ".error", ".info",],
|
"skip": [
|
||||||
|
".inverted",
|
||||||
|
".success",
|
||||||
|
".warning",
|
||||||
|
".error",
|
||||||
|
".info",
|
||||||
|
],
|
||||||
('.ui.form input[type="text"]', ".ui.form select", ".ui.input textarea"): [
|
('.ui.form input[type="text"]', ".ui.form select", ".ui.input textarea"): [
|
||||||
("background", "var(--input-background)"),
|
("background", "var(--input-background)"),
|
||||||
("color", "var(--input-color)"),
|
("color", "var(--input-color)"),
|
||||||
|
@ -415,12 +434,16 @@ REPLACEMENTS = {
|
||||||
".ui.form ::-webkit-input-placeholder",
|
".ui.form ::-webkit-input-placeholder",
|
||||||
".ui.form :-ms-input-placeholder",
|
".ui.form :-ms-input-placeholder",
|
||||||
".ui.form ::-moz-placeholder",
|
".ui.form ::-moz-placeholder",
|
||||||
): [("color", "var(--input-placeholder-color)"),],
|
): [
|
||||||
|
("color", "var(--input-placeholder-color)"),
|
||||||
|
],
|
||||||
(
|
(
|
||||||
".ui.form :focus::-webkit-input-placeholder",
|
".ui.form :focus::-webkit-input-placeholder",
|
||||||
".ui.form :focus:-ms-input-placeholder",
|
".ui.form :focus:-ms-input-placeholder",
|
||||||
".ui.form :focus::-moz-placeholder",
|
".ui.form :focus::-moz-placeholder",
|
||||||
): [("color", "var(--input-focus-placeholder-color)"),],
|
): [
|
||||||
|
("color", "var(--input-focus-placeholder-color)"),
|
||||||
|
],
|
||||||
(".ui.form .field > label", ".ui.form .inline.fields .field > label",): [
|
(".ui.form .field > label", ".ui.form .inline.fields .field > label",): [
|
||||||
("color", "var(--form-label-color)"),
|
("color", "var(--form-label-color)"),
|
||||||
],
|
],
|
||||||
|
@ -475,16 +498,24 @@ REPLACEMENTS = {
|
||||||
".ui.input > input::-webkit-input-placeholder",
|
".ui.input > input::-webkit-input-placeholder",
|
||||||
".ui.input > input::-moz-placeholder",
|
".ui.input > input::-moz-placeholder",
|
||||||
".ui.input > input:-ms-input-placeholder",
|
".ui.input > input:-ms-input-placeholder",
|
||||||
): [("color", "var(--input-placeholder-color)"),],
|
): [
|
||||||
|
("color", "var(--input-placeholder-color)"),
|
||||||
|
],
|
||||||
(
|
(
|
||||||
".ui.input > input:focus::-webkit-input-placeholder",
|
".ui.input > input:focus::-webkit-input-placeholder",
|
||||||
".ui.input > input:focus::-moz-placeholder",
|
".ui.input > input:focus::-moz-placeholder",
|
||||||
".ui.input > input:focus:-ms-input-placeholder",
|
".ui.input > input:focus:-ms-input-placeholder",
|
||||||
): [("color", "var(--input-focus-placeholder-color)"),],
|
): [
|
||||||
|
("color", "var(--input-focus-placeholder-color)"),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"item": {
|
"item": {
|
||||||
(".ui.divided.items > .item",): [("border-top", "var(--divider)"),],
|
(".ui.divided.items > .item",): [
|
||||||
(".ui.items > .item > .content",): [("color", "var(--text-color)"),],
|
("border-top", "var(--divider)"),
|
||||||
|
],
|
||||||
|
(".ui.items > .item > .content",): [
|
||||||
|
("color", "var(--text-color)"),
|
||||||
|
],
|
||||||
(".ui.items > .item .extra",): [
|
(".ui.items > .item .extra",): [
|
||||||
("color", "var(--really-discrete-text-color)"),
|
("color", "var(--really-discrete-text-color)"),
|
||||||
],
|
],
|
||||||
|
@ -503,8 +534,12 @@ REPLACEMENTS = {
|
||||||
".black",
|
".black",
|
||||||
".pink",
|
".pink",
|
||||||
],
|
],
|
||||||
(".ui.header",): [("color", "var(--header-color)"),],
|
(".ui.header",): [
|
||||||
(".ui.header .sub.header",): [("color", "var(--header-color)"),],
|
("color", "var(--header-color)"),
|
||||||
|
],
|
||||||
|
(".ui.header .sub.header",): [
|
||||||
|
("color", "var(--header-color)"),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"skip": [
|
"skip": [
|
||||||
|
@ -580,7 +615,9 @@ REPLACEMENTS = {
|
||||||
".danger",
|
".danger",
|
||||||
".elastic",
|
".elastic",
|
||||||
],
|
],
|
||||||
(".ui.inverted.dimmer > .ui.loader",): [("color", "var(--dimmer-color)"),],
|
(".ui.inverted.dimmer > .ui.loader",): [
|
||||||
|
("color", "var(--dimmer-color)"),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"skip": [
|
"skip": [
|
||||||
|
@ -620,7 +657,9 @@ REPLACEMENTS = {
|
||||||
".fitted",
|
".fitted",
|
||||||
"fixed",
|
"fixed",
|
||||||
],
|
],
|
||||||
(".ui.menu .item",): [("color", "var(--menu-item-color)"),],
|
(".ui.menu .item",): [
|
||||||
|
("color", "var(--menu-item-color)"),
|
||||||
|
],
|
||||||
(".ui.vertical.inverted.menu .menu .item", ".ui.inverted.menu .item"): [
|
(".ui.vertical.inverted.menu .menu .item", ".ui.inverted.menu .item"): [
|
||||||
("color", "var(--inverted-menu-item-color)"),
|
("color", "var(--inverted-menu-item-color)"),
|
||||||
],
|
],
|
||||||
|
@ -633,7 +672,9 @@ REPLACEMENTS = {
|
||||||
(
|
(
|
||||||
".ui.secondary.pointing.menu a.item:hover",
|
".ui.secondary.pointing.menu a.item:hover",
|
||||||
".ui.secondary.pointing.menu .active.item:hover",
|
".ui.secondary.pointing.menu .active.item:hover",
|
||||||
): [("color", "var(--secondary-menu-hover-item-color)"),],
|
): [
|
||||||
|
("color", "var(--secondary-menu-hover-item-color)"),
|
||||||
|
],
|
||||||
(".ui.menu .ui.dropdown .menu > .item",): [
|
(".ui.menu .ui.dropdown .menu > .item",): [
|
||||||
("color", "var(--dropdown-item-color) !important"),
|
("color", "var(--dropdown-item-color) !important"),
|
||||||
],
|
],
|
||||||
|
@ -656,7 +697,9 @@ REPLACEMENTS = {
|
||||||
("border-bottom", "var(--divider)"),
|
("border-bottom", "var(--divider)"),
|
||||||
("border-top", "var(--divider)"),
|
("border-top", "var(--divider)"),
|
||||||
],
|
],
|
||||||
(".ui.modal > .close.inside",): [("color", "var(--text-color)"),],
|
(".ui.modal > .close.inside",): [
|
||||||
|
("color", "var(--text-color)"),
|
||||||
|
],
|
||||||
(".ui.modal > .header",): [
|
(".ui.modal > .header",): [
|
||||||
("color", "var(--header-color)"),
|
("color", "var(--header-color)"),
|
||||||
("background", "var(--modal-background)"),
|
("background", "var(--modal-background)"),
|
||||||
|
@ -680,7 +723,9 @@ REPLACEMENTS = {
|
||||||
(
|
(
|
||||||
".ui.search > .results .result .title",
|
".ui.search > .results .result .title",
|
||||||
".ui.search > .results .result .description",
|
".ui.search > .results .result .description",
|
||||||
): [("color", "var(--dropdown-item-color)"),],
|
): [
|
||||||
|
("color", "var(--dropdown-item-color)"),
|
||||||
|
],
|
||||||
(".ui.search > .results .result:hover",): [
|
(".ui.search > .results .result:hover",): [
|
||||||
("color", "var(--dropdown-item-hover-color)"),
|
("color", "var(--dropdown-item-hover-color)"),
|
||||||
("background", "var(--dropdown-item-hover-background)"),
|
("background", "var(--dropdown-item-hover-background)"),
|
||||||
|
@ -696,7 +741,9 @@ REPLACEMENTS = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
(".ui.left.visible.sidebar",): [("box-shadow", "var(--sidebar-box-shadow)"),]
|
(".ui.left.visible.sidebar",): [
|
||||||
|
("box-shadow", "var(--sidebar-box-shadow)"),
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"statistic": {
|
"statistic": {
|
||||||
(".ui.statistic > .value", ".ui.statistic > .label"): [
|
(".ui.statistic > .value", ".ui.statistic > .label"): [
|
||||||
|
@ -704,7 +751,9 @@ REPLACEMENTS = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
(".ui.progress.success > .label",): [("color", "var(--text-color)"),],
|
(".ui.progress.success > .label",): [
|
||||||
|
("color", "var(--text-color)"),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"skip": [
|
"skip": [
|
||||||
|
|
|
@ -43,4 +43,4 @@
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
exclude = "(.git|.hg|.mypy_cache|.tox|.venv|_build|buck-out|build|dist|migrations)"
|
exclude = "(.git|.hg|.mypy_cache|.tox|.venv|_build|buck-out|build|dist|migrations|data)"
|
||||||
|
|
Loading…
Reference in New Issue