forms.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. from django import forms
  2. from django.core.files.base import ContentFile
  3. from django.utils.translation import gettext, gettext_lazy as _
  4. from mptt.forms import TreeNodeChoiceField
  5. from ..models import Theme, Css
  6. from .css import create_css, get_next_css_order
  7. from .media import create_media
  8. from .utils import get_file_hash
  9. from .validators import validate_css_name
  10. class ThemeChoiceField(TreeNodeChoiceField):
  11. level_indicator = "- - "
  12. def __init__(self, *args, **kwargs):
  13. kwargs.setdefault("queryset", Theme.objects.all())
  14. kwargs.setdefault("empty_label", _("No parent"))
  15. super().__init__(*args, **kwargs)
  16. class ThemeForm(forms.ModelForm):
  17. name = forms.CharField(label=_("Name"))
  18. parent = ThemeChoiceField(label=_("Parent"), required=False)
  19. version = forms.CharField(label=_("Version"), required=False)
  20. author = forms.CharField(label=_("Author(s)"), required=False)
  21. url = forms.URLField(label=_("Url"), required=False)
  22. class Meta:
  23. model = Theme
  24. fields = ["name", "parent", "version", "author", "url"]
  25. def __init__(self, *args, **kwargs):
  26. super().__init__(*args, **kwargs)
  27. self.limit_parent_choices()
  28. def limit_parent_choices(self):
  29. if not self.instance or not self.instance.pk:
  30. return
  31. self.fields["parent"].queryset = Theme.objects.exclude(
  32. tree_id=self.instance.tree_id,
  33. lft__gte=self.instance.lft,
  34. rght__lte=self.instance.rght,
  35. )
  36. class UploadAssetsForm(forms.Form):
  37. allowed_content_types = []
  38. allowed_extensions = []
  39. assets = forms.FileField(
  40. widget=forms.ClearableFileInput(attrs={"multiple": True}),
  41. error_messages={"required": _("No files have been uploaded.")},
  42. )
  43. def __init__(self, *args, instance=None):
  44. self.instance = instance
  45. super().__init__(*args)
  46. def clean_assets(self):
  47. assets = []
  48. for asset in self.files.getlist("assets"):
  49. try:
  50. if self.allowed_content_types:
  51. self.validate_asset_content_type(asset)
  52. if self.allowed_extensions:
  53. self.validate_asset_extension(asset)
  54. except forms.ValidationError as e:
  55. self.add_error("assets", e)
  56. else:
  57. assets.append(asset)
  58. return assets
  59. def validate_asset_content_type(self, asset):
  60. if asset.content_type in self.allowed_content_types:
  61. return
  62. message = gettext(
  63. 'File "%(file)s" content type "%(content_type)s" is not allowed.'
  64. )
  65. details = {"file": asset.name, "content_type": asset.content_type}
  66. raise forms.ValidationError(message % details)
  67. def validate_asset_extension(self, asset):
  68. filename = asset.name.lower()
  69. for extension in self.allowed_extensions:
  70. if filename.endswith(".%s" % extension):
  71. return
  72. message = gettext('File "%(file)s" extension is invalid.')
  73. details = {"file": asset.name}
  74. raise forms.ValidationError(message % details)
  75. def save(self):
  76. for asset in self.cleaned_data["assets"]:
  77. self.save_asset(asset)
  78. class UploadCssForm(UploadAssetsForm):
  79. allowed_content_types = ["text/css"]
  80. allowed_extensions = ["css"]
  81. def save_asset(self, asset):
  82. create_css(self.instance, asset)
  83. class UploadMediaForm(UploadAssetsForm):
  84. def save_asset(self, asset):
  85. create_media(self.instance, asset)
  86. class CssEditorForm(forms.ModelForm):
  87. name = forms.CharField(
  88. label=_("Name"),
  89. help_text=_(
  90. "Should be an correct filename and include the .css extension. It will be lowercased."
  91. ),
  92. validators=[validate_css_name],
  93. )
  94. source = forms.CharField(widget=forms.Textarea(), required=False)
  95. class Meta:
  96. model = Css
  97. fields = ["name"]
  98. def clean_name(self):
  99. data = self.cleaned_data["name"]
  100. queryset = self.instance.theme.css.filter(name=data)
  101. if self.instance.pk:
  102. queryset = queryset.exclude(pk=self.instance.pk)
  103. if queryset.exists():
  104. raise forms.ValidationError(
  105. gettext("This name is already in use by other asset.")
  106. )
  107. return data
  108. def clean(self):
  109. cleaned_data = super().clean()
  110. if not cleaned_data.get("source"):
  111. raise forms.ValidationError(gettext("You need to enter CSS for this file."))
  112. return cleaned_data
  113. def save(self):
  114. name = self.cleaned_data["name"]
  115. source = self.cleaned_data["source"].encode("utf-8")
  116. source_file = ContentFile(source, name)
  117. self.instance.name = name
  118. if self.instance.source_file:
  119. self.instance.source_file.delete(save=False)
  120. self.instance.source_file = source_file
  121. self.instance.source_hash = get_file_hash(source_file)
  122. self.instance.size = len(source)
  123. if not self.instance.pk:
  124. self.instance.order = get_next_css_order(self.instance.theme)
  125. self.instance.save()
  126. return self.instance
  127. class CssLinkForm(forms.ModelForm):
  128. name = forms.CharField(
  129. label=_("Link name"),
  130. help_text=_('Can be descriptive (e.g. "roboto from fonts.google.com").'),
  131. )
  132. url = forms.URLField(label=_("Remote CSS URL"))
  133. class Meta:
  134. model = Css
  135. fields = ["name", "url"]
  136. def save(self):
  137. if not self.instance.pk:
  138. self.instance.order = get_next_css_order(self.instance.theme)
  139. self.instance.save()
  140. else:
  141. self.instance.save(update_fields=["name", "url", "modified_on"])
  142. return self.instance