Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fields.py 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import copy
  2. from django.db import models
  3. from django.utils.translation import ugettext_lazy as _
  4. try:
  5. from django.utils import six
  6. except ImportError:
  7. import six
  8. try:
  9. import json
  10. except ImportError:
  11. from django.utils import simplejson as json
  12. from django.forms import fields
  13. try:
  14. from django.forms.utils import ValidationError
  15. except ImportError:
  16. from django.forms.util import ValidationError
  17. from .subclassing import SubfieldBase
  18. from .encoder import JSONEncoder
  19. class JSONFormFieldBase(object):
  20. def __init__(self, *args, **kwargs):
  21. self.load_kwargs = kwargs.pop('load_kwargs', {})
  22. super(JSONFormFieldBase, self).__init__(*args, **kwargs)
  23. def to_python(self, value):
  24. if isinstance(value, six.string_types) and value:
  25. try:
  26. return json.loads(value, **self.load_kwargs)
  27. except ValueError:
  28. raise ValidationError(_("Enter valid JSON"))
  29. return value
  30. def clean(self, value):
  31. if not value and not self.required:
  32. return None
  33. # Trap cleaning errors & bubble them up as JSON errors
  34. try:
  35. return super(JSONFormFieldBase, self).clean(value)
  36. except TypeError:
  37. raise ValidationError(_("Enter valid JSON"))
  38. class JSONFormField(JSONFormFieldBase, fields.CharField):
  39. pass
  40. class JSONCharFormField(JSONFormFieldBase, fields.CharField):
  41. pass
  42. class JSONFieldBase(six.with_metaclass(SubfieldBase, models.Field)):
  43. def __init__(self, *args, **kwargs):
  44. self.dump_kwargs = kwargs.pop('dump_kwargs', {
  45. 'cls': JSONEncoder,
  46. 'separators': (',', ':')
  47. })
  48. self.load_kwargs = kwargs.pop('load_kwargs', {})
  49. super(JSONFieldBase, self).__init__(*args, **kwargs)
  50. def pre_init(self, value, obj):
  51. """Convert a string value to JSON only if it needs to be deserialized.
  52. SubfieldBase metaclass has been modified to call this method instead of
  53. to_python so that we can check the obj state and determine if it needs to be
  54. deserialized"""
  55. try:
  56. if obj._state.adding:
  57. # Make sure the primary key actually exists on the object before
  58. # checking if it's empty. This is a special case for South datamigrations
  59. # see: https://github.com/bradjasper/django-jsonfield/issues/52
  60. if getattr(obj, "pk", None) is not None:
  61. if isinstance(value, six.string_types):
  62. try:
  63. return json.loads(value, **self.load_kwargs)
  64. except ValueError:
  65. raise ValidationError(_("Enter valid JSON"))
  66. except AttributeError:
  67. # south fake meta class doesn't create proper attributes
  68. # see this:
  69. # https://github.com/bradjasper/django-jsonfield/issues/52
  70. pass
  71. return value
  72. def to_python(self, value):
  73. """The SubfieldBase metaclass calls pre_init instead of to_python, however to_python
  74. is still necessary for Django's deserializer"""
  75. return value
  76. def get_prep_value(self, value):
  77. """Convert JSON object to a string"""
  78. if self.null and value is None:
  79. return None
  80. return json.dumps(value, **self.dump_kwargs)
  81. def _get_val_from_obj(self, obj):
  82. # This function created to replace Django deprecated version
  83. # https://code.djangoproject.com/ticket/24716
  84. if obj is not None:
  85. return getattr(obj, self.attname)
  86. else:
  87. return self.get_default()
  88. def value_to_string(self, obj):
  89. value = self._get_val_from_obj(obj)
  90. return self.get_db_prep_value(value, None)
  91. def value_from_object(self, obj):
  92. value = super(JSONFieldBase, self).value_from_object(obj)
  93. if self.null and value is None:
  94. return None
  95. return self.dumps_for_display(value)
  96. def dumps_for_display(self, value):
  97. return json.dumps(value, **self.dump_kwargs)
  98. def formfield(self, **kwargs):
  99. if "form_class" not in kwargs:
  100. kwargs["form_class"] = self.form_class
  101. field = super(JSONFieldBase, self).formfield(**kwargs)
  102. if isinstance(field, JSONFormFieldBase):
  103. field.load_kwargs = self.load_kwargs
  104. if not field.help_text:
  105. field.help_text = "Enter valid JSON"
  106. return field
  107. def get_default(self):
  108. """
  109. Returns the default value for this field.
  110. The default implementation on models.Field calls force_unicode
  111. on the default, which means you can't set arbitrary Python
  112. objects as the default. To fix this, we just return the value
  113. without calling force_unicode on it. Note that if you set a
  114. callable as a default, the field will still call it. It will
  115. *not* try to pickle and encode it.
  116. """
  117. if self.has_default():
  118. if callable(self.default):
  119. return self.default()
  120. return copy.deepcopy(self.default)
  121. # If the field doesn't have a default, then we punt to models.Field.
  122. return super(JSONFieldBase, self).get_default()
  123. class JSONField(JSONFieldBase, models.TextField):
  124. """JSONField is a generic textfield that serializes/deserializes JSON objects"""
  125. form_class = JSONFormField
  126. def dumps_for_display(self, value):
  127. kwargs = {"indent": 2}
  128. kwargs.update(self.dump_kwargs)
  129. return json.dumps(value, **kwargs)
  130. class JSONCharField(JSONFieldBase, models.CharField):
  131. """JSONCharField is a generic textfield that serializes/deserializes JSON objects,
  132. stored in the database like a CharField, which enables it to be used
  133. e.g. in unique keys"""
  134. form_class = JSONCharFormField
  135. try:
  136. from south.modelsinspector import add_introspection_rules
  137. add_introspection_rules([], ["^jsonfield\.fields\.(JSONField|JSONCharField)"])
  138. except ImportError:
  139. pass