Skip to content

Commit

Permalink
Merge branch 'master' into search-refactor2
Browse files Browse the repository at this point in the history
  • Loading branch information
ffont committed Apr 10, 2024
2 parents e26fcc7 + 2214bf7 commit caf1d8f
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 86 deletions.
2 changes: 1 addition & 1 deletion accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def __init__(self, required=True):
max_length=30,
validators=[RegexValidator(r'^[\w.+-]+$')], # is the same as Django UsernameValidator except for '@' symbol
help_text="30 characters or fewer. Can contain: letters, digits, underscores, dots, dashes and plus signs.",
error_messages={'invalid': "This value must contain only letters, digits, underscores, dots, dashes and "
error_messages={'invalid': "The username field must contain only letters, digits, underscores, dots, dashes and "
"plus signs."},
required=required)

Expand Down
35 changes: 32 additions & 3 deletions accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from django.utils.http import int_to_base36
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from general.templatetags.absurl import url2absurl
from oauth2_provider.models import AccessToken

import tickets.views as TicketViews
Expand Down Expand Up @@ -858,7 +859,9 @@ def attribution(request):

@login_required
def download_attribution(request):
content = {'csv': 'csv', 'txt': 'plain'}
content = {'csv': 'csv',
'txt': 'plain',
'json': 'json'}

qs_sounds = Download.objects.annotate(download_type=Value('sound', CharField()))\
.values('download_type', 'sound_id', 'sound__user__username', 'sound__original_filename',
Expand All @@ -871,9 +874,9 @@ def download_attribution(request):
qs = qs_sounds.union(qs_packs).order_by('-created')

download = request.GET.get('dl', '')
now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
filename = f'{request.user}_{now}_attribution.{download}'
if download in ['csv', 'txt']:
now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
filename = f'{request.user}_{now}_attribution.{download}'
response = HttpResponse(content_type=f'text/{content[download]}')
response['Content-Disposition'] = f'attachment; filename="{filename}"'
output = io.StringIO()
Expand All @@ -896,6 +899,32 @@ def download_attribution(request):
row['created']))
response.writelines(output.getvalue())
return response
elif download == 'json':
output = []
for row in qs:
if row['download_type'][0].upper() == 'S':
output.append({
'sound_url': url2absurl(reverse("sound", args=[row['sound__user__username'], row['sound_id']])),
'sound_name': row['sound__original_filename'],
'author_url': url2absurl(reverse("account", args=[row['sound__user__username']])),
'author_name': row['sound__user__username'],
'license_url': row['license__deed_url'] or row['sound__license__deed_url'],
'license_name': license_with_version(row['license__name'] or row['sound__license__name'],
row['license__deed_url'] or row['sound__license__deed_url']),
'timestamp': str(row['created'])
})
elif row['download_type'][0].upper() == 'P':
output.append({
'pack_url': url2absurl(reverse("pack", args=[row['sound__user__username'], row['sound_id']])),
'pack_name': row['sound__original_filename'],
'author_url': url2absurl(reverse("account", args=[row['sound__user__username']])),
'author_name': row['sound__user__username'],
'license_url': row['license__deed_url'] or row['sound__license__deed_url'],
'license_name': license_with_version(row['license__name'] or row['sound__license__name'],
row['license__deed_url'] or row['sound__license__deed_url']),
'timestamp': str(row['created'])
})
return JsonResponse(output, safe=False)
else:
return HttpResponseRedirect(reverse('accounts-attribution'))

