import logging
import magic
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Button
from crispy_forms.layout import Field
from crispy_forms.layout import Layout
from crispy_forms.layout import Submit
from django import forms
from alphabetlearning.resources.models import Resource
from alphabetlearning.resources.models import ResourceCategory
from alphabetlearning.resources.models import ResourceSubcategory
from alphabetlearning.resources.models import ResourceType
from .models import AGE_RANGE_CHOICES
from .models import CURRICULUM_CHOICES
logger = logging.getLogger(__name__)
ALLOWED_THUMBNAILS = 5
ALLOWED_PDFS = 20
def _create_choices_tuple() -> list[tuple[str, str]]:
"""Returns a list of tuples containing resource subcategory names."""
vals = sorted(ResourceSubcategory.objects.values_list("name", flat=True), key=str)
out = tuple((x, x) for x in vals)
return out
class ResourceCreateForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.add_input(Submit("submit", "Submit"))
self.fields["subcategories"].choices = _create_choices_tuple()
pass
error_css_class = "error"
required_css_class = "required"
name = forms.CharField(
max_length=255,
help_text="Concisely describe what the resource is, aiming for"
" in 35-45 characters. "
"eg: 'Fractions KS2 Worksheet and Answers.'",
)
description = forms.CharField(
max_length=5000,
widget=forms.Textarea,
help_text=(" Use <strong>markdown</strong> here. "
"This is the main (long) description so go for it. "
"5000 characters at your disposal."
),
)
card_description = forms.CharField(
max_length=1000,
widget=forms.Textarea,
help_text=(
"If you enter text here, it will be used in the 'card' description "
"box on the home page. Max 1000 characters."
),
)
resource_type = forms.ModelChoiceField(queryset=ResourceType.objects.all())
age_range = forms.ChoiceField(
choices=AGE_RANGE_CHOICES,
help_text="Try to be accurate in your choice of age range so that your resource "
"shows up in the correct searches. (Although we don't have searches yet!)",
)
curriculum = forms.ChoiceField(
choices=CURRICULUM_CHOICES,
required=False,
)
main_resource_category = forms.ModelChoiceField(
queryset=ResourceCategory.objects.all(),
help_text="Categorise your resource by subject so it shows up in the correct "
"searches. It's a good idea to limit the number of subjects you select "
"to one or two to make your resource easier to find.",
)
subcategories = forms.MultipleChoiceField(
required=False,
)
pdf_files = forms.FileField(
widget=forms.TextInput(
attrs={
"multiple": True,
"type": "File",
"required": True,
},
),
required=False,
help_text="You can multi-select up to 20 .pdf files here.",
label="PDF files",
)
thumbnail_files = forms.FileField(
widget=forms.TextInput(
attrs={
"multiple": True,
"type": "File",
"required": True,
},
),
required=False,
label="Cover images",
help_text="Your cover image will be displayed in the search results and as "
"the first image on your resource page in the preview function. "
"It is important to add an eye catching cover image that gives "
"other teachers an idea about what your resource contains. "
"You can multi-select up to 5 .png or .jpg files here.",
)
pdf_files.widget.attrs.update({"class": "file_upload", "accept": ".pdf"})
thumbnail_files.widget.attrs.update({"class": "file_upload", "accept": ".png,.jpg"})
feature_slot = forms.IntegerField(
min_value=1,
max_value=3,
required=False,
help_text=(
"Please enter either 1, 2 or 3 here. This will dictate where on the page "
"this resource will feature on the main list page."
),
)
def clean_thumbnail_files(self):
thumbnail_files = self.files.getlist("thumbnail_files")
if not thumbnail_files:
raise forms.ValidationError("Please select at least one thumbnail file.")
acceptable = ["image/png", "image/jpeg"]
for f in thumbnail_files:
content_type = magic.from_buffer(f.file.read(), mime=True)
f.file.seek(0)
if content_type not in acceptable:
raise forms.ValidationError("Please select only PNG or JPG files.")
if len(thumbnail_files) > ALLOWED_THUMBNAILS:
raise forms.ValidationError("Please select up to 5 files.")
return thumbnail_files
def clean_pdf_files(self):
pdf_files = self.files.getlist("pdf_files")
if not pdf_files:
raise forms.ValidationError("Please select at least one PDF file.")
acceptable = ["application/pdf"]
for f in pdf_files:
content_type = magic.from_buffer(f.file.read(), mime=True)
f.file.seek(0)
if content_type not in acceptable:
raise forms.ValidationError("Please select only PDF files.")
if len(pdf_files) > ALLOWED_PDFS:
raise forms.ValidationError("Please select up to 20 PDF files.")
return pdf_files
class ResourceUpdateMetadataForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.add_input(Submit("submit", "Submit"))
self.fields["subcategories"].queryset = ResourceSubcategory.objects.all().order_by("name")
error_css_class = "error"
required_css_class = "required"
class Meta:
model = Resource
fields = [
"name",
"description",
"card_description",
"resource_type",
"age_range",
"curriculum",
"main_resource_category",
"subcategories",
"feature_slot",
]
class ResourceUpdatePDFsForm(forms.Form):
def __init__(self, cancel_url: str, *args, **kwargs):
try:
self.resource = kwargs.pop("resource")
except KeyError:
pass
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.layout = Layout(
Field("pdf_files"),
FormActions(
Submit("submit", "Submit", css_class="btn btn-primary"),
Button("cancel", "Cancel", css_class="btn btn-danger", onclick="location.href=''"),
),
)
pdf_files = forms.FileField(
widget=forms.TextInput(
attrs={
"multiple": True,
"type": "File",
"required": True,
},
),
required=False,
help_text="You can multi-select up to 20 .pdf files here.",
label="PDF files",
)
pdf_files.widget.attrs.update({"class": "file_upload", "accept": ".pdf"})
def clean_pdf_files(self):
pdf_files = self.files.getlist("pdf_files")
if not pdf_files:
raise forms.ValidationError("Please select at least one PDF file.")
acceptable = ["application/pdf"]
for f in pdf_files:
content_type = magic.from_buffer(f.file.read(), mime=True)
f.file.seek(0)
if content_type not in acceptable:
raise forms.ValidationError("Please select only PDF files.")
if len(pdf_files) > ALLOWED_PDFS:
raise forms.ValidationError("Please select up to 20 PDF files.")
return pdf_files
class ResourceUpdateThumbnailsForm(forms.Form):
def __init__(self, *args, **kwargs):
try:
self.resource = kwargs.pop("resource")
except KeyError:
pass
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.add_input(Submit("submit", "Submit"))
thumbnail_files = forms.FileField(
widget=forms.TextInput(
attrs={
"multiple": True,
"type": "File",
"required": True,
},
),
required=False,
label="Thumbnail files",
help_text="You can upload 5 files.",
)
thumbnail_files.widget.attrs.update({"class": "file_upload", "accept": ".png,.jpg"})
def clean_thumbnail_files(self):
thumbnail_files = self.files.getlist("thumbnail_files")
if not thumbnail_files:
raise forms.ValidationError("Please select at least one thumbnail file.")
acceptable = ["image/png", "image/jpeg"]
for f in thumbnail_files:
content_type = magic.from_buffer(f.file.read(), mime=True)
f.file.seek(0)
if content_type not in acceptable:
raise forms.ValidationError("Please select only PNG or JPG files.")
if len(thumbnail_files) > ALLOWED_THUMBNAILS:
raise forms.ValidationError("Please select up to 5 files.")
return thumbnail_files