Multi-platform: support multiple files per version #201
@ -1,4 +1,5 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
from urllib.parse import urlencode
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
@ -754,14 +755,55 @@ class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
|||||||
permissions.append({'slug': slug, 'reason': reason, 'name': all_permission_names[slug]})
|
permissions.append({'slug': slug, 'reason': reason, 'name': all_permission_names[slug]})
|
||||||
return permissions
|
return permissions
|
||||||
|
|
||||||
|
def _get_download_name(self) -> str:
|
||||||
|
"""Return a file name for downloads."""
|
||||||
|
replace_char = f'{self}'.replace('.', '-')
|
||||||
Oleg-Komarov marked this conversation as resolved
Outdated
|
|||||||
|
return f'{utils.slugify(replace_char)}.zip'
|
||||||
|
|
||||||
Oleg-Komarov marked this conversation as resolved
Outdated
Anna Sirota
commented
to avoid making platform into file ext, maybe to avoid making platform into file ext, maybe `-`?
|
|||||||
|
def get_download_url(self, platform=None, append_repository_and_compatibility=True) -> str:
|
||||||
|
filename = f'{self.extension.type_slug_singular}-{self.extension.slug}-v{self.version}.zip'
|
||||||
|
if platform:
|
||||||
|
download_url = reverse(
|
||||||
|
'extensions:version-platform-download',
|
||||||
|
kwargs={
|
||||||
|
'type_slug': self.extension.type_slug,
|
||||||
|
'slug': self.extension.slug,
|
||||||
|
'version': self.version,
|
||||||
|
'platform': platform,
|
||||||
|
'filename': filename,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
download_url = reverse(
|
||||||
|
'extensions:version-download',
|
||||||
|
kwargs={
|
||||||
|
'type_slug': self.extension.type_slug,
|
||||||
|
'slug': self.extension.slug,
|
||||||
|
'version': self.version,
|
||||||
|
'filename': filename,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if append_repository_and_compatibility:
|
||||||
|
params = {
|
||||||
|
'repository': '/api/v1/extensions/',
|
||||||
|
'blender_version_min': self.blender_version_min,
|
||||||
|
}
|
||||||
|
if self.blender_version_max:
|
||||||
|
params['blender_version_max'] = self.blender_version_max
|
||||||
|
if platforms := self.platforms.all():
|
||||||
|
params['platforms'] = ','.join([p.slug for p in platforms])
|
||||||
|
query_string = urlencode(params)
|
||||||
|
download_url += f'?{query_string}'
|
||||||
|
return download_url
|
||||||
|
|
||||||
def get_download_list(self) -> List[dict]:
|
def get_download_list(self) -> List[dict]:
|
||||||
files = list(self.files.all())
|
files = list(self.files.all())
|
||||||
Oleg-Komarov marked this conversation as resolved
Outdated
Anna Sirota
commented
Would it make sense to use file hash in the URL? Would it make sense to use file hash in the URL?
This would allow skipping the file-selecting logic in the `public` view altogether by using the hash to retrieve the file directly.
|
|||||||
if len(files) == 1:
|
if len(files) == 1:
|
||||||
file = files[0]
|
file = files[0]
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'name': file.download_name(),
|
'name': self._get_download_name(),
|
||||||
'url': file.download_url(platform=None),
|
'url': self.get_download_url(platform=None),
|
||||||
'size': file.size_bytes,
|
'size': file.size_bytes,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -778,8 +820,8 @@ class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'platform': p,
|
'platform': p,
|
||||||
'name': file.download_name(),
|
'name': self._get_download_name(),
|
||||||
'url': file.download_url(platform=p),
|
'url': self.get_download_url(platform=p),
|
||||||
'size': file.size_bytes,
|
'size': file.size_bytes,
|
||||||
}
|
}
|
||||||
for p, file in platform2file.items()
|
for p, file in platform2file.items()
|
||||||
|
@ -92,7 +92,7 @@ class ListedExtensionsSerializer(serializers.ModelSerializer):
|
|||||||
'archive_hash': matching_file.original_hash,
|
'archive_hash': matching_file.original_hash,
|
||||||
'archive_size': matching_file.size_bytes,
|
'archive_size': matching_file.size_bytes,
|
||||||
'archive_url': self.request.build_absolute_uri(
|
'archive_url': self.request.build_absolute_uri(
|
||||||
matching_file.download_url(
|
matching_version.download_url(
|
||||||
platform=self.platform,
|
platform=self.platform,
|
||||||
append_repository_and_compatibility=False,
|
append_repository_and_compatibility=False,
|
||||||
)
|
)
|
||||||
|
@ -63,9 +63,12 @@ def extension_version_download(request, type_slug, slug, version, filename):
|
|||||||
def extension_version_platform_download(request, type_slug, slug, version, platform, filename):
|
def extension_version_platform_download(request, type_slug, slug, version, platform, filename):
|
||||||
"""Download an extension version and count downloads.
|
"""Download an extension version and count downloads.
|
||||||
|
|
||||||
|
This method processes urls constructed by Version.get_download_list.
|
||||||
|
|
||||||
The `filename` parameter is used to pass a file name ending with `.zip`.
|
The `filename` parameter is used to pass a file name ending with `.zip`.
|
||||||
This is a convention Blender uses to initiate an extension installation on an HTML anchor
|
This is a convention Blender uses to initiate an extension installation on an HTML anchor
|
||||||
drag&drop.
|
drag&drop.
|
||||||
|
Also see $arg_filename usage in playbooks/templates/nginx/http.conf
|
||||||
"""
|
"""
|
||||||
extension_version = get_object_or_404(Version, extension__slug=slug, version=version)
|
extension_version = get_object_or_404(Version, extension__slug=slug, version=version)
|
||||||
ExtensionDownload.create_from_request(request, object_id=extension_version.extension_id)
|
ExtensionDownload.create_from_request(request, object_id=extension_version.extension_id)
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from urllib.parse import urlencode
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from common.model_mixins import CreatedModifiedMixin, TrackChangesMixin
|
from common.model_mixins import CreatedModifiedMixin, TrackChangesMixin
|
||||||
from files.utils import get_sha256, guess_mimetype_from_ext, get_thumbnail_upload_to
|
from files.utils import get_sha256, guess_mimetype_from_ext, get_thumbnail_upload_to
|
||||||
@ -204,49 +202,6 @@ class File(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
|||||||
'tags': data.get('tags'),
|
'tags': data.get('tags'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def download_name(self) -> str:
|
|
||||||
"""Return a file name for downloads."""
|
|
||||||
version = self.version.first()
|
|
||||||
replace_char = f'{version}'.replace('.', '-')
|
|
||||||
return f'{utils.slugify(replace_char)}.zip'
|
|
||||||
|
|
||||||
def download_url(self, platform=None, append_repository_and_compatibility=True) -> str:
|
|
||||||
filename = self.download_name()
|
|
||||||
version = self.version.first()
|
|
||||||
if platform:
|
|
||||||
download_url = reverse(
|
|
||||||
'extensions:version-platform-download',
|
|
||||||
kwargs={
|
|
||||||
'type_slug': version.extension.type_slug,
|
|
||||||
'slug': version.extension.slug,
|
|
||||||
'version': version.version,
|
|
||||||
'platform': platform,
|
|
||||||
'filename': filename,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
download_url = reverse(
|
|
||||||
'extensions:version-download',
|
|
||||||
kwargs={
|
|
||||||
'type_slug': version.extension.type_slug,
|
|
||||||
'slug': version.extension.slug,
|
|
||||||
'version': version.version,
|
|
||||||
'filename': filename,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if append_repository_and_compatibility:
|
|
||||||
params = {
|
|
||||||
'repository': '/api/v1/extensions/',
|
|
||||||
'blender_version_min': version.blender_version_min,
|
|
||||||
}
|
|
||||||
if version.blender_version_max:
|
|
||||||
params['blender_version_max'] = version.blender_version_max
|
|
||||||
if platforms := self.platforms():
|
|
||||||
params['platforms'] = ','.join(platforms)
|
|
||||||
query_string = urlencode(params)
|
|
||||||
download_url += f'?{query_string}'
|
|
||||||
return download_url
|
|
||||||
|
|
||||||
def get_thumbnail_of_size(self, size_key: str) -> str:
|
def get_thumbnail_of_size(self, size_key: str) -> str:
|
||||||
"""Return absolute path portion of the URL of a thumbnail of this file.
|
"""Return absolute path portion of the URL of a thumbnail of this file.
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class TestTasks(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
response = self.client.get(version.extension.get_versions_url())
|
response = self.client.get(version.extension.get_versions_url())
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
response = self.client.get(version.files.first().download_url())
|
response = self.client.get(version.download_url())
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response['Location'],
|
response['Location'],
|
||||||
|
Loading…
Reference in New Issue
Block a user
The
filename
belowfilename = f'{self.extension.type_slug_singular}-{self.extension.slug}-v{self.version}.zip'
looks like a better name: doesn't rely on
self.__str__
.It also looks like these file names should be the same in both places.