Source code for dynamic_preferences.types

"""
You'll find here the final, concrete classes of preferences you can use
in your own project.

"""
from django import forms
from django.db.models.signals import pre_delete

from django.core.files.storage import default_storage

from .preferences import AbstractPreference, Section
from .exceptions import MissingModel
from dynamic_preferences.serializers import *
from dynamic_preferences.settings import preferences_settings


[docs] class BasePreferenceType(AbstractPreference): """ Used as a base for all other preference classes. You should subclass this one if you want to implement your own preference. """ field_class = None """ A form field that will be used to display and edit the preference use a class, not an instance. :Example: .. code-block:: python from django import forms class MyPreferenceType(BasePreferenceType): field_class = forms.CharField """ #: A serializer class (see dynamic_preferences.serializers) serializer = None field_kwargs = {} """ Additional kwargs to be passed to the form field. :Example: .. code-block:: python class MyPreference(StringPreference): field_kwargs = { 'required': False, 'initial': 'Hello there' } """ @property def initial(self): return self.get_initial()
[docs] def get_initial(self): """ :return: initial data for form field from field_attribute['initial'] or default """ return self.field_kwargs.get("initial", self.get("default"))
@property def field(self): """ :return: an instance of a form field for this preference, with the correct configuration (widget, initial value, validators...) """ return self.setup_field() def setup_field(self, **kwargs): field_class = self.get("field_class") field_kwargs = self.get_field_kwargs() field_kwargs.update(kwargs) return field_class(**field_kwargs)
[docs] def get_field_kwargs(self): """ Return a dict of arguments to use as parameters for the field class instianciation. This will use :py:attr:`field_kwargs` as a starter, and use sensible defaults for a few attributes: - :py:attr:`instance.verbose_name` for the field label - :py:attr:`instance.help_text` for the field help text - :py:attr:`instance.widget` for the field widget - :py:attr:`instance.required` defined if the value is required or not - :py:attr:`instance.initial` defined if the initial value """ kwargs = self.field_kwargs.copy() kwargs.setdefault("label", self.get("verbose_name")) kwargs.setdefault("help_text", self.get("help_text")) kwargs.setdefault("widget", self.get("widget")) kwargs.setdefault("required", self.get("required")) kwargs.setdefault("initial", self.initial) kwargs.setdefault("validators", []) kwargs["validators"].append(self.validate) return kwargs
[docs] def api_repr(self, value): """ Used only to represent a preference value using Rest Framework """ return value
[docs] def get_api_additional_data(self): """ Additional data to serialize for use on front-end side, for example """ return {}
[docs] def get_api_field_data(self): """ Field data to serialize for use on front-end side, for example will include choices available for a choice field """ field = self.setup_field() d = { "class": field.__class__.__name__, "widget": {"class": field.widget.__class__.__name__}, } try: d["input_type"] = field.widget.input_type except AttributeError: # some widgets, such as Select do not have an input type # in django < 1.11 d["input_type"] = None return d
[docs] def validate(self, value): """ Used to implement custom cleaning logic for use in forms and serializers. The method will be passed as a validator to the preference form field. :Example: .. code-block:: python def validate(self, value): if value == '42': raise ValidationError('Wrong value!') """ return
[docs] class BooleanPreference(BasePreferenceType): """ A preference type that stores a boolean. """ field_class = forms.BooleanField serializer = BooleanSerializer required = False
[docs] class IntegerPreference(BasePreferenceType): """ A preference type that stores an integer. """ field_class = forms.IntegerField serializer = IntegerSerializer
IntPreference = IntegerPreference
[docs] class DecimalPreference(BasePreferenceType): """ A preference type that stores a :py:class:`decimal.Decimal`. """ field_class = forms.DecimalField serializer = DecimalSerializer
[docs] class FloatPreference(BasePreferenceType): """ A preference type that stores a float. """ field_class = forms.FloatField serializer = FloatSerializer
[docs] class StringPreference(BasePreferenceType): """ A preference type that stores a string. """ field_class = forms.CharField serializer = StringSerializer
[docs] class LongStringPreference(StringPreference): """ A preference type that stores a string, but with a textarea widget. """ widget = forms.Textarea
[docs] class ChoicePreference(BasePreferenceType): """ A preference type that stores a string among a list of choices. """ choices = () """ Expects the same values as for django :py:class:`forms.ChoiceField`. :Example: .. code-block:: python class MyChoicePreference(ChoicePreference): choices = [ ('c', 'Carrot'), ('t', 'Tomato'), ] """ field_class = forms.ChoiceField serializer = StringSerializer
[docs] def get_field_kwargs(self): field_kwargs = super(ChoicePreference, self).get_field_kwargs() field_kwargs["choices"] = self.get("choices") or self.field_attribute["initial"] return field_kwargs
[docs] def get_api_additional_data(self): d = super(ChoicePreference, self).get_api_additional_data() d["choices"] = self.get("choices") return d
def get_choice_values(self): return [c[0] for c in self.get("choices")]
[docs] def validate(self, value): if value not in self.get_choice_values(): raise forms.ValidationError("{} is not a valid choice".format(value))
[docs] def create_deletion_handler(preference): """ Will generate a dynamic handler to purge related preference on instance deletion """ def delete_related_preferences(sender, instance, *args, **kwargs): queryset = preference.registry.preference_model.objects.filter( name=preference.name, section=preference.section ) related_preferences = queryset.filter( raw_value=preference.serializer.serialize(instance) ) related_preferences.delete() return delete_related_preferences
[docs] class ModelChoicePreference(BasePreferenceType): """ A preference type that stores a reference to a model instance. :Example: .. code-block:: python from myapp.blog.models import BlogEntry @registry.register class FeaturedEntry(ModelChoicePreference): section = Section('blog') name = 'featured_entry' queryset = BlogEntry.objects.filter(status='published') blog_entry = BlogEntry.objects.get(pk=12) manager['blog__featured_entry'] = blog_entry # accessing the value will return the model instance assert manager['blog__featured_entry'].pk == 12 .. note:: You should provide either the :py:attr:`queryset` or :py:attr:`model` attribute """ field_class = forms.ModelChoiceField serializer_class = ModelSerializer model = None """ Which model class to link the preference to. You can skip this if you define the :py:attr:`queryset` attribute. """ queryset = None """ A queryset to filter available model instances. """ signals_handlers = {} def __init__(self, *args, **kwargs): super(ModelChoicePreference, self).__init__(*args, **kwargs) if self.model is not None: # Set queryset following model attribute self.queryset = self.model.objects.all() elif self.queryset is not None: # Set model following queryset attribute self.model = self.queryset.model else: raise MissingModel self.serializer = self.serializer_class(self.model) self._setup_signals() def _setup_signals(self): handler = create_deletion_handler(self) # We need to keep a reference to the handler or it will cause # weakref to die and our handler will not be called self.signals_handlers["pre_delete"] = [handler] pre_delete.connect(handler, sender=self.model)
[docs] def get_field_kwargs(self): kw = super(ModelChoicePreference, self).get_field_kwargs() kw["queryset"] = self.get("queryset") return kw
[docs] def api_repr(self, value): if not value: return None if value.__class__.__name__ == "QuerySet": return [val.pk for val in value] return value.pk
[docs] class ModelMultipleChoicePreference(ModelChoicePreference): """ A preference type that stores a reference list to the model instances. :Example: .. code-block:: python from myapp.blog.models import BlogEntry @registry.register class FeaturedEntries(ModelMultipleChoicePreference): section = Section('blog') name = 'featured_entries' queryset = BlogEntry.objects.all() blog_entries = BlogEntry.objects.filter(status='published') manager['blog__featured_entries'] = blog_entries # accessing the value will return the model queryset assert manager['blog__featured_entries'] == blog_entries .. note:: You should provide either the :py:attr:`queryset` or :py:attr:`model` attribute """ serializer_class = ModelMultipleSerializer field_class = forms.ModelMultipleChoiceField def _setup_signals(self): pass
[docs] class FilePreference(BasePreferenceType): """ A preference type that stores a a reference to a model. :Example: .. code-block:: python from django.core.files.uploadedfile import SimpleUploadedFile @registry.register class Logo(FilePreference): section = Section('blog') name = 'logo' logo = SimpleUploadedFile( "logo.png", b"file_content", content_type="image/png") manager['blog__logo'] = logo # accessing the value will return a FieldFile object, just as # django.db.models.FileField assert manager['blog__logo'].read() == b'file_content' manager['blog__logo'].delete() """ field_class = forms.FileField serializer_class = FileSerializer default = None @property def serializer(self): """ The serializer need additional data about the related preference to upload file to correct directory """ return self.serializer_class(self)
[docs] def get_field_kwargs(self): kwargs = super(FilePreference, self).get_field_kwargs() kwargs["required"] = self.get("required", False) return kwargs
def get_upload_path(self): return os.path.join( preferences_settings.FILE_PREFERENCE_UPLOAD_DIR, self.identifier() )
[docs] def get_file_storage(self): """ Override this method if you want to use a custom storage """ return default_storage
[docs] def api_repr(self, value): if value: return value.url
[docs] class DurationPreference(BasePreferenceType): """ A preference type that stores a timedelta. """ field_class = forms.DurationField serializer = DurationSerializer
[docs] def api_repr(self, value): return duration_string(value)
[docs] class DatePreference(BasePreferenceType): """ A preference type that stores a date. """ field_class = forms.DateField serializer = DateSerializer
[docs] def api_repr(self, value): return value.isoformat()
[docs] class DateTimePreference(BasePreferenceType): """ A preference type that stores a datetime. """ field_class = forms.DateTimeField serializer = DateTimeSerializer
[docs] def api_repr(self, value): return value.isoformat()
[docs] class TimePreference(BasePreferenceType): """ A preference type that stores a time. """ field_class = forms.TimeField serializer = TimeSerializer
[docs] def api_repr(self, value): return value.isoformat()
[docs] class MultipleChoicePreference(ChoicePreference): """ A preference type that stores multiple strings among a list of choices. :Example: .. code-block:: python @registry.register class FeaturedEntries(MultipleChoicePreference): section = Section('blog') name = 'featured_entries' choices = [ ('c', 'Carrot'), ('t', 'Tomato'), ] .. note:: Internally, the selected choices are stored as a string, separated by a separator. The separator defaults to ','. The way this is implemented still is sae also on keys that cotain the separator, but if in doubt, you can still set the :py:attr:`separator` to any other character. """ widget = forms.CheckboxSelectMultiple field_class = forms.MultipleChoiceField serializer = MultipleSerializer
[docs] def validate(self, value): for v in value: super().validate(v)