File: fields.py

package info (click to toggle)
python-django-colorfield 0.8.0%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 336 kB
  • sloc: javascript: 2,510; python: 569; makefile: 12; sh: 2
file content (114 lines) | stat: -rwxr-xr-x 4,715 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.db.models import CharField, signals
from django.db.models.fields.files import ImageField

from colorfield.utils import get_image_file_background_color
from colorfield.validators import color_hex_validator, color_hexa_validator
from colorfield.widgets import ColorWidget

VALIDATORS_PER_FORMAT = {"hex": color_hex_validator, "hexa": color_hexa_validator}

DEFAULT_PER_FORMAT = {"hex": "#FFFFFF", "hexa": "#FFFFFFFF"}


class ColorField(CharField):

    default_validators = []

    def __init__(self, *args, **kwargs):
        # works like Django choices, but does not restrict input to the given choices
        self.samples = kwargs.pop("samples", None)
        self.format = kwargs.pop("format", "hex").lower()
        if self.format not in ["hex", "hexa"]:
            raise ValueError("Unsupported color format: {}".format(self.format))
        self.default_validators = [VALIDATORS_PER_FORMAT[self.format]]

        self.image_field = kwargs.pop("image_field", None)
        if self.image_field:
            kwargs.setdefault("blank", True)

        kwargs.setdefault("max_length", 18)
        if kwargs.get("null"):
            kwargs.setdefault("blank", True)
            kwargs.setdefault("default", None)
        elif kwargs.get("blank"):
            kwargs.setdefault("default", "")
        else:
            kwargs.setdefault("default", DEFAULT_PER_FORMAT[self.format])
        super(ColorField, self).__init__(*args, **kwargs)

        if self.choices and self.samples:
            raise ImproperlyConfigured(
                "Invalid options: 'choices' and 'samples' are mutually exclusive, "
                "you can set only one of the two for a ColorField instance."
            )

    def formfield(self, **kwargs):
        palette = []
        if self.choices:
            choices = self.get_choices(include_blank=False)
            palette = [choice[0] for choice in choices]
        elif self.samples:
            palette = [choice[0] for choice in self.samples]
        kwargs["widget"] = ColorWidget(
            attrs={
                "default": self.get_default(),
                "format": self.format,
                "palette": palette,
                # # this will be used to hide the widget color spectrum if choices is defined:
                # 'palette_choices_only': bool(self.choices),
            }
        )
        return super(ColorField, self).formfield(**kwargs)

    def contribute_to_class(self, cls, name, **kwargs):
        super(ColorField, self).contribute_to_class(cls, name, **kwargs)
        if cls._meta.abstract:
            return
        if self.image_field:
            signals.post_save.connect(self._update_from_image_field, sender=cls)

    def deconstruct(self):
        name, path, args, kwargs = super(ColorField, self).deconstruct()
        kwargs["samples"] = self.samples
        kwargs["image_field"] = self.image_field
        return name, path, args, kwargs

    def _get_image_field_color(self, instance):
        color = ""
        image_file = getattr(instance, self.image_field)
        if image_file:
            alpha = self.format == "hexa"
            with image_file.open() as _:
                color = get_image_file_background_color(image_file, alpha)
        return color

    def _update_from_image_field(self, instance, created, *args, **kwargs):
        if not instance or not instance.pk or not self.image_field:
            return
        # check if the field is a valid ImageField
        try:
            field_cls = instance._meta.get_field(self.image_field)
            if not isinstance(field_cls, ImageField):
                raise ImproperlyConfigured(
                    'Invalid "image_field" field type, '
                    'expected an instance of "models.ImageField".'
                )
        except FieldDoesNotExist:
            raise ImproperlyConfigured(
                'Invalid "image_field" field name, '
                '"{}" field not found.'.format(self.image_field)
            )
        # update value from picking color from image field
        color = self._get_image_field_color(instance)
        color_field_name = self.attname
        color_field_value = getattr(instance, color_field_name, None)
        if color_field_value != color and color:
            color_field_value = color or self.default
            # update in-memory value
            setattr(instance, color_field_name, color_field_value)
            # update stored value
            manager = instance.__class__.objects
            manager.filter(pk=instance.pk).update(
                **{color_field_name: color_field_value}
            )