csv.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. from django import forms
  2. from django.utils.translation import gettext_lazy as _
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
  5. from django.db.models import Q
  6. from utilities.choices import unpack_grouped_choices
  7. from utilities.object_types import object_type_identifier
  8. __all__ = (
  9. 'CSVChoiceField',
  10. 'CSVContentTypeField',
  11. 'CSVModelChoiceField',
  12. 'CSVModelMultipleChoiceField',
  13. 'CSVMultipleChoiceField',
  14. 'CSVMultipleContentTypeField',
  15. 'CSVTypedChoiceField',
  16. )
  17. class CSVChoicesMixin:
  18. STATIC_CHOICES = True
  19. def __init__(self, *, choices=(), **kwargs):
  20. super().__init__(choices=choices, **kwargs)
  21. self.choices = unpack_grouped_choices(choices)
  22. class CSVChoiceField(CSVChoicesMixin, forms.ChoiceField):
  23. """
  24. A CSV field which accepts a single selection value.
  25. """
  26. pass
  27. class CSVMultipleChoiceField(CSVChoicesMixin, forms.MultipleChoiceField):
  28. """
  29. A CSV field which accepts multiple selection values.
  30. """
  31. def to_python(self, value):
  32. if not value:
  33. return []
  34. if not isinstance(value, str):
  35. raise forms.ValidationError(_("Invalid value for a multiple choice field: {value}").format(value=value))
  36. return value.split(',')
  37. class CSVTypedChoiceField(forms.TypedChoiceField):
  38. STATIC_CHOICES = True
  39. class CSVModelChoiceField(forms.ModelChoiceField):
  40. """
  41. Extends Django's `ModelChoiceField` to provide additional validation for CSV values.
  42. """
  43. default_error_messages = {
  44. 'invalid_choice': _('Object not found: %(value)s'),
  45. }
  46. def to_python(self, value):
  47. try:
  48. return super().to_python(value)
  49. except MultipleObjectsReturned:
  50. raise forms.ValidationError(
  51. _('"{value}" is not a unique value for this field; multiple objects were found').format(value=value)
  52. )
  53. class CSVModelMultipleChoiceField(forms.ModelMultipleChoiceField):
  54. """
  55. Extends Django's `ModelMultipleChoiceField` to support comma-separated values.
  56. """
  57. default_error_messages = {
  58. 'invalid_choice': _('Object not found: %(value)s'),
  59. }
  60. def clean(self, value):
  61. value = value.split(',') if value else []
  62. return super().clean(value)
  63. class CSVContentTypeField(CSVModelChoiceField):
  64. """
  65. CSV field for referencing a single content type, in the form `<app>.<model>`.
  66. """
  67. STATIC_CHOICES = True
  68. def prepare_value(self, value):
  69. return object_type_identifier(value)
  70. def to_python(self, value):
  71. if not value:
  72. return None
  73. try:
  74. app_label, model = value.split('.')
  75. except ValueError:
  76. raise forms.ValidationError(_('Object type must be specified as "<app>.<model>"'))
  77. try:
  78. return self.queryset.get(app_label=app_label, model=model)
  79. except ObjectDoesNotExist:
  80. raise forms.ValidationError(_('Invalid object type'))
  81. class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField):
  82. """
  83. CSV field for referencing one or more content types, in the form `<app>.<model>`.
  84. """
  85. STATIC_CHOICES = True
  86. # TODO: Improve validation of selected ContentTypes
  87. def prepare_value(self, value):
  88. if type(value) is str:
  89. ct_filter = Q()
  90. for name in value.split(','):
  91. app_label, model = name.split('.')
  92. ct_filter |= Q(app_label=app_label, model=model)
  93. return list(ContentType.objects.filter(ct_filter).values_list('pk', flat=True))
  94. return object_type_identifier(value)