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

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