diff --git a/accounts/urls.py b/accounts/urls.py index 3821d3b6e..7a7dba451 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -96,7 +96,8 @@ path('bookmarks/category//delete/', bookmarks.delete_bookmark_category, name="delete-bookmark-category"), path('bookmarks//delete/', bookmarks.delete_bookmark, name="delete-bookmark"), path('bookmarks/category//edit_modal/', bookmarks.edit_bookmark_category, name="edit-bookmark-category"), - + path('bookmarks//download/', bookmarks.download_bookmark_category, name="download-bookmark-category"), + path('bookmarks//licenses/', bookmarks.bookmark_category_licenses, name="category-licenses"), path('messages/', messages.inbox, name='messages'), path('messages/sent/', messages.sent_messages, name='messages-sent'), diff --git a/apiv2/views.py b/apiv2/views.py index f9d2bd926..540ce069f 100755 --- a/apiv2/views.py +++ b/apiv2/views.py @@ -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) ################## diff --git a/bookmarks/models.py b/bookmarks/models.py index d12332bd7..4ec51b7b6 100755 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -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): @@ -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): diff --git a/bookmarks/views.py b/bookmarks/views.py index 68712058f..7eaac45f2 100755 --- a/bookmarks/views.py +++ b/bookmarks/views.py @@ -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 @@ -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): @@ -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): diff --git a/sounds/models.py b/sounds/models.py index 072acaee9..204bb7279 100644 --- a/sounds/models.py +++ b/sounds/models.py @@ -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 diff --git a/sounds/views.py b/sounds/views.py index 0298c4571..db569cd55 100644 --- a/sounds/views.py +++ b/sounds/views.py @@ -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): diff --git a/templates/bookmarks/bookmarks.html b/templates/bookmarks/bookmarks.html index a1cf1040e..c2a7fa335 100644 --- a/templates/bookmarks/bookmarks.html +++ b/templates/bookmarks/bookmarks.html @@ -20,7 +20,8 @@

Bookmark categories

  • {{cat.name}} ยท {{cat.num_bookmarks|bw_intcomma}} bookmark{{ cat.num_bookmarks|pluralize }} {% if is_owner %} {% bw_icon 'trash' %} - {% bw_icon 'edit' %} + {% bw_icon 'edit' %} + {% bw_icon 'download' %} {% endif %}
  • {% endfor %} @@ -36,9 +37,9 @@

    Category: {% if category %}{{category.n
    {% if page.object_list %}
    - {% for bookmark in page.object_list %} + {% for bookmark, sound in page_bookmarks_and_sound_objects %}
    - {% display_sound_small bookmark.sound %} + {% display_sound_small sound %} {% if is_owner %} {% endif %} diff --git a/templates/sounds/multiple_sounds_attribution.txt b/templates/sounds/multiple_sounds_attribution.txt new file mode 100644 index 000000000..179cd9e9c --- /dev/null +++ b/templates/sounds/multiple_sounds_attribution.txt @@ -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 %} + diff --git a/templates/sounds/pack_attribution.txt b/templates/sounds/pack_attribution.txt deleted file mode 100644 index 3849c00b2..000000000 --- a/templates/sounds/pack_attribution.txt +++ /dev/null @@ -1,31 +0,0 @@ -{% load absurl %}Sound pack downloaded from Freesound ----------------------------------------- - -"{{ pack.name }}" - -This pack of sounds contains sounds by the following user{{ users|pluralize }}: -{% for user in users %} - {{user.username}} ( {% absurl 'account' user.username %} ){% endfor %} - -{% if pack %}You can find this pack online at: {% absurl 'pack' pack.user.username pack.id %}{% endif %} - -{%if pack.description %} -Pack description ----------------- - -{{ pack.description }} - -{% endif %} -Licenses in this pack (see below for individual sound licenses) ---------------------------------------------------------------- - -{% for license in licenses %}{{license}}: {{license.deed_url}} -{% endfor %} - -Sounds in this pack -------------------- - -{% for sound in sound_list %} * {{sound.base_filename_slug}}.{{sound.type}} - * url: {% absurl 'short-sound-link' sound.id %} - * license: {{sound.license}} -{% endfor %} - diff --git a/utils/downloads.py b/utils/downloads.py index 28f9061c9..da02628c5 100644 --- a/utils/downloads.py +++ b/utils/downloads.py @@ -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 diff --git a/utils/tests/tests.py b/utils/tests/tests.py index bebbe49a2..28dc237cc 100644 --- a/utils/tests/tests.py +++ b/utils/tests/tests.py @@ -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