generic_fields.py 6.28 KB
from django import forms
from django.db.models import Q

# TODO: import particular classes?
import crispy_forms.bootstrap as bootstrap
import crispy_forms.layout as layout

from .query_managers import (
    SingleValueQueryManager,
    MultiValueQueryManager,
    RangesQueryManager,
    SingleRegexQueryManager,
    RegexQueryManager,
    ComboQueryManager
)

bootstrap.InlineCheckboxes.template = 'checkboxselectmultiple_inline_with_tooltips.html'

class LayoutField(object):
    
    def layout(self, x, **kwargs):
        return layout.Field(x, **kwargs)


class CheckboxesLayoutField(object):
    
    def layout(self, x, **kwargs):
        if self.checkboxes:
            return bootstrap.InlineCheckboxes(x, **kwargs)
        else:
            return layout.Field(x, **kwargs)


class RadiosLayoutField(object):
    
    def layout(self, x, **kwargs):
        return bootstrap.InlineRadios(x, **kwargs)


class SelectLayoutField(object):
    
    def layout(self, x, **kwargs):
        return layout.Field(x, css_class='custom-select', **kwargs)

# ==============================================================================


#TODO move implementation common for RangesFilter and RegexFilter to an abstract ExpressionFilter?
class RangeFilter(forms.CharField, LayoutField):
    
    def __init__(self, label, lookup, initial='', empty_value='', **kwargs):
        super().__init__(label=label, required=False, initial=initial, empty_value=empty_value, **kwargs)
        self.query_manager = RangesQueryManager(lookup)
    
    # can’t use static default_validators since the query manager is a class field
    def validate(self, value):
        self.query_manager.expression_validator(value)
    

class RegexFilter(forms.CharField, LayoutField):
    
    def __init__(self, label, lookup, additional_operators=False, initial='.*', empty_value='.*', autocomplete=None, **kwargs):
        self.css_class = 'regex-autocomplete' if autocomplete else None
        self.data_autocomplete = autocomplete if autocomplete else None
        super().__init__(label=label, required=False, initial=initial, empty_value=empty_value, **kwargs)
        self.query_manager = RegexQueryManager(lookup, additional_operators=additional_operators)
        
    # can’t use static default_validators since validation depends on the
    # query_manager instance (allowed operators)
    def validate(self, value):
        self.query_manager.expression_validator(value)
    
    def layout(self, x, **kwargs):
        return layout.Field(x, css_class=self.css_class, data_autocomplete=self.data_autocomplete, **kwargs)


class SingleRegexFilter(forms.CharField, LayoutField):
    
    def __init__(self, label, lookup, initial='.*', empty_value='.*', **kwargs):
        super().__init__(label=label, required=False, initial=initial, empty_value=empty_value, **kwargs)
        self.query_manager = SingleRegexQueryManager(lookup)


class MultipleChoiceFilter(forms.MultipleChoiceField, CheckboxesLayoutField):
    
    def __init__(self, label, choices, lookup, **kwargs):
        super().__init__(
            label=label,
            choices=choices,
            required=False,
            **kwargs
        )
        self.query_manager = MultiValueQueryManager(lookup, default_conjunction=False)
        self.checkboxes = True
    
    def valid_value(self, value):
        text_value = str(value)
        # ignore the optional help tooltip provided in choices, only compare with the key
        for k, (v, help) in self.choices:
            print(k, value)
            if value == k or text_value == str(k):
                return True
        return False


class ModelMultipleChoiceFilter(forms.ModelMultipleChoiceField, CheckboxesLayoutField):
    
    def __init__(self, label, queryset, key, lookup, human_values={}, checkboxes=True, **kwargs):
        super().__init__(
            label=label,
            queryset=queryset,
            required=False,
            **kwargs
        )
        self.query_manager = MultiValueQueryManager(lookup, default_conjunction=False)
        self.key = key
        self.human_values = human_values
        self.checkboxes = checkboxes
    
    def label_from_instance(self, obj):
        val = self.human_values.get(obj.__dict__[self.key], obj.__dict__[self.key])
        if self.checkboxes:
            if type(val) == tuple:
                return val
            else:
                return (val, None)
        else:
            if type(val) == tuple:
                return '{} ({})'.format(val[1], val[0])
            else:
                return val


class ChoiceFilter(forms.ChoiceField, SelectLayoutField):
    
    def __init__(self, label, choices, **kwargs):
        super().__init__(
            label=label,
            choices=choices,
            required=False,
            **kwargs
        )


class ModelChoiceFilter(forms.ModelChoiceField, SelectLayoutField):
    
    def __init__(self, label, queryset, key, lookup, human_values={}, **kwargs):
        super().__init__(
            label=label,
            queryset=queryset,
            required=False,
            **kwargs
        )
        self.query_manager = SingleValueQueryManager(lookup)
        # str object or callable
        self.key = key
        self.human_values = human_values
    
    def label_from_instance(self, obj):
        if type(self.key) == str:
            return self.human_values.get(obj.__dict__[self.key], obj.__dict__[self.key])
        else:
            return self.key(obj)


class OperatorField(forms.ChoiceField, RadiosLayoutField):
    
    def __init__(self):
        super().__init__(
            label=' ',
            choices=((False, 'lub'), (True, 'i')),
            initial=False,
            required=False,
         )
    
    def to_python(self, value):
        if value in ('1', 'true', 'True', True):
            return True
        if value in ('0', 'false', 'False', False):
            return False
        return None


class SwitchField(forms.BooleanField, LayoutField):
    
    def __init__(self, label, css_class=None):
        super().__init__(label=label, required=False)
        self.css_class = css_class
    
    def layout(self, x, **kwargs):
        wrapper_class = 'custom-switch float-right font-weight-bold'
        if self.css_class:
            wrapper_class += ' ' + self.css_class
        return layout.Field(x, wrapper_class=wrapper_class, **kwargs)