1 # Copyright (C) 2010 Oregon State University et al.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 from datetime
import datetime
19 from decimal
import Decimal
24 from numbers
import Real
26 Real
= float, int, Decimal
28 from django
.core
.exceptions
import ValidationError
29 from django
.core
.validators
import (EMPTY_VALUES
, MaxValueValidator
,
31 from django
.db
import models
32 from django
.db
.models
.fields
import DecimalField
33 from django
.forms
.fields
import CharField
, RegexField
34 from django
.utils
.translation
import ugettext
as _
36 from south
.modelsinspector
import add_introspection_rules
38 from django_fields
.fields
import EncryptedCharField
41 class PatchedEncryptedCharField(EncryptedCharField
):
43 django_fields upstream refuses to fix a bug, so we get to do it ourselves.
45 Feel free to destroy this class and switch back to upstream if
46 https://github.com/svetlyak40wt/django-fields/pull/12 is ever merged into
47 a released version of django_fields.
50 def get_db_prep_value(self
, value
, connection
=None, prepared
=False):
54 return EncryptedCharField
.get_db_prep_value(self
, value
,
55 connection
=connection
,
59 add_introspection_rules([], ["^ganeti_web\.fields\.PatchedEncryptedCharField"])
62 class PreciseDateTimeField(DecimalField
):
64 Custom field which provides sub-second precision.
66 MySQL and other databases follow the SQL92 standard:
68 TIMESTAMP - contains the datetime field's YEAR, MONTH, DAY, HOUR,
71 However, sometimes more precision is needed, and this field provides
72 arbitrarily high-precision datetimes.
74 Internally, this field is a DECIMAL field. The value is stored as a
75 straight UNIX timestamp, with extra digits of precision representing the
78 This field is not timezone-safe.
80 By default, this field supports six decimal places, for microseconds. It
81 will store a total of eighteen digits for the entire timestamp. Both of
82 these values can be adjusted.
84 The keyword argument ``decimal_places`` controls how many sub-second
85 decimal places will be stored. The keyword argument ``max_digits``
86 controls the total number of digits stored.
89 __metaclass__
= models
.SubfieldBase
91 def __init__(self
, **kwargs
):
93 if not 'decimal_places' in kwargs
:
94 kwargs
['decimal_places'] = 6
95 if not 'max_digits' in kwargs
:
96 kwargs
['max_digits'] = kwargs
['decimal_places'] + 12
98 self
.shifter
= Decimal(10)**kwargs
['decimal_places']
100 super(PreciseDateTimeField
, self
).__init
__(**kwargs
)
102 def get_prep_value(self
, value
):
104 Turn a datetime into a Decimal.
110 # Use Decimal for the math to avoid floating-point loss of precision
111 # or trailing ulps. We want *exactly* as much precision as we had
113 seconds
= Decimal(int(time
.mktime(value
.timetuple())))
114 fraction
= Decimal(value
.microsecond
)
115 return seconds
+ (fraction
/ self
.shifter
)
117 def get_db_prep_save(self
, value
, connection
):
119 Prepare a value for the database.
121 Overridden to handle the datetime-Decimal conversion because
122 DecimalField doesn't otherwise understand our intent.
124 Part of the Django field API.
127 # Cribbed from the DecimalField implementation. Uses
128 # self.get_prep_value instead of self.to_python to ensure that only
129 # Decimals are passed here.
130 return connection
.ops
.value_to_db_decimal(self
.get_prep_value(value
),
134 def to_python(self
, value
):
136 Turn a backend type into a Python type.
138 Part of the Django field API.
143 if isinstance(value
, (datetime
,)):
145 if isinstance(value
, (Decimal
, basestring
)):
146 return datetime
.fromtimestamp(float(value
))
147 if isinstance(value
, Real
):
148 return datetime
.fromtimestamp(value
)
150 raise ValidationError(_('Unable to convert %s to datetime.') % value
)
153 # Migration rules for PDTField. PDTField's serialization is surprisingly
154 # straightforward and doesn't need any help here.
155 add_introspection_rules([], ["^ganeti_web\.fields\.PreciseDateTimeField"])
158 class DataVolumeField(CharField
):
162 def __init__(self
, min_value
=None, max_value
=None, **kwargs
):
163 super(DataVolumeField
, self
).__init
__(**kwargs
)
165 self
.validators
.append(MinValueValidator(min_value
))
167 self
.validators
.append(MaxValueValidator(max_value
))
169 def to_python(self
, value
):
171 Turn a bytecount into an integer, in megabytes.
173 XXX looks like it's actually mebibytes
174 XXX this should handle the SI base2 versions as well (MiB, GiB, etc.)
175 XXX should round up to the next megabyte?
178 if value
in EMPTY_VALUES
:
181 # Make a not-unreasonable attempt to pass through numbers which don't
182 # need the formatting.
189 return int(float(value
))
193 value
= str(value
).upper().strip()
195 matches
= re
.match(r
'([0-9]+(?:\.[0-9]+)?)\s*(M|G|T|MB|GB|TB)?$',
198 raise ValidationError(_('Invalid format.'))
201 unit
= matches
.group(2)
209 multiplier
= 1024 * 1024
211 intvalue
= int(float(matches
.group(1)) * multiplier
)
215 # Migration rules for DVField. DVField doesn't do anything fancy, so the
216 # default rules will work.
217 add_introspection_rules([], ["^ganeti_web\.fields\.DataVolumeField"])
220 class MACAddressField(RegexField
):
222 Form field that validates MAC Addresses.
225 def __init__(self
, *args
, **kwargs
):
226 kwargs
["regex"] = '^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$'
227 super(MACAddressField
, self
).__init
__(*args
, **kwargs
)
230 # Migration rules for MAField. MAField doesn't do anything fancy, so the
231 # default rules will work.
232 add_introspection_rules([], ["^ganeti_web\.fields\.MACAddressField"])
235 class SQLSumIf(models
.sql
.aggregates
.Aggregate
):
238 # XXX not all databases treat 1 and True the same, or have True. Use the
239 # expression 1=1 which always evaluates true with a value compatible with
241 sql_template
= "%(function)s(CASE %(condition)s WHEN 1=1 THEN " \
242 "%(field)s ELSE NULL END)"
245 class SumIf(models
.Aggregate
):
248 def add_to_query(self
, query
, alias
, col
, source
, is_summary
):
249 aggregate
= SQLSumIf(col
, source
=source
,
250 is_summary
=is_summary
, **self
.extra
)
251 query
.aggregates
[alias
] = aggregate