Skip to content

Commit

Permalink
Merge pull request #1800 from MTG/issue1290
Browse files Browse the repository at this point in the history
bookmark category download implementation
  • Loading branch information
ffont authored Dec 10, 2024
2 parents 30f43c5 + 598f0ce commit 762b267
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 61 deletions.
3 changes: 2 additions & 1 deletion accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
path('bookmarks/category/<int:category_id>/delete/', bookmarks.delete_bookmark_category, name="delete-bookmark-category"),
path('bookmarks/<int:bookmark_id>/delete/', bookmarks.delete_bookmark, name="delete-bookmark"),
path('bookmarks/category/<int:category_id>/edit_modal/', bookmarks.edit_bookmark_category, name="edit-bookmark-category"),

path('bookmarks/<int:category_id>/download/', bookmarks.download_bookmark_category, name="download-bookmark-category"),
path('bookmarks/<int:category_id>/licenses/', bookmarks.bookmark_category_licenses, name="category-licenses"),

path('messages/', messages.inbox, name='messages'),
path('messages/sent/', messages.sent_messages, name='messages-sent'),
Expand Down
4 changes: 3 additions & 1 deletion apiv2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,8 +701,10 @@ def get(self, request, *args, **kwargs):
raise NotFoundException(
msg='Sounds in pack %i have not yet been described or moderated' % int(pack_id), resource=self)

sounds_list = pack.sounds.filter(processing_state="OK", moderation_state="OK").select_related('user', 'license')
licenses_url = (reverse('pack-licenses', args=[pack.user.username, pack.id]))
return download_sounds(licenses_url, pack)
licenses_content = pack.get_attribution(sound_qs=sounds_list)
return download_sounds(licenses_url, licenses_content, sounds_list)


##################
Expand Down
22 changes: 21 additions & 1 deletion bookmarks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
from django.contrib.auth.models import User
from django.db import models

from sounds.models import Sound
from sounds.models import Sound, License
from django.template.loader import render_to_string


class BookmarkCategory(models.Model):
Expand All @@ -30,6 +31,25 @@ class BookmarkCategory(models.Model):

def __str__(self):
return f"{self.name}"

def get_attribution(self, sound_qs):
#If no queryset of sounds is provided, take it from the bookmark category
if(sound_qs):
sounds_list=sound_qs
else:
bookmarked_sounds = Bookmark.objects.filter(category_id=self.id).values("sound_id")
sounds_list = Sound.objects.filter(id__in=bookmarked_sounds, processing_state="OK", moderation_state="OK").select_related('user','license')

users = User.objects.filter(sounds__in=sounds_list).distinct()
# Generate text file with license info
licenses = License.objects.filter(sound__id__in = sounds_list).distinct()
attribution = render_to_string(("sounds/multiple_sounds_attribution.txt"),
dict(type="Bookmark Category",
users=users,
object=self,
licenses=licenses,
sound_list=sounds_list))
return attribution


class Bookmark(models.Model):
Expand Down
32 changes: 27 additions & 5 deletions bookmarks/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.db.models import Count
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from bookmarks.forms import BookmarkForm, BookmarkCategoryForm
from bookmarks.models import Bookmark, BookmarkCategory
from sounds.models import Sound
from utils.downloads import download_sounds
from utils.pagination import paginate
from utils.username import redirect_if_old_username_or_404, raise_404_if_user_is_deleted

Expand All @@ -41,20 +42,23 @@ def bookmarks(request, category_id=None):
n_uncat = Bookmark.objects.select_related("sound").filter(user=user, category=None).count()
if not category_id:
category = None
bookmarked_sounds = Bookmark.objects.select_related("sound", "sound__user").filter(user=user, category=None)
bookmarked_sounds = Bookmark.objects.filter(user=user, category=None)
else:
category = get_object_or_404(BookmarkCategory, id=category_id, user=user)
bookmarked_sounds = category.bookmarks.select_related("sound", "sound__user").all()
bookmarked_sounds = category.bookmarks.all()
bookmark_categories = BookmarkCategory.objects.filter(user=user).annotate(num_bookmarks=Count('bookmarks'))
tvars = {'user': user,
'is_owner': is_owner,
'n_uncat': n_uncat,
'category': category,
'bookmark_categories': bookmark_categories}
tvars.update(paginate(request, bookmarked_sounds, settings.BOOKMARKS_PER_PAGE))

paginator = paginate(request, bookmarked_sounds, settings.BOOKMARKS_PER_PAGE)
page_sounds = Sound.objects.ordered_ids([bookmark.sound_id for bookmark in paginator['page'].object_list])
tvars.update(paginator)
tvars['page_bookmarks_and_sound_objects'] = zip(paginator['page'].object_list, page_sounds)
return render(request, 'bookmarks/bookmarks.html', tvars)


