Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 8 additions & 22 deletions accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from django.shortcuts import render
from django.urls import reverse
from django.utils.safestring import mark_safe
from django_object_actions import DjangoObjectActions
from django_object_actions import DjangoObjectActions, action

from accounts.forms import username_taken_by_other_user
from accounts.models import (
Expand Down Expand Up @@ -190,7 +190,7 @@ def get_actions(self, request):
del actions["delete_selected"]
return actions

@admin.action(description="Delete the user but keep the sounds available")
@action(description="Delete the user but keep the sounds available", label="Delete user only")
def delete_preserve_sounds(self, request, obj):
username = obj.username
if request.method == "POST":
Expand Down Expand Up @@ -224,9 +224,7 @@ def delete_preserve_sounds(self, request, obj):
tvars = {"users_to_delete": [user_info], "type": "delete_preserve_sounds"}
return render(request, "accounts/admin_delete_confirmation.html", tvars)

delete_preserve_sounds.label = "Delete user only"

@admin.action(description="Delete the user and the sounds")
@action(description="Delete the user and the sounds", label="Delete user and sounds")
def delete_include_sounds(self, request, obj):
username = obj.username
if request.method == "POST":
Expand Down Expand Up @@ -267,9 +265,7 @@ def delete_include_sounds(self, request, obj):

return render(request, "accounts/admin_delete_confirmation.html", tvars)

delete_include_sounds.label = "Delete user and sounds"

@admin.action(description="Delete the user and the sounds, mark deleted user as spammer")
@action(description="Delete the user and the sounds, mark deleted user as spammer", label="Delete as spammer")
def delete_spammer(self, request, obj):
username = obj.username
if request.method == "POST":
Expand Down Expand Up @@ -308,9 +304,7 @@ def delete_spammer(self, request, obj):

return render(request, "accounts/admin_delete_confirmation.html", tvars)

delete_spammer.label = "Delete as spammer"

@admin.action(description="Completely delete user from db")
@action(description="Completely delete user from db", label="Full delete")
def full_delete(self, request, obj):
username = obj.username
if request.method == "POST":
Expand Down Expand Up @@ -349,9 +343,7 @@ def full_delete(self, request, obj):

return render(request, "accounts/admin_delete_confirmation.html", tvars)

full_delete.label = "Full delete"

@admin.action(description="Clear all user flags for of spam reports and akismet")
@action(description="Clear all user flags for of spam reports and akismet", label="Clear spam flags")
def clear_spam_flags(self, request, obj):
num_akismet, _ = obj.akismetspam_set.all().delete()
num_reports, _ = obj.flags.all().delete()
Expand All @@ -363,20 +355,14 @@ def clear_spam_flags(self, request, obj):
)
return HttpResponseRedirect(reverse("admin:auth_user_change", args=[obj.id]))

clear_spam_flags.label = "Clear spam flags"

@admin.action(description="Open user on site")
@action(description="Open user on site", label="View on site")
def view_on_site_action(self, request, obj):
return HttpResponseRedirect(reverse("account", args=[obj.username]))

view_on_site_action.label = "View on site"

@admin.action(description="Edit profile in admin")
@action(description="Edit profile in admin", label="Edit profile in admin")
def edit_profile_admin(self, request, obj):
return HttpResponseRedirect(reverse("admin:accounts_profile_change", args=[obj.profile.id]))

edit_profile_admin.label = "Edit profile in admin"

# NOTE: in the line below we removed the 'full_delete' option as ideally we should never need to use it. In for
# some unexpected reason we happen to need it, we can call the .delete() method on a user object using the terminal.
# If we observe a real need for that, we can re-add the option to the admin.
Expand Down
2 changes: 1 addition & 1 deletion donations/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


class DonateForm(forms.Form):
RADIO_CHOICES = []
RADIO_CHOICES: list[tuple[str, str]] = []

