extensions-website/extensions/views/manage.py

353 lines
11 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 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
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(self.request.user).prefetch_related(
'authors',
'preview_set',
'preview_set__file',
'ratings',
'team',
'versions',
'versions__file',
'versions__tags',
)
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.DRAFT:
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.DRAFT:
return "Updated successfully"
return "Submitted to the Approval Queue"
def test_func(self) -> bool:
return self.extension.status == Extension.STATUSES.DRAFT
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()