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.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 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
[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 timdelta. """ 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()