donation_type = forms.ChoiceField(
widget=forms.RadioSelect(),
Expand Down
4 changes: 2 additions & 2 deletions freesound/audio_descriptor_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
AUDIO_DESCRIPTOR_TYPE_LIST_STRINGS = "list_of_strings"
AUDIO_DESCRIPTOR_TYPE_FLOAT_ARRAY = "float_array"
AUDIO_DESCRIPTOR_TYPE_JSON = "json" # For complex structures
DEFAULT_AUDIO_DESCRIPTOR_TYPE = AUDIO_DESCRIPTOR_TYPE_FLOAT
DEFAULT_AUDIO_DESCRIPTOR_TYPE: str = AUDIO_DESCRIPTOR_TYPE_FLOAT
DEFAULT_AUDIO_DESCRIPTOR_FLOAT_PRECISION = 3 # Number of decimal digits for float audio descriptors

condition_music_or_instrument_samples = lambda s: s.category_names[0] in ["Music", "Instrument samples"]
condition_instrument_samples = lambda s: s.category_names[0] == "Instrument samples"
condition_sfx_or_soundscapes = lambda s: s.category_names[0] in ["Sound effects", "Soundscapes"]
CONSOLIDATED_ANALYZER_NAME = "consolidated"
CONSOLIDATED_AUDIO_DESCRIPTORS = [
CONSOLIDATED_AUDIO_DESCRIPTORS: list[dict] = [
{
"name": "category",
"analyzer": "bst-extractor_v2",
Expand Down
22 changes: 11 additions & 11 deletions freesound/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@
AWS_SES_SHORT_BOUNCE_RATE_DATAPOINTS = 4 # cron period (1hr) / AWS stats period (15min)

# If ALLOWED emails is not empty, only emails going to these destinations will be actually sent
ALLOWED_EMAILS = []
ALLOWED_EMAILS: list[str] = []

# Email subjects
EMAIL_SUBJECT_PREFIX = "[freesound]"
Expand Down Expand Up @@ -292,7 +292,7 @@
# -------------------------------------------------------------------------------
# Freesound miscellaneous settings

SUPPORT = ()
SUPPORT: tuple[tuple[str, str], ...] = ()

IFRAME_PLAYER_SIZE = {"large": [920, 245], "medium": [481, 86], "small": [375, 30], "twitter_card": [440, 132]}

Expand Down Expand Up @@ -352,7 +352,7 @@
ALLOWED_AUDIOFILE_EXTENSIONS = ["wav", "aiff", "aif", "ogg", "flac", "mp3", "m4a", "wv"]
LOSSY_FILE_EXTENSIONS = ["ogg", "mp3", "m4a"]
# Note that some SOUND_TYPE_CHOICES below might correspond to multiple extensions (aiff/aif > aiff)
SOUND_TYPE_CHOICES = (
SOUND_TYPE_CHOICES: tuple[tuple[str, str], ...] = (
("wav", "Wave"),
("ogg", "Ogg Vorbis"),
("aiff", "AIFF"),
Expand All @@ -378,7 +378,7 @@
# Time since last post (in seconds) and maximum number of posts per day
LAST_FORUM_POST_MINIMUM_TIME = 60 * 5
BASE_MAX_POSTS_PER_DAY = 5
SPAM_BLACKLIST = []
SPAM_BLACKLIST: list[str] = []
MIN_DAYS_FOR_COMMENTING_WITH_LINKS = 10

# Random Sound of the day settings
Expand Down Expand Up @@ -470,7 +470,7 @@ def load_broad_sound_taxonomy_from_csv(path):
BST_CATEGORY_CHOICES = [
(key, value["name"]) for key, value in BROAD_SOUND_TAXONOMY.items() if "-" not in key
] # Top-level categories
BST_SUBCATEGORY_CHOICES = [
BST_SUBCATEGORY_CHOICES: list = [
(key, value["name"]) for key, value in BROAD_SOUND_TAXONOMY.items() if "-" in key
] # Second-level categories

Expand Down Expand Up @@ -532,7 +532,7 @@ def load_broad_sound_taxonomy_from_csv(path):
BST_ANALYZER_NAME = "bst-extractor_v1"
BSTV2_ANALYZER_NAME = "bst-extractor_v2"

ANALYZERS_CONFIGURATION = {
ANALYZERS_CONFIGURATION: dict[str, dict] = {
AUDIOCOMMONS_ANALYZER_NAME: {},
FREESOUND_ESSENTIA_EXTRACTOR_NAME: {},
BIRDNET_ANALYZER_NAME: {},
Expand Down Expand Up @@ -644,7 +644,7 @@ def load_broad_sound_taxonomy_from_csv(path):

SIMILARITY_SPACE_LAION_CLAP = "laion_clap"
SIMILARITY_FREESOUND_CLASSIC = "freesound_classic"
SIMILARITY_SPACES = {
SIMILARITY_SPACES: dict[str, dict] = {
SIMILARITY_SPACE_LAION_CLAP: {
"vector_property_name": "clap_embedding",
"vector_size": 512,
Expand All @@ -662,7 +662,7 @@ def load_broad_sound_taxonomy_from_csv(path):
}
SIMILARITY_SPACES_NAMES = list(SIMILARITY_SPACES.keys())
SIMILARITY_SPACES_ANALYZER_NAMES = list(set([ss["analyzer"] for ss in SIMILARITY_SPACES.values()]))
SIMILARITY_SPACE_DEFAULT = SIMILARITY_SPACE_LAION_CLAP
SIMILARITY_SPACE_DEFAULT: str = SIMILARITY_SPACE_LAION_CLAP
SIMILARITY_MIN_THRESHOLD = 0.7

MAX_SEARCH_RESULTS_IN_MAP_DISPLAY = (
Expand Down Expand Up @@ -704,7 +704,7 @@ def load_broad_sound_taxonomy_from_csv(path):

# -------------------------------------------------------------------------------
# Sentry settings
SENTRY_DSN = None
SENTRY_DSN: str | None = None
TRACES_SAMPLE_RATE = 0.1

# -------------------------------------------------------------------------------
Expand Down Expand Up @@ -814,7 +814,7 @@ def load_broad_sound_taxonomy_from_csv(path):
RATELIMIT_SIMILARITY_GROUP: "2/s",
RATELIMIT_REGISTRATION_GROUP: "5/m",
}
BLOCKED_IPS = []
BLOCKED_IPS: list[str] = []
CACHED_BLOCKED_IPS_KEY = "cached_blocked_ips"
CACHED_BLOCKED_IPS_TIME = 60 * 5 # 5 minutes

Expand Down Expand Up @@ -993,7 +993,7 @@ def load_broad_sound_taxonomy_from_csv(path):
CSV_PATH = os.path.join(DATA_PATH, "csv/")
ANALYSIS_PATH = os.path.join(DATA_PATH, "analysis/")
FILE_UPLOAD_TEMP_DIR = os.path.join(DATA_PATH, "tmp_uploads/")
PROCESSING_TEMP_DIR = os.path.join(DATA_PATH, "tmp_processing/")
PROCESSING_TEMP_DIR: str = os.path.join(DATA_PATH, "tmp_processing/")
PROCESSING_BEFORE_DESCRIPTION_DIR = os.path.join(DATA_PATH, "processing_before_description/")

# URLs (depend on DATA_URL potentially re-defined in local_settings.py)
Expand Down
6 changes: 5 additions & 1 deletion fscollections/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
# See AUTHORS file.
#

from __future__ import annotations

from urllib.parse import quote

from django.contrib.auth.models import User
Expand Down Expand Up @@ -45,7 +47,9 @@ class Collection(LicenseSummaryMixin, models.Model):
# TODO: description should be required (check how to display it in edit form + collectionsound form)
description = models.TextField(blank=True)
maintainers = models.ManyToManyField(User, related_name="collection_maintainer")
sounds = models.ManyToManyField(Sound, through="CollectionSound", related_name="collections", blank=True)
sounds: models.ManyToManyField[Sound, "CollectionSound"] = models.ManyToManyField(
Sound, through="CollectionSound", related_name="collections", blank=True
)
num_sounds = models.PositiveIntegerField(default=0)
num_downloads = models.PositiveIntegerField(default=0)
public = models.BooleanField(default=False)
Expand Down
8 changes: 5 additions & 3 deletions general/templatetags/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
# See AUTHORS file.
#

from __future__ import annotations

import datetime
import json
import time
Expand Down Expand Up @@ -93,11 +95,11 @@ def in_list(value, arg):


@register.filter
def chunks(l, n):
def chunks(l: list, n: int) -> list:
"""
Returns the elements of l grouped in chunks of size n.
:param list l: list of elements to regroup
:param int n: number of elements per group
:param l: list of elements to regroup
:param n: number of elements per group
:return: list of n-sized lists
"""
if not isinstance(l, list):
Expand Down
24 changes: 14 additions & 10 deletions monitor/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import requests
from django.conf import settings
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser, User
from django.core.cache import caches
from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
Expand All @@ -45,8 +45,12 @@
)


def user_is_staff(user: AbstractBaseUser | AnonymousUser) -> bool:
return isinstance(user, User) and user.is_staff


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def get_queues_status(request):
try:
celery_task_counts = get_queues_task_counts()
Expand All @@ -56,15 +60,15 @@ def get_queues_status(request):


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def monitor_home(request):
# Test instrumentation: bump a counter so we can confirm it appears on /metrics.
MONITOR_HOME_VIEWS.inc()
return HttpResponseRedirect(reverse("monitor-stats"))


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def monitor_processing(request):
# Processing
sounds_queued_count = Sound.objects.filter(processing_ongoing_state="QU").count()
Expand All @@ -90,7 +94,7 @@ def monitor_processing(request):


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def monitor_analysis(request):
# Analysis
analyzers_data = {}
Expand Down Expand Up @@ -142,7 +146,7 @@ def monitor_analysis(request):


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def monitor_similarity(request):
# Get stats about similarity vectors indexed in Solr
try:
Expand All @@ -169,7 +173,7 @@ def monitor_similarity(request):


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def monitor_moderation(request):
sounds_in_moderators_queue_count = tickets.views._get_sounds_in_moderators_queue_count(request.user)
new_upload_count = tickets.views.new_sound_tickets_count()
Expand Down Expand Up @@ -198,14 +202,14 @@ def monitor_moderation(request):


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def monitor_stats(request):
tvars = {"activePage": "stats"}
return render(request, "monitor/stats.html", tvars)


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def moderators_stats(request):
return HttpResponseRedirect(reverse("monitor-moderation"))

Expand Down Expand Up @@ -266,7 +270,7 @@ def totals_stats_ajax(request):


@login_required
@user_passes_test(lambda u: u.is_staff, login_url="/")
@user_passes_test(user_is_staff, login_url="/")
def process_sounds(request):
# Send sounds to processing according to their processing_state
processing_status = request.GET.get("prs", None)
Expand Down
Loading