@redirect_if_old_username_or_404
@raise_404_if_user_is_deleted
def bookmarks_for_user(request, username, category_id=None):
Expand Down Expand Up @@ -84,7 +88,25 @@ def delete_bookmark_category(request, category_id):
else:
return HttpResponseRedirect(reverse("bookmarks-for-user", args=[request.user.username]))

@login_required
@transaction.atomic()
def download_bookmark_category(request, category_id):
category = get_object_or_404(BookmarkCategory, id=category_id, user=request.user)
bookmarked_sounds = Bookmark.objects.filter(category_id=category.id).values("sound_id")
sounds_list = Sound.objects.filter(id__in=bookmarked_sounds, processing_state="OK", moderation_state="OK").select_related('user','license')
licenses_url = (reverse('category-licenses', args=[category_id]))
licenses_content = category.get_attribution(sound_qs=sounds_list)
# NOTE: unlike pack downloads, here we are not doing any cache check to avoid consecutive downloads
return download_sounds(licenses_file_url=licenses_url, licenses_file_content=licenses_content, sounds_list=sounds_list)


def bookmark_category_licenses(category_id):
category = get_object_or_404(BookmarkCategory, id=category_id)
attribution = category.get_attribution()
return HttpResponse(attribution, content_type="text/plain")


@login_required
@transaction.atomic()
def edit_bookmark_category(request, category_id):

Expand Down
15 changes: 10 additions & 5 deletions sounds/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1788,16 +1788,21 @@ def invalidate_template_caches(self):
invalidate_template_cache("bw_display_pack", self.id, player_size)
invalidate_template_cache("bw_pack_stats", self.id)

