-
Notifications
You must be signed in to change notification settings - Fork 3
/
django_bitmask_field.py
153 lines (126 loc) · 4.64 KB
/
django_bitmask_field.py
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import codecs
import functools
from django import forms
from django.core import checks, exceptions, validators
from django.db import models
from django.utils.encoding import force_bytes
from django.utils.translation import ugettext_lazy as _
from six import integer_types, text_type, PY3
from six.moves import reduce
if PY3:
memoryview = memoryview
buffer_types = (bytes, bytearray, memoryview)
else:
# memoryview and buffer are not strictly equivalent, but should be fine for
# django core usage (mainly BinaryField). However, Jython doesn't support
# buffer (see http://bugs.jython.org/issue1521), so we have to be careful.
import sys
if sys.platform.startswith('java'):
memoryview = memoryview
else:
memoryview = buffer
buffer_types = (bytearray, memoryview)
long = integer_types[-1]
def int2bytes(i):
hex_value = '{0:x}'.format(i)
# make length of hex_value a multiple of two
hex_value = '0' * (len(hex_value) % 2) + hex_value
return codecs.decode(hex_value, 'hex_codec')
def bytes2int(b):
return long(codecs.encode(b, 'hex_codec'), 16)
class BitmaskFormField(forms.TypedMultipleChoiceField):
def prepare_value(self, value):
if isinstance(value, list):
return value
if not value:
return value
return [
long(bit) * (2 ** place)
for place, bit in enumerate('{0:b}'.format(value)[::-1])
if bit == '1'
]
def has_changed(self, initial, data):
return initial != self._coerce(data)
def _coerce(self, value):
values = super(BitmaskFormField, self)._coerce(value)
if values is None:
return values
return reduce(long.__or__, map(long, values), long(0))
class BitmaskField(models.BinaryField):
description = _('Bitmask')
default_validators = [validators.MinValueValidator(0)]
def __init__(self, *args, **kwargs):
editable = kwargs.get('editable', True)
super(BitmaskField, self).__init__(*args, **kwargs)
self.editable = editable
self.validators = list(self.__validators)
@property
def __validators(self):
for validator in self.validators:
if isinstance(validator, validators.MaxLengthValidator):
max_value = 2 ** (validator.limit_value * 8)
yield validators.MaxValueValidator(max_value)
else:
yield validator
def _check_choices(self):
errors = super(BitmaskField, self)._check_choices()
if not errors and self.choices and not all(
isinstance(choice, integer_types) and choice >= 0
for choice, description in self.flatchoices
):
return [
checks.Error(
"all 'choices' must be of integer type.",
obj=self,
)
]
return errors
def deconstruct(self):
return models.Field.deconstruct(self)
@property
def all_values(self):
return reduce(
long.__or__,
map(long, list(zip(*self.flatchoices))[0]),
long(0),
)
def validate(self, value, model_instance):
try:
super(BitmaskField, self).validate(value, model_instance)
except exceptions.ValidationError as error:
if error.code != 'invalid_choice':
raise
if (
self.choices
and value not in self.empty_values
and value & self.all_values != value
):
raise exceptions.ValidationError(
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)
def value_to_string(self, obj):
return models.Field.value_to_string(self, obj)
def to_python(self, value):
if isinstance(value, buffer_types):
return bytes2int(force_bytes(value))
elif isinstance(value, text_type):
return long(value)
return value
def get_prep_value(self, value):
value = super(BitmaskField, self).get_prep_value(value)
if value is None:
return value
return int2bytes(value)
def from_db_value(self, value, expression, connection, *args, **kwargs):
return self.to_python(value)
def formfield(self, **kwargs):
defaults = {
'form_class': functools.partial(forms.IntegerField, min_value=0),
'choices_form_class': BitmaskFormField,
}
if self.choices:
defaults['coerce'] = long
defaults.update(kwargs)
return super(BitmaskField, self).formfield(**defaults)