Update Black & run for whole repository

This commit is contained in:
Georg Krause 2022-01-09 11:40:49 +00:00 committed by JuniorJPDJ
parent 68210d5330
commit 850dc69091
53 changed files with 441 additions and 149 deletions

View File

@ -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

View File

@ -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/",

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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"""

View File

@ -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()

View File

@ -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)

View File

@ -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,
), ),
], ],
) )

View File

@ -26,7 +26,7 @@ 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:
@ -38,7 +38,7 @@ def normalize_query(
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.
""" """

View File

@ -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):

View File

@ -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",

View File

@ -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:

View File

@ -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"

View File

@ -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):

View File

@ -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

View File

@ -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(),

View File

@ -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(

View File

@ -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

View File

@ -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:

View File

@ -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 = (

View File

@ -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",

View File

@ -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()

View File

@ -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",

View File

@ -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:

View File

@ -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:

View File

@ -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(

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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")

View File

@ -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(

View File

@ -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,

View File

@ -301,14 +301,18 @@ 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 = (
"<html><head><link rel=before>{}<link rel=after>\n\n</head></html>".format(
expected 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,14 +344,18 @@ 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 = (
'<html><head><link rel=manifest href="{}">\n\n</head></html>'.format(
expected_url 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"),
], ],

View File

@ -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),

View File

@ -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",

View File

@ -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)

View File

@ -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],

View File

@ -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):

View File

@ -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

View File

@ -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]

View File

@ -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()

View File

@ -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)

View File

@ -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,
) )

View File

@ -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)

View File

@ -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)

View File

@ -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"]

View File

@ -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()

View File

@ -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

View File

@ -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",
)
] ]

View File

@ -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": [

View File

@ -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)"