summaryrefslogtreecommitdiff
path: root/postgresqleu/util/fields.py
blob: 6e86ceb0cf3a28ddc9c7aba4b27150d2b40213aa (plain)
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from decimal import Decimal
from django.db import models
from django.core.exceptions import ValidationError
from .forms import ImageBinaryFormField, PdfBinaryFormField
from django.forms import ModelChoiceField

import io

from PIL import Image, ImageFile

from postgresqleu.util.magic import magicdb
from postgresqleu.util.image import rescale_image


class LowercaseEmailField(models.EmailField):
    def get_prep_value(self, value):
        value = super(models.EmailField, self).get_prep_value(value)
        if value is not None:
            value = value.lower()
        return value


class ImageBinaryField(models.Field):
    empty_values = [None, b'']

    def __init__(self, max_length, *args, **kwargs):
        self.resolution = kwargs.pop('resolution', None)
        self.auto_scale = kwargs.pop('auto_scale', False)
        super(ImageBinaryField, self).__init__(*args, **kwargs)
        self.max_length = max_length

    def deconstruct(self):
        name, path, args, kwargs = super(ImageBinaryField, self).deconstruct()
        return name, path, args, kwargs

    def get_internal_type(self):
        return "ImageBinaryField"

    def get_placeholder(self, value, compiler, connection):
        return '%s'

    def get_default(self):
        return b''

    def db_type(self, connection):
        return 'bytea'

    def get_db_prep_value(self, value, connection, prepared=False):
        value = super(ImageBinaryField, self).get_db_prep_value(value, connection, prepared)
        if value is not None:
            return connection.Database.Binary(value)
        return value

    def value_to_string(self, obj):
        """Binary data is serialized as base64"""
        return b64encode(force_bytes(self.value_from_object(obj))).decode('ascii')

    def to_python(self, value):
        if self.max_length is not None and len(value) > self.max_length:
            raise ValidationError("Maximum size of file is {} bytes".format(self.max_length))

        if isinstance(value, memoryview):
            v = bytes(value)
        else:
            v = value
        try:
            p = ImageFile.Parser()
            p.feed(v)
            p.close()
            img = p.image
        except Exception as e:
            raise ValidationError("Could not parse image: %s" % e)

        if img.format.upper() not in ('JPEG', 'PNG'):
            raise ValidationError("Only JPEG or PNG files are allowed")

        if self.resolution:
            if img.size[0] != self.resolution[0] or img.size[1] != self.resolution[1]:
                if self.auto_scale:
                    value = rescale_image(img, self.resolution, centered=True)
                else:
                    raise ValidationError("Image size must be {}x{}".format(*self.resolution))

        return value

    def save_form_data(self, instance, data):
        if data is not None:
            if not data:
                data = b''
            setattr(instance, self.name, data)

    def formfield(self, **kwargs):
        defaults = {'form_class': ImageBinaryFormField}
        defaults.update(kwargs)
        return super(ImageBinaryField, self).formfield(**defaults)


class PdfBinaryField(ImageBinaryField):
    def get_internal_type(self):
        return "PdfBinaryField"

    def formfield(self, **kwargs):
        defaults = {'form_class': PdfBinaryFormField}
        defaults.update(kwargs)
        return super(PdfBinaryField, self).formfield(**defaults)

    def to_python(self, value):
        if self.max_length is not None and len(value) > self.max_length:
            raise ValidationError("Maximum size of file is {} bytes".format(self.max_length))

        mtype = magicdb.buffer(value)
        if not mtype.startswith('application/pdf'):
            raise ValidationError("File must be PDF, not %s" % mtype)

        return value


class UserModelChoiceField(ModelChoiceField):
    def label_from_instance(self, obj):
        return "{0} - {1} {2} <{3}>".format(obj.username, obj.first_name, obj.last_name, obj.email)


class NormalizedDecimalField(models.DecimalField):
    def from_db_value(self, value, expression, connection):
        if value is None:
            return value
        return value.quantize(Decimal(1)) if value == value.to_integral() else value.normalize()

    def to_python(self, value):
        val = super().to_python(value)
        if val is None:
            return val
        return val.quantize(Decimal(1)) if val == val.to_integral() else val.normalize()