Skip to content

Commit

Permalink
Merge branch 'master' into ticket-permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
ffont authored Oct 16, 2023
2 parents 9168f2f + 36210e1 commit aef4c3c
Show file tree
Hide file tree
Showing 25 changed files with 361 additions and 193 deletions.
11 changes: 7 additions & 4 deletions accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
from sounds.forms import LicenseForm, PackForm, BWPackForm, SoundDescriptionForm, GeotaggingForm
from sounds.models import Sound, Pack, Download, SoundLicenseHistory, BulkUploadProgress, PackDownload
from sounds.views import edit_and_describe_sounds_helper, clear_session_edit_and_describe_data
from tickets.models import TicketComment, Ticket
from tickets.models import TicketComment, Ticket, UserAnnotation
from utils.cache import invalidate_user_template_caches
from utils.dbtime import DBTime
from utils.filesystem import generate_tree, remove_directory_if_empty
Expand Down Expand Up @@ -1434,9 +1434,11 @@ def account(request, username):
if not user.is_active:
messages.add_message(request, messages.INFO, 'This account has <b>not been activated</b> yet.')
if request.user.has_perm('tickets.can_moderate'):
num_sounds_pending_count = user.profile.num_sounds_pending_moderation()
num_sounds_pending = user.profile.num_sounds_pending_moderation()
num_mod_annotations = UserAnnotation.objects.filter(user=user).count()
else:
num_sounds_pending_count = None
num_sounds_pending = None
num_mod_annotations = None

show_about = ((request.user == user) # user is looking at own page
or request.user.is_superuser # admins should always see about fields
Expand Down Expand Up @@ -1464,7 +1466,8 @@ def account(request, username):
'show_unfollow_button': show_unfollow_button,
'has_bookmarks': has_bookmarks,
'show_about': show_about,
'num_sounds_pending_count': num_sounds_pending_count,
'num_sounds_pending': num_sounds_pending,
'num_mod_annotations': num_mod_annotations,
'following_modal_page': request.GET.get('following', 1), # BW only, used to load a specific modal page
'followers_modal_page': request.GET.get('followers', 1), # BW only
'following_tags_modal_page': request.GET.get('followingTags', 1), # BW only
Expand Down
6 changes: 5 additions & 1 deletion docker/Dockerfile.workers_web
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ RUN make clean && make

FROM freesound:2023-07

RUN mkdir -p /etc/apt/keyrings/
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" > /etc/apt/sources.list.d/nodesource.list

# Install specific dependencies needed for processing, building static files and for ssh
RUN wget -q -O - https://deb.nodesource.com/setup_18.x | bash - && apt-get update \
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
sndfile-programs \
libsndfile1-dev \
Expand Down
10 changes: 6 additions & 4 deletions forum/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@
# See AUTHORS file.
#

from adminsortable.admin import SortableAdmin
from django.contrib import admin
from forum.models import Forum, Thread, Post

from forum.models import Forum, Post, Thread


@admin.register(Forum)
class ForumAdmin(admin.ModelAdmin):
class ForumAdmin(SortableAdmin):
raw_id_fields = ('last_post', )
list_display = ('name', 'num_threads', 'change_order')
list_display = ('name', 'num_threads')



Expand All @@ -42,4 +45,3 @@ class PostAdmin(admin.ModelAdmin):
raw_id_fields = ('author', 'thread')
list_display = ('thread', 'author', 'created')
search_fields = ('=author__username', "body")

18 changes: 18 additions & 0 deletions forum/migrations/0004_alter_forum_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-09-27 19:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('forum', '0003_auto_20230201_1102'),
]

operations = [
migrations.AlterField(
model_name='forum',
name='order',
field=models.PositiveIntegerField(default=0, editable=False),
),
]
1 change: 1 addition & 0 deletions freesound/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
'silk',
'admin_reorder',
'captcha',
'adminsortable',
]

# Specify custom ordering of models in Django Admin index
Expand Down
74 changes: 34 additions & 40 deletions freesound/static/bw-frontend/src/components/loginModals.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,72 @@ const checkUsernameAvailability = (username, baseURL, callback) => {
const req = new XMLHttpRequest();
req.open('GET', baseURL + '?username=' + username, true);
req.onload = () => {
if (req.status >= 200 && req.status < 300) {
const data = JSON.parse(req.responseText);
callback(data.result);
}
if (req.status >= 200 && req.status < 300) {
const data = JSON.parse(req.responseText);
callback(data.result);
}
};
req.send();
};

