import logging from typing import List, Tuple import magic from crispy_forms.bootstrap import FormActions from crispy_forms.helper import FormHelper from crispy_forms.layout import Submit, Field, Layout, Button from django import forms from pyblackbird_cc.resources.models import Resource, ResourceSubcategory from pyblackbird_cc.resources.models import ResourceCategory from pyblackbird_cc.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.""" return sorted(list(ResourceSubcategory.objects.values_list('name', flat=True)), key=str) 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() 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=" You can (and should) use Markdown here. " "This is your opportunity to clearly explain what your resource " "is all " "about! It’s worth remembering that you are using the space to " "communicate to two " "different audiences. Firstly, think about what fellow teachers " "would like " "to know, such as exactly what the resource contains and how it " "could be used in the classroom. Secondly, the words you include " "on this page are also talking to internal and external search " "engines." " External search engines, like Google, show the first 155 characters " "of the resource description, so make sure you take advantage " "of these " "characters by using lots of relevant keywords as part of an " "enticing pitch.", ) 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=f"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