def get_attribution(self):
sounds_list = self.sounds.filter(processing_state="OK",
def get_attribution(self, sound_qs):
#If no queryset of sounds is provided, take it from the pack
if(sound_qs):
sounds_list = sound_qs
else:
sounds_list = self.sounds.filter(processing_state="OK",
moderation_state="OK").select_related('user', 'license')

users = User.objects.filter(sounds__in=sounds_list).distinct()
# Generate text file with license info
licenses = License.objects.filter(sound__pack=self).distinct()
attribution = render_to_string("sounds/pack_attribution.txt",
dict(users=users,
pack=self,
attribution = render_to_string("sounds/multiple_sounds_attribution.txt",
dict(type="Pack",
users=users,
object=self,
licenses=licenses,
sound_list=sounds_list))
return attribution
Expand Down
5 changes: 4 additions & 1 deletion sounds/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,11 @@ def pack_download(request, username, pack_id):
pds.append(PackDownloadSound(sound=sound, license_id=sound.license_id, pack_download=pd))
PackDownloadSound.objects.bulk_create(pds)
cache.set(cache_key, True, 60 * 5) # Don't save downloads for the same user/pack in the next 5 minutes

sounds_list = pack.sounds.filter(processing_state="OK", moderation_state="OK").select_related('user', 'license')
licenses_url = (reverse('pack-licenses', args=[username, pack_id]))
return download_sounds(licenses_url, pack)
licenses_content = pack.get_attribution(sound_qs=sounds_list)
return download_sounds(licenses_url, licenses_content, sounds_list)


def pack_licenses(request, username, pack_id):
Expand Down
7 changes: 4 additions & 3 deletions templates/bookmarks/bookmarks.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ <h4>Bookmark categories</h4>
<li><a href="{% url "bookmarks-for-user-for-category" user.username cat.id %}" {% if category.id == cat.id %}style="font-weight:bold"{% endif %} aria-label="Category: {{cat.name}}">{{cat.name}}</a> <span class="text-grey"> · {{cat.num_bookmarks|bw_intcomma}} bookmark{{ cat.num_bookmarks|pluralize }}</span>
{% if is_owner %}
<a class="cursor-pointer h-spacing-left-1" data-toggle="confirmation-modal" data-modal-confirmation-title="Are you sure you want to remove this bookmark category?" data-modal-confirmation-help-text="Note that all the bookmarks inside this category will also be removed" data-modal-confirmation-url="{% url "delete-bookmark-category" cat.id %}{% if cat.id != category.id %}?next={{request.path}}&page={{current_page}}{% endif %}" title="Remove bookmark category" aria-label="Remove bookmark category">{% bw_icon 'trash' %}</a>
<a class="cursor-pointer h-spacing-left-1" data-toggle="modal-default-with-form" data-modal-content-url="{% url 'edit-bookmark-category' cat.id %}?ajax=1" data-reload-on-success= "true" title="Edit bookmark category" aria-label="Edit bookmark category">{% bw_icon 'edit' %}</a>
<a class="cursor-pointer" data-toggle="modal-default-with-form" data-modal-content-url="{% url 'edit-bookmark-category' cat.id %}?ajax=1" data-reload-on-success= "true" title="Edit bookmark category" aria-label="Edit bookmark category">{% bw_icon 'edit' %}</a>
<a class="cursor-pointer" href="{% url 'download-bookmark-category' cat.id %}" title="Download bookmark category" aria-label="Download bookmark category">{% bw_icon 'download' %}</a>
{% endif %}</li>
{% endfor %}
</ul>
Expand All @@ -36,9 +37,9 @@ <h4><span class="text-light-grey">Category:</span> {% if category %}{{category.n
<div class="v-spacing-top-3">
{% if page.object_list %}
<div class="row">
{% for bookmark in page.object_list %}
{% for bookmark, sound in page_bookmarks_and_sound_objects %}
<div class="col-6 col-md-4">
{% display_sound_small bookmark.sound %}
{% display_sound_small sound %}
{% if is_owner %}
<div class="right v-spacing-4 v-spacing-top-negative-2"><a class="cursor-pointer bw-link--grey" data-toggle="confirmation-modal" data-modal-confirmation-title="Are you sure you want to remove this bookmark from '{{bookmark.category_name_or_uncategorized}}'?" data-modal-confirmation-url="{% url "delete-bookmark" bookmark.id %}?next={{request.path}}&page={{current_page}}" title="Remove from bookmarks" aria-label="Remove from bookmarks">{% bw_icon 'trash' %} Remove bookmark</a></div>
{% endif %}
Expand Down
31 changes: 31 additions & 0 deletions templates/sounds/multiple_sounds_attribution.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% load absurl %}{{type}} downloaded from Freesound
----------------------------------------
{# NOTE: an object can either be a pack of sounds or a bookmark category#}
"{{ object.name }}"

This {{ type }} of sounds contains sounds by the following user{{ users|pluralize }}:
{% for user in users %} - {{user.username}} ( {% absurl 'account' user.username %} ){% endfor %}

{% if type == "Pack" %}You can find this pack online at: {% absurl 'pack' object.user.username object.id %}{% endif %}

{%if object.description %}
{{ type }} description
----------------

{{ object.description }}

{% endif %}
Licenses in this {{type}} (see below for individual sound licenses)
---------------------------------------------------------------

{% for license in licenses %}{{license}}: {{license.deed_url}}
{% endfor %}

Sounds in this {{type}}
-------------------

{% for sound in sound_list %} * {{sound.base_filename_slug}}.{{sound.type}}
* url: {% absurl 'short-sound-link' sound.id %}
* license: {{sound.license}}
{% endfor %}

31 changes: 0 additions & 31 deletions templates/sounds/pack_attribution.txt

This file was deleted.

26 changes: 14 additions & 12 deletions utils/downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,29 @@
from utils.nginxsendfile import prepare_sendfile_arguments_for_sound_download


def download_sounds(licenses_url, pack):
"""
From a list of sounds generates the HttpResponse with the information of
the wav files of the sonds and a text file with the license. This response
def download_sounds(licenses_file_url, licenses_file_content, sounds_list):
"""From a list of sounds generates the HttpResponse with the information of
the wav files of the sounds and a text file with the license. This response
is handled by mod_zipfile of nginx to generate a zip file with the content.
Args:
licenses_file_url (str): url to the sound Pack or BookmarkCategory licenses
licenses_file_content (str): attributions for the different sounds in the Pack or BookmarkCategory
sounds_list (django.db.models.query.QuerySet): list of sounds forming the Pack or BookmarkCategory
Returns:
HttpResponse: information of the wav files of the sounds and a text file with the license
"""
attribution = pack.get_attribution()
license_crc = zlib.crc32(attribution.encode('UTF-8')) & 0xffffffff
license_crc = zlib.crc32(licenses_file_content.encode('UTF-8')) & 0xffffffff
filelist = "%02x %i %s %s\r\n" % (license_crc,
len(attribution.encode('UTF-8')),
licenses_url,
len(licenses_file_content.encode('UTF-8')),
licenses_file_url,
"_readme_and_license.txt")

sounds_list = pack.sounds.filter(processing_state="OK", moderation_state="OK").select_related('user', 'license')

for sound in sounds_list:
if sound.crc == '':
continue
_, name, url = prepare_sendfile_arguments_for_sound_download(sound)
filelist += "%s %i %s %s\r\n" % (sound.crc, sound.filesize, url, name)

response = HttpResponse(filelist, content_type="text/plain")
response['X-Archive-Files'] = 'zip'
return response
Expand Down
4 changes: 3 additions & 1 deletion utils/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ def test_download_sounds(self):
license=License.objects.all()[0],
pack=pack,
md5="fakemd5_%i" % i)
sounds_list = pack.sounds.filter(processing_state="OK", moderation_state="OK").select_related('user', 'license')
licenses_url = (reverse('pack-licenses', args=["testuser", pack.id]))
ret = utils.downloads.download_sounds(licenses_url, pack)
licenses_content = pack.get_attribution(sound_qs=sounds_list)
ret = utils.downloads.download_sounds(licenses_url, licenses_content, sounds_list)
self.assertEqual(ret.status_code, 200)

@override_uploads_path_with_temp_directory
Expand Down

0 comments on commit 762b267

Please sign in to comment.