Expand Down
6 changes: 6 additions & 0 deletions freesound/static/bw-frontend/src/components/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ const handleGenericModal = (fetchContentUrl, onLoadedCallback, onClosedCallback,
if (onLoadedCallback !== undefined){
onLoadedCallback(modalContainer);
}

// Trigger modal loaded event in case it should be used by other components
const event = new CustomEvent("modalLoaded", {
detail: {fetchContentUrl, modalContainer},
});
document.dispatchEvent(event);

// If modal is activated with a param, add the param to the URL when opening the modal
if (modalActivationParam !== undefined){
Expand Down
60 changes: 48 additions & 12 deletions freesound/static/bw-frontend/src/pages/sound.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import './page-polyfills';
import {showToast} from '../components/toast';
import {playAtTime} from '../components/player/utils';
import {handleGenericModalWithForm} from '../components/modal';
import {addRecaptchaScriptTagToMainHead} from '../utils/recaptchaDynamicReload'
import { showToast } from '../components/toast';
import { playAtTime } from '../components/player/utils';
import { handleGenericModalWithForm, dismissModal } from '../components/modal';
import { addRecaptchaScriptTagToMainHead } from '../utils/recaptchaDynamicReload'
import { prepareAfterDownloadSoundModals } from '../components/afterDownloadModal.js';

const toggleEmbedCodeElement = document.getElementById('toggle-embed-code');
Expand All @@ -17,15 +17,25 @@ const urlParams = new URLSearchParams(window.location.search);

prepareAfterDownloadSoundModals();

const copyShareUrlToClipboard = () => {
var shareLinkInputElement = shareLinkElement.getElementsByTagName("input")[0];
shareLinkInputElement.select();
shareLinkInputElement.setSelectionRange(0, 99999);
const copyFromInputElement = (inputElement) => {
inputElement.select();
inputElement.setSelectionRange(0, 99999);
document.execCommand("copy");
showToast('Sound URL copied in the clipboard');
document.getSelection().removeAllRanges();
}


const copyShareUrlToClipboard = (useFileURL) => {
var shareLinkInputElement = shareLinkElement.getElementsByTagName("input")[0];
if (useFileURL) {
shareLinkInputElement.value = shareLinkElement.dataset.staticFileUrl;
} else {
shareLinkInputElement.value = shareLinkElement.dataset.soundPageUrl;
}
copyFromInputElement(shareLinkInputElement);
showToast('Sound URL copied to the clipboard');
}

const toggleEmbedCode = () => {
if (embedLinksElement.style.display === "none") {
embedLinksElement.style.display = "block";
Expand All @@ -38,10 +48,11 @@ const toggleEmbedCode = () => {
}
}

const toggleShareLink = () => {
const toggleShareLink = (evt) => {
if (shareLinkElement.style.display === "none") {
shareLinkElement.style.display = "block";
copyShareUrlToClipboard();
const useFileURL = evt.altKey;
copyShareUrlToClipboard(useFileURL);
} else {
shareLinkElement.style.display = "none";
}
Expand All @@ -52,7 +63,7 @@ const toggleShareLink = () => {
}

toggleEmbedCodeElement.addEventListener('click', toggleEmbedCode);
toggleShareLinkElement.addEventListener('click', toggleShareLink);
toggleShareLinkElement.addEventListener('click', evt => toggleShareLink(evt));


const generateEmbedCode = (size) => {
Expand Down Expand Up @@ -135,3 +146,28 @@ if (flagSoundModalParamValue) {
flagSoundButton.addEventListener('click', (evt) => {
handleFlagSoundModal();
})


// Attribution modal
const handleAttributionModal = (modalContainer) => {
const selectElement = modalContainer.getElementsByTagName('select')[0];
const textArea = modalContainer.getElementsByTagName('textarea')[0];
textArea.value = selectElement.value;
selectElement.addEventListener('change', () => {
textArea.value = selectElement.value;
});

const buttons = modalContainer.getElementsByTagName('button');
const copyButton = buttons[buttons.length - 1];
copyButton.addEventListener('click', () => {
copyFromInputElement(textArea);
showToast('Attribution text copied to the clipboard');
dismissModal(modalContainer.id);
});
}

document.addEventListener('modalLoaded', (evt) => {
if (evt.detail.modalContainer.id === 'soundAttributionModal') {
handleAttributionModal(evt.detail.modalContainer);
}
});
4 changes: 2 additions & 2 deletions freesound/static/bw-frontend/styles/layout/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ html {
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
font-family: BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
font-size: $base-size-mobile;
line-height: $base-line-height-mobile;
color: $black;
background-color: $white;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 400; /* designer sugegsted overall font weight 500, but we're making it 400 as otherwise it felt too heavy */
font-weight: 400; /* designer suggested overall font weight 500, but we're making it 400 as otherwise it felt too heavy */

/* disallow avoid horizontal scrolling */
max-width: 100%;
Expand Down
1 change: 1 addition & 0 deletions freesound/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
path('people/<username>/sounds/<int:sound_id>/similar/', sounds.views.similar, name="sound-similar"),
path('people/<username>/sounds/<int:sound_id>/downloaders/', sounds.views.downloaders, name="sound-downloaders"),
path('people/<username>/sounds/<int:sound_id>/comments/', comments.views.for_sound, name="sound-comments"),
path('people/<username>/sounds/<int:sound_id>/attribution/', sounds.views.attribution_modal, name="sound-attribution"),
path('people/<username>/packs/', sounds.views.packs_for_user, name="packs-for-user"),
path('people/<username>/packs/<int:pack_id>/', sounds.views.pack, name="pack"),
path('people/<username>/packs/<int:pack_id>/section/stats/', sounds.views.pack_stats_section, name="pack-stats-section"),
Expand Down
6 changes: 6 additions & 0 deletions general/templatetags/absurl.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ def absurl(parser, token, node_cls=AbsoluteURLNode):
asvar=node_instance.asvar)

absurl = register.tag(absurl)


@register.filter
def url2absurl(path):
domain = f"https://{Site.objects.get_current().domain}"
return urllib.parse.urljoin(domain, path)
25 changes: 25 additions & 0 deletions sounds/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from comments.models import Comment
from freesound.celery import app as celery_app
from general import tasks
from general.templatetags.absurl import url2absurl
from geotags.models import GeoTag
from ratings.models import SoundRating
from general.templatetags.util import formatnumber
Expand Down Expand Up @@ -928,6 +929,30 @@ def get_license_history(self):
"""
return [(slh.created, slh.license) for slh in
self.soundlicensehistory_set.select_related('license').order_by('-created')]

@cached_property
def attribution_texts(self):
attribution_texts = {
'plain_text': f'{self.original_filename} by {self.user.username} -- {url2absurl(reverse("short-sound-link", args=[self.id]))} -- License: {self.license.name_with_version}',
'html': f'<a href="{url2absurl(self.get_absolute_url())}">{self.original_filename}</a> by <a href="{url2absurl(reverse("account", args=[self.user.username]))}">{self.user.username}</a> | License: <a href="{ self.license.deed_url }">{self.license.name_with_version}</a>',
'json': json.dumps({
'sound_url': url2absurl(self.get_absolute_url()),
'sound_name': self.original_filename,
'author_url': url2absurl(reverse("account", args=[self.user.username])),
'author_name': self.user.username,
'license_url': self.license.deed_url,
'license_name': self.license.name_with_version,
})
}
if not self.sources.exists():
return attribution_texts
else:
sources_attribution_texts = [s.attribution_texts for s in self.sources.all()]
attribution_texts['plain_text'] = "\n\n".join([attribution_texts['plain_text']] + [st['plain_text'] for st in sources_attribution_texts])
attribution_texts['html'] = "<br>\n".join([attribution_texts['html']] + [st['html'] for st in sources_attribution_texts])
attribution_texts['json'] = json.dumps([json.loads(attribution_texts['json'])] + [json.loads(st['json']) for st in sources_attribution_texts])
return attribution_texts


def get_sound_tags(self, limit=None):
"""
Expand Down
9 changes: 9 additions & 0 deletions sounds/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,15 @@ def flag(request, username, sound_id):
return render(request, 'sounds/modal_flag_sound.html', tvars)


def attribution_modal(request, username, sound_id):
if not request.GET.get('ajax'):
# If not loaded as a modal, redirect to the sound page with parameter to open modal
return HttpResponseRedirect(reverse('sound', args=[username, sound_id]) + '?attribution=1')
sound = get_object_or_404(Sound, id=sound_id)
tvars = {'sound': sound}
return render(request, 'sounds/modal_attribution.html', tvars)


def sound_short_link(request, sound_id):
sound = get_object_or_404(Sound, id=sound_id)
return redirect('sound', username=sound.user.username, sound_id=sound.id)
Expand Down
6 changes: 3 additions & 3 deletions templates/accounts/attribution.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
This is the list of files you have downloaded. When you use freesound samples under the Attribution or Attribution NonCommercial license, you have to <a href="{% url "wiki-page" "faq" %}#how-do-i-creditattribute">credit the original creator of the sound</a> in your work.
This list makes it easy to do so. "S" means sound, "P" means pack.
There are 3 flavors of this list: <a href="?format=regular">regular</a>, <a href="?format=html">html</a> or <a href="?format=plaintext">plain text</a>.
Alternatively, you can download the <b>complete record of your downloaded sounds</b> in <a href="{% url "accounts-download-attribution" %}?dl=csv">csv</a> or <a href="{% url "accounts-download-attribution" %}?dl=txt">plain text</a> formats.
Alternatively, you can download the <b>complete record of your downloaded sounds</b> in <a href="{% url "accounts-download-attribution" %}?dl=csv">csv</a>, <a href="{% url "accounts-download-attribution" %}?dl=txt">plain text</a>, or <a href="{% url "accounts-download-attribution" %}?dl=json">json</a> formats.
</p><p>

</p>
Expand All @@ -26,7 +26,7 @@ <h4 class="v-spacing-top-4">Downloaded on {{group.grouper}}</h4>
<ul>
{% for download_item in group.list %}
{% if download_item.download_type == 'sound' %}
<li>S: <a href="{% absurl 'sound' download_item.sound__user__username download_item.sound_id %}">{{download_item.sound__original_filename }}</a> by <a href="{% absurl 'account' download_item.sound__user__username %}">{{download_item.sound__user__username }}</a> | <span class="text-grey">License: </span>{% if download_item.license__name %}{{ download_item.license__name|license_with_version:download_item.license__deed_url}}{% else %}{{ download_item.sound__license__name|license_with_version:download_item.sound__license__deed_url }}{% endif %}</li>
<li>S: <a href="{% absurl 'sound' download_item.sound__user__username download_item.sound_id %}">{{download_item.sound__original_filename }}</a> by <a href="{% absurl 'account' download_item.sound__user__username %}">{{download_item.sound__user__username }}</a> | <span class="text-grey">License: </span>{% if download_item.license__name %}<a class="bw-link--black" href="{{ download_item.license__deed_url }}">{{ download_item.license__name|license_with_version:download_item.license__deed_url}}{% else %}<a class="bw-link--black" href="{{ download_item.sound__license__deed_url }}">{{ download_item.sound__license__name|license_with_version:download_item.sound__license__deed_url }}</a>{% endif %}</li>
{% else %}
{% comment %}NOTE: in the line below, even though we're displaying information about a pack download, we use download_item.X where X takes the same names as in the line above when we were displaying
information about sound download. This is beacuse after doing the uninon of the two QuerySets (see accounts.views.attribution) the names of the columns are "unified" and taken from the main QuerySet{% endcomment %}
Expand All @@ -45,7 +45,7 @@ <h4 class="v-spacing-top-4">Downloaded on {{group.grouper}}</h4>
{% for download_item in group.list %}
&nbsp;&nbsp;&nbsp;&nbsp;{% filter force_escape %}
{% if download_item.download_type == 'sound' %}
<li>S: <a href="{% absurl 'sound' download_item.sound__user__username download_item.sound_id %}">{{download_item.sound__original_filename }}</a> by <a href="{% absurl 'account' download_item.sound__user__username %}">{{download_item.sound__user__username }}</a> | License: {% if download_item.license__name %}{{ download_item.license__name|license_with_version:download_item.license__deed_url}}{% else %}{{ download_item.sound__license__name|license_with_version:download_item.sound__license__deed_url }}{% endif %}</li>
<li>S: <a href="{% absurl 'sound' download_item.sound__user__username download_item.sound_id %}">{{download_item.sound__original_filename }}</a> by <a href="{% absurl 'account' download_item.sound__user__username %}">{{download_item.sound__user__username }}</a> | License: {% if download_item.license__name %}<a href="{{ download_item.license__deed_url }}">{{ download_item.license__name|license_with_version:download_item.license__deed_url}}{% else %}<a href="{{ download_item.sound__license__deed_url }}">{{ download_item.sound__license__name|license_with_version:download_item.sound__license__deed_url }}</a>{% endif %}</li>
{% else %}
{% comment %}NOTE: in the line below, even though we're displaying information about a pack download, we use download_item.X where X takes the same names as in the line above when we were displaying
information about sound download. This is beacuse after doing the uninon of the two QuerySets (see accounts.views.attribution) the names of the columns are "unified" and taken from the main QuerySet{% endcomment %}
Expand Down
28 changes: 28 additions & 0 deletions templates/sounds/modal_attribution.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends "molecules/modal_base.html" %}
{% load util %}

{% block id %}soundAttributionModal{% endblock %}
{% block extra-class %}{% endblock %}
{% block aria-label %}Get attribution text{% endblock %}

{% block body %}
<div class="col-12">
<div class="text-center">
<h4 class="v-spacing-5">Get attribution text</h4>
</div>
<div class="v-spacing-4">
<div class="bw-form">
<div>
Format:
<select>
<option value="{{ sound.attribution_texts.plain_text|safe|force_escape }}">Plain text</option>
<option value="{{ sound.attribution_texts.html|safe|force_escape }}">HTML</option>
<option value="{{ sound.attribution_texts.json|safe|force_escape }}">JSON</option>
</select>
</div>
<textarea></textarea>
</div>
<button class="btn-primary v-spacing-top-1 w-100">Copy text</button>
</div>
</div>
{% endblock %}
Loading

0 comments on commit caf1d8f

Please sign in to comment.