extensions-website/extensions/forms.py
Anna Sirota caae613747 Make it possible to fully delete unlisted/unrated extensions and versions (#81)
* removes all soft-deletion;
* shows a "Delete extension" button on the draft page in case it can be deleted;
* shows a "Delete version" button on the version page in case it can be deleted;
* a version can be deleted if
  * its file isn't approved, and it doesn't have any ratings;
* an extension can be deleted if
  * it's not listed, and doesn't have any ratings or abuse reports;
  * all it's versions can also be deleted;
* changes default `File.status` from `APPROVED` to `AWAITING_REVIEW`
  With version's file status being `APPROVED` by default, a version can never be deleted, even when the extension is still a draft.
  This change doesn't affect the approval process because
   * when an extension is approved its latest version becomes approved automatically (no change here);
   * when a new version is uploaded to an approved extension, it's approved automatically (this is new).

This allows authors to delete their drafts, freeing the extension slug and making it possible to re-upload the same file.
This also makes it possible to easily fix mistakes during the drafting of a new extension (e.g. delete a version and re-upload it without bumping a version for each typo/mistake in packaging and so on).
(see #78 and #63)

Reviewed-on: #81
2024-04-19 11:00:13 +02:00

155 lines
4.7 KiB
Python

import logging
from django import forms
from django.utils.translation import gettext_lazy as _
from files.validators import FileMIMETypeValidator
from constants.base import ALLOWED_PREVIEW_MIMETYPES
import extensions.models
import files.models
logger = logging.getLogger(__name__)
class EditPreviewForm(forms.ModelForm):
class Meta:
model = extensions.models.Extension.previews.through
fields = (
'caption',
'position',
)
widgets = {
'position': forms.HiddenInput(attrs={'data-position': ''}),
}
def __init__(self, *args, **kwargs):
self.base_fields['caption'].widget.attrs.update({'placeholder': 'Describe the preview'})
super().__init__(*args, **kwargs)
EditPreviewFormSet = forms.inlineformset_factory(
extensions.models.Extension,
extensions.models.Extension.previews.through,
form=EditPreviewForm,
extra=0,
)
class AddPreviewFileForm(forms.ModelForm):
msg_unexpected_file_type = _('Choose a JPEG, PNG or WebP image, or an MP4 video')
class Meta:
model = files.models.File
fields = ('caption', 'source')
source = forms.FileField(
allow_empty_file=False,
required=True,
validators=[
FileMIMETypeValidator(
allowed_mimetypes=ALLOWED_PREVIEW_MIMETYPES,
message=msg_unexpected_file_type,
),
],
widget=forms.ClearableFileInput(
attrs={'accept': ','.join(ALLOWED_PREVIEW_MIMETYPES)},
),
)
caption = forms.CharField(max_length=255, required=False)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
self.extension = kwargs.pop('extension')
self.base_fields['caption'].widget.attrs.update({'placeholder': 'Describe the preview'})
super().__init__(*args, **kwargs)
def save(self, *args, **kwargs):
"""Save Preview from the cleaned form data."""
# If file with this hash was already uploaded by the same user, return it
hash_ = self.instance.generate_hash(self.instance.source)
model = self.instance.__class__
existing_image = model.objects.filter(original_hash=hash_, user=self.request.user).first()
if (
existing_image
and not existing_image.extension_preview.filter(extension_id=self.extension.id).count()
):
logger.warning('Found an existing %s pk=%s', model, existing_image.pk)
self.instance = existing_image
# Fill in missing fields from request and the source file
self.instance.user = self.request.user
instance = super().save(*args, **kwargs)
# Create extension preview and save caption to it
instance.extension_preview.create(
caption=self.cleaned_data['caption'],
extension=self.extension,
)
return instance
class AddPreviewModelFormSet(forms.BaseModelFormSet):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
self.extension = kwargs.pop('extension')
super().__init__(*args, **kwargs)
# Make sure formset doesn't attempt to select existing File records
self.queryset = files.models.File.objects.none()
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super().get_form_kwargs(*args, **kwargs)
form_kwargs['request'] = self.request
form_kwargs['extension'] = self.extension
return form_kwargs
AddPreviewFormSet = forms.modelformset_factory(
files.models.File,
form=AddPreviewFileForm,
formset=AddPreviewModelFormSet,
extra=1,
)
class ExtensionUpdateForm(forms.ModelForm):
class Meta:
model = extensions.models.Extension
fields = (
'description',
'support',
)
class ExtensionDeleteForm(forms.ModelForm):
class Meta:
model = extensions.models.Extension
fields = []
class VersionForm(forms.ModelForm):
class Meta:
model = extensions.models.Version
fields = {'file', 'release_notes'}
def __init__(self, *args, **kwargs):
"""Limit 'file' choices to the initial file value."""
super().__init__(*args, **kwargs)
# Mark 'file' field as disabled so that Django form allows using its initial value.
self.fields['file'].disabled = True
def clean_file(self, *args, **kwargs):
"""Return file that was passed to the form via the initial values.
This ensures that it doesn't have to be supplied by the form data.
"""
return self.initial['file']
class VersionDeleteForm(forms.ModelForm):
class Meta:
model = extensions.models.Version
fields = []