const customProblemsLoggingInSubmit = (event) => {
const problemsLoggingInForm = document.getElementById("problemsLoggingInModalForm");
const params = serialize(problemsLoggingInForm);

// Create new Ajax request to submit registration form contents
const req = new XMLHttpRequest();
req.open('POST', problemsLoggingInForm.action, true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
req.onload = () => {
if (req.status >= 200 && req.status < 300) {
showToast('Check your email, we\'ve sent you a link');
}
if (req.status >= 200 && req.status < 300) {
showToast("If the address you've entered is correct, you should now receive an email with instructions");
}
};
req.onerror = () => {
// Unexpected errors happened while processing request: show error in toast
showToast('Some errors occurred while processing the form. Please try again later.')
// Unexpected errors happened while processing request: show error in toast
showToast('Some errors occurred while processing the form. Please try again later.')
};

// Send the form
req.send(params);

// Stop propagation of submit event
event.preventDefault();
return false;
};

const initRegistrationForm = (registrationForm) => {

// Bind click actions on links to move to other login modals
initLoginAndRegistrationModalLinks('registerModal');

// Load grecaptcha script tag (needed if this is loaded ajax)
addRecaptchaScriptTagToMainHead(registrationForm);

// Add "next" parameter to the form action so users are redirected to the same page when registration finishes
const pathWithParameters = window.location.pathname + window.location.search;
registrationForm.action = registrationForm.action + '&next=' + encodeURI(pathWithParameters);

// Initialize checkboxes (registration form contains checkboxes)
addCheckboxVisibleElements();

// Add event handler to check username availability on focusout
const usernameInputElement = registrationForm.querySelector('input[name="username"]');
usernameInputElement.addEventListener("focusout", () => {
checkUsernameAvailability(usernameInputElement.value, registrationForm.dataset.checkUsernameUrl, (isAvailable) => {
const previousElementIsErrorlist = usernameInputElement.previousElementSibling.classList.contains('errorlist');
if (isAvailable === true){
// Check if there is an "invalid username" message shown and remove it if username is now valid
if (previousElementIsErrorlist){
usernameInputElement.previousElementSibling.remove();
}
} else {
// Check if there is an "invalid username" message shown, and add one if it is not there
if (!previousElementIsErrorlist) {
usernameInputElement.insertAdjacentHTML('beforebegin', '<ul class="errorlist"><li>You cannot use this username to create an account</li></ul>')
}
}
});
checkUsernameAvailability(usernameInputElement.value, registrationForm.dataset.checkUsernameUrl, (isAvailable) => {
const previousElementIsErrorlist = usernameInputElement.previousElementSibling.classList.contains('errorlist');
if (isAvailable === true){
// Check if there is an "invalid username" message shown and remove it if username is now valid
if (previousElementIsErrorlist){
usernameInputElement.previousElementSibling.remove();
}
} else {
// Check if there is an "invalid username" message shown, and add one if it is not there
if (!previousElementIsErrorlist) {
usernameInputElement.insertAdjacentHTML('beforebegin', '<ul class="errorlist"><li>You cannot use this username to create an account</li></ul>')
}
}
});
});
};

Expand All @@ -82,15 +82,15 @@ const initLoginForm = (loginForm) => {
const pathWithParameters = window.location.pathname + window.location.search;
const loginNextHiddenInput = loginForm.querySelectorAll('input[name="next"]')[0];
if (!loginNextHiddenInput.value){
// Only if next value is not yet set, set it automatically
loginNextHiddenInput.value = pathWithParameters;
// Only if next value is not yet set, set it automatically
loginNextHiddenInput.value = pathWithParameters;
}
};

const initProblemsLoggingInForm = (problemsLoggingInForm) => {
// Assign custom onsubmit method which will submit the form via AJAX and show notification
problemsLoggingInForm.onsubmit = (event) => {
customProblemsLoggingInSubmit(event);
customProblemsLoggingInSubmit(event);
}
};

Expand Down Expand Up @@ -167,10 +167,4 @@ document.addEventListener("DOMContentLoaded", () => {
}
});

// Handle recovery account button link
const recoveryAccountBtn = document.getElementById('recovery-account');
recoveryAccountBtn.addEventListener('click', () => {
showToast("Check your email, we've sent you a link");
});

export {initRegistrationForm, initProblemsLoggingInForm, initLoginForm};
41 changes: 40 additions & 1 deletion freesound/static/bw-frontend/src/components/moderationModals.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
import {handleGenericModal, bindModalActivationElements, activateModalsIfParameters, initPlayersInModal, stopPlayersInModal} from './modal';
import {handleGenericModal, bindModalActivationElements, activateModalsIfParameters, initPlayersInModal, stopPlayersInModal, dismissModal} from './modal';
import {makePostRequest} from "../utils/postRequest";
import {showToast} from "./toast";

const saveAnnotation = (addAnnotationUrl, text, user_id) => {

let formData = {};
formData.text = text;

makePostRequest(addAnnotationUrl, formData, (responseText) => {
// Bookmark saved successfully. Close model and show feedback
dismissModal(`moderationAnnotationsModal`);
const responseData = JSON.parse(responseText);
document.getElementsByClassName('annotation-counter-' + user_id).forEach(element => {
element.innerText = responseData.num_annotations;
});
showToast(responseData.message);
}, () => {
// Unexpected errors happened while processing request: close modal and show error in toast
dismissModal(`moderationAnnotationsModal`);
showToast('Some errors occurred while adding the annotation.');
});
}

