Oleg Komarov
71a32c1ad8
As specified in #70: Use the latest extension version compatible with a given blender_version, as opposed to the previous behavior when only the latest extension version was checked. Now if there are any compatible versions, the extension will be listed, previously it was listed only if the latest version was compatible. Also this PR uses an opportunity to optimize the db prefetches to avoid 6*N + 1 query problem. Reviewed-on: #86 Reviewed-by: Anna Sirota <railla@noreply.localhost>
129 lines
4.4 KiB
Python
129 lines
4.4 KiB
Python
import logging
|
|
|
|
from rest_framework.response import Response
|
|
from rest_framework import serializers
|
|
from rest_framework.views import APIView
|
|
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from common.compare import is_in_version_range, version
|
|
from extensions.models import Extension
|
|
from extensions.utils import clean_json_dictionary_from_optional_fields
|
|
|
|
|
|
from constants.base import (
|
|
EXTENSION_TYPE_SLUGS_SINGULAR,
|
|
)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class ListedExtensionsSerializer(serializers.ModelSerializer):
|
|
error_messages = {
|
|
"invalid_version": "Invalid version: use full semantic versioning like 4.2.0."
|
|
}
|
|
|
|
class Meta:
|
|
model = Extension
|
|
fields = ()
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.request = kwargs.pop('request', None)
|
|
self.blender_version = kwargs.pop('blender_version', None)
|
|
self._validate()
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def _validate(self):
|
|
if self.blender_version is None:
|
|
return
|
|
try:
|
|
version(self.blender_version)
|
|
except ValidationError:
|
|
self.fail('invalid_version')
|
|
|
|
def to_representation(self, instance):
|
|
matching_version = None
|
|
# avoid triggering additional db queries, reuse the prefetched queryset
|
|
versions = [
|
|
v
|
|
for v in instance.versions.all()
|
|
if v.file and v.file.status in instance.valid_file_statuses
|
|
]
|
|
if not versions:
|
|
return None
|
|
versions = sorted(versions, key=lambda v: v.date_created, reverse=True)
|
|
if self.blender_version:
|
|
for v in versions:
|
|
if is_in_version_range(
|
|
self.blender_version,
|
|
v.blender_version_min,
|
|
v.blender_version_max,
|
|
):
|
|
matching_version = v
|
|
break
|
|
else:
|
|
# same as latest_version, but without triggering a new queryset
|
|
matching_version = versions[0]
|
|
|
|
if not matching_version:
|
|
return None
|
|
|
|
data = {
|
|
'id': instance.extension_id,
|
|
'schema_version': matching_version.schema_version,
|
|
'name': instance.name,
|
|
'version': matching_version.version,
|
|
'tagline': matching_version.tagline,
|
|
'archive_hash': matching_version.file.original_hash,
|
|
'archive_size': matching_version.file.size_bytes,
|
|
'archive_url': self.request.build_absolute_uri(matching_version.download_url),
|
|
'type': EXTENSION_TYPE_SLUGS_SINGULAR.get(instance.type),
|
|
'blender_version_min': matching_version.blender_version_min,
|
|
'blender_version_max': matching_version.blender_version_max,
|
|
'website': self.request.build_absolute_uri(instance.get_absolute_url()),
|
|
# avoid triggering additional db queries, reuse the prefetched queryset
|
|
'maintainer': str(instance.authors.all()[0]),
|
|
'license': [license_iter.slug for license_iter in matching_version.licenses.all()],
|
|
'permissions': [permission.slug for permission in matching_version.permissions.all()],
|
|
# TODO: handle copyright
|
|
'tags': [str(tag) for tag in matching_version.tags.all()],
|
|
}
|
|
|
|
return clean_json_dictionary_from_optional_fields(data)
|
|
|
|
|
|
class ExtensionsAPIView(APIView):
|
|
serializer_class = ListedExtensionsSerializer
|
|
|
|
@extend_schema(
|
|
parameters=[
|
|
OpenApiParameter(
|
|
name="blender_version",
|
|
description=("Blender version to check for compatibility"),
|
|
type=str,
|
|
)
|
|
]
|
|
)
|
|
def get(self, request):
|
|
blender_version = request.GET.get('blender_version')
|
|
qs = Extension.objects.listed.prefetch_related(
|
|
'authors',
|
|
'versions',
|
|
'versions__file',
|
|
'versions__licenses',
|
|
'versions__permissions',
|
|
'versions__tags',
|
|
).all()
|
|
serializer = self.serializer_class(
|
|
qs, blender_version=blender_version, request=request, many=True
|
|
)
|
|
data = [e for e in serializer.data if e is not None]
|
|
return Response(
|
|
{
|
|
# TODO implement extension blocking by moderators
|
|
'blocklist': [],
|
|
'data': data,
|
|
'version': 'v1',
|
|
}
|
|
)
|