389 lines
13 KiB
Python
389 lines
13 KiB
Python
"""Contains views allowing developers to manage their add-ons."""
|
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
|
from django.contrib.messages.views import SuccessMessageMixin
|
|
from django.db import transaction
|
|
from django.shortcuts import get_object_or_404, redirect, reverse
|
|
from django.views.generic import DetailView, ListView
|
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
|
|
|
|
|
|
from .mixins import (
|
|
ExtensionQuerysetMixin,
|
|
OwnsFileMixin,
|
|
MaintainedExtensionMixin,
|
|
DraftVersionMixin,
|
|
)
|
|
from extensions.forms import (
|
|
ExtensionDeleteForm,
|
|
ExtensionUpdateForm,
|
|
VersionDeleteForm,
|
|
VersionForm,
|
|
)
|
|
from extensions.models import Extension, Version
|
|
from files.forms import FileForm
|
|
from files.models import File
|
|
from stats.models import ExtensionView
|
|
import ratings.models
|
|
|
|
|
|
class ExtensionDetailView(ExtensionQuerysetMixin, DetailView):
|
|
model = Extension
|
|
context_object_name = 'extension'
|
|
template_name = 'extensions/detail.html'
|
|
|
|
def get_queryset(self):
|
|
"""Allow logged in users to view unlisted add-ons in certain conditions.
|
|
|
|
* maintainers should be able to preview their yet unlisted add-ons;
|
|
* staff should be able to preview yet unlisted add-ons;
|
|
"""
|
|
return self.get_extension_queryset().prefetch_related(
|
|
'authors',
|
|
'ratings',
|
|
'ratings__user',
|
|
'versions',
|
|
'versions__file',
|
|
'versions__file__validation',
|
|
'versions__permissions',
|
|
)
|
|
|
|
def get_object(self, queryset=None):
|
|
"""Record a page view when returning the Extension object."""
|
|
obj = super().get_object(queryset=queryset)
|
|
if obj.is_listed and (
|
|
self.request.user.is_anonymous or not obj.has_maintainer(self.request.user)
|
|
):
|
|
ExtensionView.create_from_request(self.request, object_id=obj.pk)
|
|
return obj
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
if self.request.user.is_authenticated:
|
|
context['my_rating'] = ratings.models.Rating.get_for(
|
|
self.request.user.pk, self.object.pk
|
|
)
|
|
extension = context['object']
|
|
# Add the image for "og:image" meta to the context
|
|
if extension.featured_image and extension.featured_image.is_listed:
|
|
context['default_image_path'] = extension.featured_image.thumbnail_1080p_url
|
|
return context
|
|
|
|
|
|
class VersionsView(ExtensionQuerysetMixin, ListView):
|
|
model = Version
|
|
paginate_by = 15
|
|
|
|
def get_queryset(self):
|
|
"""Allow logged in users to view unlisted versions in certain conditions.
|
|
|
|
* maintainers should be able to preview their yet unlisted versions;
|
|
* staff should be able to preview yet unlisted versions;
|
|
"""
|
|
self.extension_queryset = self.get_extension_queryset()
|
|
self.extension = get_object_or_404(self.extension_queryset, slug=self.kwargs['slug'])
|
|
queryset = self.extension.versions
|
|
if self.request.user.is_staff or self.extension.has_maintainer(self.request.user):
|
|
return queryset.all()
|
|
return queryset.listed
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['extension'] = self.extension
|
|
return context
|
|
|
|
|
|
class ManageListView(LoginRequiredMixin, ListView):
|
|
model = Extension
|
|
paginate_by = 15
|
|
template_name = 'extensions/manage/list.html'
|
|
|
|
def get_queryset(self):
|
|
return Extension.objects.authored_by(user_id=self.request.user.pk)
|
|
|
|
|
|
class UpdateExtensionView(
|
|
LoginRequiredMixin,
|
|
MaintainedExtensionMixin,
|
|
SuccessMessageMixin,
|
|
UpdateView,
|
|
):
|
|
model = Extension
|
|
template_name = 'extensions/manage/update.html'
|
|
form_class = ExtensionUpdateForm
|
|
success_message = "Updated successfully"
|
|
|
|
def get_form_kwargs(self):
|
|
"""Pass request object to the form."""
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['request'] = self.request
|
|
return kwargs
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
extension = self.extension
|
|
if extension.status == extension.STATUSES.INCOMPLETE:
|
|
return redirect('extensions:draft', slug=extension.slug, type_slug=extension.type_slug)
|
|
else:
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
self.object.refresh_from_db()
|
|
return self.object.get_manage_url()
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context['edit_preview_formset'] = context['form'].edit_preview_formset
|
|
context['add_preview_formset'] = context['form'].add_preview_formset
|
|
context['featured_image_form'] = context['form'].featured_image_form
|
|
context['icon_form'] = context['form'].icon_form
|
|
return context
|
|
|
|
@transaction.atomic
|
|
def form_valid(self, *args, **kwargs):
|
|
return super().form_valid(*args, **kwargs)
|
|
|
|
|
|
class DeleteExtensionView(
|
|
LoginRequiredMixin,
|
|
UserPassesTestMixin,
|
|
DeleteView,
|
|
):
|
|
model = Extension
|
|
template_name = 'extensions/confirm_delete.html'
|
|
form_class = ExtensionDeleteForm
|
|
|
|
def get_success_url(self):
|
|
return reverse('extensions:manage-list')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context['extension_name'] = self.object.name
|
|
context['confirm_url'] = self.object.get_delete_url()
|
|
return context
|
|
|
|
def test_func(self) -> bool:
|
|
obj = self.get_object()
|
|
# Only maintainers allowed
|
|
if not obj.has_maintainer(self.request.user):
|
|
return False
|
|
# Unless this extension cannot be deleted anymore
|
|
cannot_be_deleted_reasons = obj.cannot_be_deleted_reasons
|
|
if len(cannot_be_deleted_reasons) > 0:
|
|
return False
|
|
return True
|
|
|
|
|
|
class VersionDeleteView(
|
|
LoginRequiredMixin,
|
|
MaintainedExtensionMixin,
|
|
UserPassesTestMixin,
|
|
DeleteView,
|
|
):
|
|
model = Version
|
|
template_name = 'extensions/version_confirm_delete.html'
|
|
form_class = VersionDeleteForm
|
|
|
|
def get_success_url(self):
|
|
return reverse(
|
|
'extensions:manage-versions',
|
|
kwargs={
|
|
'type_slug': self.kwargs['type_slug'],
|
|
'slug': self.kwargs['slug'],
|
|
},
|
|
)
|
|
|
|
def get_object(self, queryset=None):
|
|
return get_object_or_404(
|
|
Version,
|
|
extension__slug=self.kwargs['slug'],
|
|
pk=self.kwargs['pk'],
|
|
)
|
|
|
|
def _get_version_from_id(self):
|
|
version_id = self.kwargs['pk']
|
|
version = self.extension.versions.filter(id=version_id).first()
|
|
if version is None:
|
|
raise RuntimeError(
|
|
f'Could not find version {version_id} for extension {self.extension.id}'
|
|
)
|
|
return version
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
version = self._get_version_from_id()
|
|
context['extension_name'] = self.extension.name
|
|
context['version'] = version.version
|
|
context['confirm_url'] = version.get_delete_url()
|
|
return context
|
|
|
|
def test_func(self) -> bool:
|
|
obj = self.get_object()
|
|
# Unless this version cannot be deleted anymore
|
|
cannot_be_deleted_reasons = obj.cannot_be_deleted_reasons
|
|
if len(cannot_be_deleted_reasons) > 0:
|
|
return False
|
|
return True
|
|
|
|
|
|
class ManageVersionsView(
|
|
LoginRequiredMixin,
|
|
MaintainedExtensionMixin,
|
|
VersionsView,
|
|
):
|
|
pass
|
|
|
|
|
|
class NewVersionView(
|
|
LoginRequiredMixin,
|
|
MaintainedExtensionMixin,
|
|
CreateView,
|
|
):
|
|
"""Upload a file for a new version of existing extension."""
|
|
|
|
model = File
|
|
template_name = 'extensions/submit.html'
|
|
form_class = FileForm
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['extension'] = self.extension
|
|
return ctx
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['request'] = self.request
|
|
kwargs['extension'] = self.extension
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
return reverse(
|
|
'extensions:new-version-finalise',
|
|
kwargs={
|
|
'type_slug': self.extension.type_slug,
|
|
'slug': self.extension.slug,
|
|
'pk': self.object.pk,
|
|
},
|
|
)
|
|
|
|
|
|
class NewVersionFinalizeView(LoginRequiredMixin, OwnsFileMixin, CreateView):
|
|
"""Finalise a new version of existing extension and send it to review."""
|
|
|
|
template_name = 'extensions/new_version_finalise.html'
|
|
form_class = VersionForm
|
|
|
|
def _get_extension(self) -> 'Extension':
|
|
return get_object_or_404(Extension, slug=self.kwargs['slug'])
|
|
|
|
def _get_version(self, extension) -> 'Version':
|
|
return Version.objects.update_or_create(
|
|
extension=extension, file=self.file, **self.file.parsed_version_fields
|
|
)[0]
|
|
|
|
def get_form_kwargs(self):
|
|
form_kwargs = super().get_form_kwargs()
|
|
form_kwargs['instance'] = self._get_version(self.extension)
|
|
return form_kwargs
|
|
|
|
def get_initial(self):
|
|
"""Return initial values for the version, based on the file."""
|
|
initial = super().get_initial()
|
|
initial['file'] = self.file
|
|
initial.update(**self.file.parsed_version_fields)
|
|
return initial
|
|
|
|
def get_success_url(self):
|
|
return self.extension.get_manage_versions_url()
|
|
|
|
|
|
class UpdateVersionView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
|
"""Update release notes for an existing version."""
|
|
|
|
template_name = 'extensions/new_version_finalise.html'
|
|
model = Version
|
|
fields = ['blender_version_max', 'release_notes']
|
|
|
|
def get_success_url(self):
|
|
return reverse(
|
|
'extensions:versions',
|
|
kwargs={
|
|
'type_slug': self.object.extension.type_slug,
|
|
'slug': self.object.extension.slug,
|
|
},
|
|
)
|
|
|
|
def test_func(self) -> bool:
|
|
# Only maintainers are allowed to perform this
|
|
return self.get_object().extension.has_maintainer(self.request.user)
|
|
|
|
|
|
class DraftExtensionView(
|
|
LoginRequiredMixin,
|
|
MaintainedExtensionMixin,
|
|
DraftVersionMixin,
|
|
UserPassesTestMixin,
|
|
SuccessMessageMixin,
|
|
FormView,
|
|
):
|
|
template_name = 'extensions/draft_finalise.html'
|
|
form_class = VersionForm
|
|
|
|
@property
|
|
def success_message(self) -> str:
|
|
if self.extension.status == Extension.STATUSES.INCOMPLETE:
|
|
return "Updated successfully"
|
|
return "Submitted to the Approval Queue"
|
|
|
|
def test_func(self) -> bool:
|
|
return self.extension.status == Extension.STATUSES.INCOMPLETE
|
|
|
|
def get_form_kwargs(self):
|
|
form_kwargs = super().get_form_kwargs()
|
|
form_kwargs['instance'] = self.extension.versions.first()
|
|
return form_kwargs
|
|
|
|
def get_initial(self):
|
|
"""Return initial values for the version, based on the file."""
|
|
initial = super().get_initial()
|
|
if self.version:
|
|
initial['file'] = self.version.file
|
|
initial.update(**self.version.file.parsed_version_fields)
|
|
return initial
|
|
|
|
def get_context_data(self, form=None, extension_form=None, **kwargs):
|
|
"""Add all the additional forms to the context."""
|
|
context = super().get_context_data(**kwargs)
|
|
if not extension_form:
|
|
extension_form = ExtensionUpdateForm(instance=self.extension, request=self.request)
|
|
context['extension_form'] = extension_form
|
|
context['edit_preview_formset'] = extension_form.edit_preview_formset
|
|
context['add_preview_formset'] = extension_form.add_preview_formset
|
|
context['featured_image_form'] = extension_form.featured_image_form
|
|
context['icon_form'] = extension_form.icon_form
|
|
return context
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
"""Handle bound forms and valid/invalid logic with the extra forms."""
|
|
form = self.get_form()
|
|
extension_form = ExtensionUpdateForm(
|
|
self.request.POST, self.request.FILES, instance=self.extension, request=self.request
|
|
)
|
|
if form.is_valid() and extension_form.is_valid():
|
|
return self.form_valid(form, extension_form)
|
|
return self.form_invalid(form, extension_form)
|
|
|
|
@transaction.atomic
|
|
def form_valid(self, form, extension_form):
|
|
"""Save all the forms in correct order.
|
|
|
|
Extension must be saved first.
|
|
"""
|
|
extension_form.save()
|
|
form.save()
|
|
return super().form_valid(form)
|
|
|
|
def form_invalid(self, form, extension_form):
|
|
return self.render_to_response(self.get_context_data(form, extension_form))
|
|
|
|
def get_success_url(self):
|
|
return self.extension.get_manage_url()
|