const handleModerationModal = (modalUrl, modalActivationParam, atPage) => {
if ((atPage !== undefined) && modalUrl.indexOf('&page') == -1){
Expand All @@ -15,14 +37,31 @@ const handleModerationModal = (modalUrl, modalActivationParam, atPage) => {
}, true, true, modalActivationParam);
}

const handleUserAnnnotationModal = (modalUrl, modalActivationParam) => {
handleGenericModal(modalUrl, (modalContainer) => {
// Bind action to save annotation (and prevent default form submit)
const formElement = modalContainer.getElementsByTagName('form')[0];
const buttonsInModalForm = formElement.getElementsByTagName('button');
const textInputField = formElement.getElementsByTagName('textarea')[0];
const saveButtonElement = buttonsInModalForm[buttonsInModalForm.length - 1];
saveButtonElement.addEventListener('click', (e) => {
e.preventDefault();
saveAnnotation(saveButtonElement.dataset.addAnnotationUrl, textInputField.value, saveButtonElement.dataset.userId);
});
}, (modalContainer) => {
}, true, true, modalActivationParam);
}

const bindModerationModals = (container) => {
bindModalActivationElements('[data-toggle="pending-moderation-modal"]', handleModerationModal, container);
bindModalActivationElements('[data-toggle="user-annotations-modal"]', handleUserAnnnotationModal, container);
bindModalActivationElements('[data-toggle="tardy-users-modal"]', handleModerationModal, container);
bindModalActivationElements('[data-toggle="tardy-moderators-modal"]', handleModerationModal, container);
}

bindModerationModals();
activateModalsIfParameters('[data-toggle="pending-moderation-modal"]', handleModerationModal);
activateModalsIfParameters('[data-toggle="user-annotations-modal"]', handleUserAnnnotationModal);
activateModalsIfParameters('[data-toggle="tardy-users-modal"]', handleModerationModal);
activateModalsIfParameters('[data-toggle="tardy-moderators-modal"]', handleModerationModal);

Expand Down
3 changes: 0 additions & 3 deletions freesound/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import comments.views
import bookmarks.views
import follow.views
import general.views
import donations.views
import utils.tagrecommendation_utilities as tagrec
from apiv2.apiv2_utils import apiv1_end_of_life_message
Expand Down Expand Up @@ -123,8 +122,6 @@
re_path(r'^crossdomain\.xml$', TemplateView.as_view(template_name='crossdomain.xml'), name="crossdomain"),

# admin views
re_path(r'^admin/orderedmove/(?P<direction>up|down)/(?P<model_type_id>\d+)/(?P<model_id>\d+)/$',
general.views.admin_move_ordered_model, name="admin-move"),
path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls),

Expand Down
52 changes: 5 additions & 47 deletions general/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@
# Authors:
# See AUTHORS file.
#

from comments.models import Comment
from adminsortable.models import SortableMixin
from django.contrib.auth.models import User
from django.contrib.contenttypes import fields
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from django.db import models

from favorites.models import Favorite
from ratings.models import SoundRating
from tags.models import TaggedItem


class SocialModel(models.Model):
tags = fields.GenericRelation(TaggedItem)
fans = fields.GenericRelation(Favorite)
Expand All @@ -40,48 +38,8 @@ class AkismetSpam(SocialModel):
spam = models.TextField()
created = models.DateTimeField(auto_now_add=True)

class OrderedModel(models.Model):
order = models.PositiveIntegerField(editable=False)

def save(self, *args, **kwargs):
if not self.id:
try:
self.order = self.__class__.objects.all().order_by("-order")[0].order + 1
except IndexError:
self.order = 0
super().save(*args, **kwargs)

def change_order(self):
model_type_id = ContentType.objects.get_for_model(self.__class__).id
model_id = self.id
kwargs = {"direction": "up", "model_type_id": model_type_id, "model_id": model_id}
url_up = reverse("admin-move", kwargs=kwargs)
kwargs["direction"] = "down"
url_down = reverse("admin-move", kwargs=kwargs)
return mark_safe(f'<a href="{url_up}">up</a> | <a href="{url_down}">down</a>')
change_order.short_description = 'Move'
change_order.admin_order_field = 'order'

@staticmethod
def move(direction, model_type_id, model_id):
try:
ModelClass = ContentType.objects.get(id=model_type_id).model_class()

current_model = ModelClass.objects.get(id=model_id)

if direction == "down":
swap_model = ModelClass.objects.filter(order__gt=current_model.order).order_by("order")[0]
elif direction == "up":
swap_model = ModelClass.objects.filter(order__lt=current_model.order).order_by("-order")[0]

current_model.order, swap_model.order = swap_model.order, current_model.order

current_model.save()
swap_model.save()
except IndexError:
pass
except ModelClass.DoesNotExist:
pass
class OrderedModel(SortableMixin):
order = models.PositiveIntegerField(default=0, editable=False)

class Meta:
ordering = ["order"]
Expand Down
Loading

0 comments on commit aef4c3c

Please sign in to comment.