OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/ |
| 2 # |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a |
| 4 # copy of this software and associated documentation files (the |
| 5 # "Software"), to deal in the Software without restriction, including |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- |
| 9 # lowing conditions: |
| 10 # |
| 11 # The above copyright notice and this permission notice shall be included |
| 12 # in all copies or substantial portions of the Software. |
| 13 # |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 20 # IN THE SOFTWARE. |
| 21 |
| 22 import datetime |
| 23 from key import Key |
| 24 from boto.utils import Password |
| 25 from boto.sdb.db.query import Query |
| 26 import re |
| 27 import boto |
| 28 import boto.s3.key |
| 29 from boto.sdb.db.blob import Blob |
| 30 |
| 31 |
| 32 class Property(object): |
| 33 |
| 34 data_type = str |
| 35 type_name = '' |
| 36 name = '' |
| 37 verbose_name = '' |
| 38 |
| 39 def __init__(self, verbose_name=None, name=None, default=None, |
| 40 required=False, validator=None, choices=None, unique=False): |
| 41 self.verbose_name = verbose_name |
| 42 self.name = name |
| 43 self.default = default |
| 44 self.required = required |
| 45 self.validator = validator |
| 46 self.choices = choices |
| 47 if self.name: |
| 48 self.slot_name = '_' + self.name |
| 49 else: |
| 50 self.slot_name = '_' |
| 51 self.unique = unique |
| 52 |
| 53 def __get__(self, obj, objtype): |
| 54 if obj: |
| 55 obj.load() |
| 56 return getattr(obj, self.slot_name) |
| 57 else: |
| 58 return None |
| 59 |
| 60 def __set__(self, obj, value): |
| 61 self.validate(value) |
| 62 |
| 63 # Fire off any on_set functions |
| 64 try: |
| 65 if obj._loaded and hasattr(obj, "on_set_%s" % self.name): |
| 66 fnc = getattr(obj, "on_set_%s" % self.name) |
| 67 value = fnc(value) |
| 68 except Exception: |
| 69 boto.log.exception("Exception running on_set_%s" % self.name) |
| 70 |
| 71 setattr(obj, self.slot_name, value) |
| 72 |
| 73 def __property_config__(self, model_class, property_name): |
| 74 self.model_class = model_class |
| 75 self.name = property_name |
| 76 self.slot_name = '_' + self.name |
| 77 |
| 78 def default_validator(self, value): |
| 79 if isinstance(value, basestring) or value == self.default_value(): |
| 80 return |
| 81 if not isinstance(value, self.data_type): |
| 82 raise TypeError('Validation Error, %s.%s expecting %s, got %s' % (se
lf.model_class.__name__, self.name, self.data_type, type(value))) |
| 83 |
| 84 def default_value(self): |
| 85 return self.default |
| 86 |
| 87 def validate(self, value): |
| 88 if self.required and value == None: |
| 89 raise ValueError('%s is a required property' % self.name) |
| 90 if self.choices and value and not value in self.choices: |
| 91 raise ValueError('%s not a valid choice for %s.%s' % (value, self.mo
del_class.__name__, self.name)) |
| 92 if self.validator: |
| 93 self.validator(value) |
| 94 else: |
| 95 self.default_validator(value) |
| 96 return value |
| 97 |
| 98 def empty(self, value): |
| 99 return not value |
| 100 |
| 101 def get_value_for_datastore(self, model_instance): |
| 102 return getattr(model_instance, self.name) |
| 103 |
| 104 def make_value_from_datastore(self, value): |
| 105 return value |
| 106 |
| 107 def get_choices(self): |
| 108 if callable(self.choices): |
| 109 return self.choices() |
| 110 return self.choices |
| 111 |
| 112 |
| 113 def validate_string(value): |
| 114 if value == None: |
| 115 return |
| 116 elif isinstance(value, str) or isinstance(value, unicode): |
| 117 if len(value) > 1024: |
| 118 raise ValueError('Length of value greater than maxlength') |
| 119 else: |
| 120 raise TypeError('Expecting String, got %s' % type(value)) |
| 121 |
| 122 |
| 123 class StringProperty(Property): |
| 124 |
| 125 type_name = 'String' |
| 126 |
| 127 def __init__(self, verbose_name=None, name=None, default='', |
| 128 required=False, validator=validate_string, |
| 129 choices=None, unique=False): |
| 130 Property.__init__(self, verbose_name, name, default, required, |
| 131 validator, choices, unique) |
| 132 |
| 133 |
| 134 class TextProperty(Property): |
| 135 |
| 136 type_name = 'Text' |
| 137 |
| 138 def __init__(self, verbose_name=None, name=None, default='', |
| 139 required=False, validator=None, choices=None, |
| 140 unique=False, max_length=None): |
| 141 Property.__init__(self, verbose_name, name, default, required, |
| 142 validator, choices, unique) |
| 143 self.max_length = max_length |
| 144 |
| 145 def validate(self, value): |
| 146 value = super(TextProperty, self).validate(value) |
| 147 if not isinstance(value, str) and not isinstance(value, unicode): |
| 148 raise TypeError('Expecting Text, got %s' % type(value)) |
| 149 if self.max_length and len(value) > self.max_length: |
| 150 raise ValueError('Length of value greater than maxlength %s' % self.
max_length) |
| 151 |
| 152 |
| 153 class PasswordProperty(StringProperty): |
| 154 """ |
| 155 |
| 156 Hashed property whose original value can not be |
| 157 retrieved, but still can be compared. |
| 158 |
| 159 Works by storing a hash of the original value instead |
| 160 of the original value. Once that's done all that |
| 161 can be retrieved is the hash. |
| 162 |
| 163 The comparison |
| 164 |
| 165 obj.password == 'foo' |
| 166 |
| 167 generates a hash of 'foo' and compares it to the |
| 168 stored hash. |
| 169 |
| 170 Underlying data type for hashing, storing, and comparing |
| 171 is boto.utils.Password. The default hash function is |
| 172 defined there ( currently sha512 in most cases, md5 |
| 173 where sha512 is not available ) |
| 174 |
| 175 It's unlikely you'll ever need to use a different hash |
| 176 function, but if you do, you can control the behavior |
| 177 in one of two ways: |
| 178 |
| 179 1) Specifying hashfunc in PasswordProperty constructor |
| 180 |
| 181 import hashlib |
| 182 |
| 183 class MyModel(model): |
| 184 password = PasswordProperty(hashfunc=hashlib.sha224) |
| 185 |
| 186 2) Subclassing Password and PasswordProperty |
| 187 |
| 188 class SHA224Password(Password): |
| 189 hashfunc=hashlib.sha224 |
| 190 |
| 191 class SHA224PasswordProperty(PasswordProperty): |
| 192 data_type=MyPassword |
| 193 type_name="MyPassword" |
| 194 |
| 195 class MyModel(Model): |
| 196 password = SHA224PasswordProperty() |
| 197 |
| 198 """ |
| 199 data_type = Password |
| 200 type_name = 'Password' |
| 201 |
| 202 def __init__(self, verbose_name=None, name=None, default='', required=False, |
| 203 validator=None, choices=None, unique=False, hashfunc=None): |
| 204 |
| 205 """ |
| 206 The hashfunc parameter overrides the default hashfunc in boto.utils.P
assword. |
| 207 |
| 208 The remaining parameters are passed through to StringProperty.__init_
_""" |
| 209 |
| 210 StringProperty.__init__(self, verbose_name, name, default, required, |
| 211 validator, choices, unique) |
| 212 self.hashfunc = hashfunc |
| 213 |
| 214 def make_value_from_datastore(self, value): |
| 215 p = self.data_type(value, hashfunc=self.hashfunc) |
| 216 return p |
| 217 |
| 218 def get_value_for_datastore(self, model_instance): |
| 219 value = StringProperty.get_value_for_datastore(self, model_instance) |
| 220 if value and len(value): |
| 221 return str(value) |
| 222 else: |
| 223 return None |
| 224 |
| 225 def __set__(self, obj, value): |
| 226 if not isinstance(value, self.data_type): |
| 227 p = self.data_type(hashfunc=self.hashfunc) |
| 228 p.set(value) |
| 229 value = p |
| 230 Property.__set__(self, obj, value) |
| 231 |
| 232 def __get__(self, obj, objtype): |
| 233 return self.data_type(StringProperty.__get__(self, obj, objtype), hashfu
nc=self.hashfunc) |
| 234 |
| 235 def validate(self, value): |
| 236 value = Property.validate(self, value) |
| 237 if isinstance(value, self.data_type): |
| 238 if len(value) > 1024: |
| 239 raise ValueError('Length of value greater than maxlength') |
| 240 else: |
| 241 raise TypeError('Expecting %s, got %s' % (type(self.data_type), type
(value))) |
| 242 |
| 243 |
| 244 class BlobProperty(Property): |
| 245 data_type = Blob |
| 246 type_name = "blob" |
| 247 |
| 248 def __set__(self, obj, value): |
| 249 if value != self.default_value(): |
| 250 if not isinstance(value, Blob): |
| 251 oldb = self.__get__(obj, type(obj)) |
| 252 id = None |
| 253 if oldb: |
| 254 id = oldb.id |
| 255 b = Blob(value=value, id=id) |
| 256 value = b |
| 257 Property.__set__(self, obj, value) |
| 258 |
| 259 |
| 260 class S3KeyProperty(Property): |
| 261 |
| 262 data_type = boto.s3.key.Key |
| 263 type_name = 'S3Key' |
| 264 validate_regex = "^s3:\/\/([^\/]*)\/(.*)$" |
| 265 |
| 266 def __init__(self, verbose_name=None, name=None, default=None, |
| 267 required=False, validator=None, choices=None, unique=False): |
| 268 Property.__init__(self, verbose_name, name, default, required, |
| 269 validator, choices, unique) |
| 270 |
| 271 def validate(self, value): |
| 272 value = super(S3KeyProperty, self).validate(value) |
| 273 if value == self.default_value() or value == str(self.default_value()): |
| 274 return self.default_value() |
| 275 if isinstance(value, self.data_type): |
| 276 return |
| 277 match = re.match(self.validate_regex, value) |
| 278 if match: |
| 279 return |
| 280 raise TypeError('Validation Error, expecting %s, got %s' % (self.data_ty
pe, type(value))) |
| 281 |
| 282 def __get__(self, obj, objtype): |
| 283 value = Property.__get__(self, obj, objtype) |
| 284 if value: |
| 285 if isinstance(value, self.data_type): |
| 286 return value |
| 287 match = re.match(self.validate_regex, value) |
| 288 if match: |
| 289 s3 = obj._manager.get_s3_connection() |
| 290 bucket = s3.get_bucket(match.group(1), validate=False) |
| 291 k = bucket.get_key(match.group(2)) |
| 292 if not k: |
| 293 k = bucket.new_key(match.group(2)) |
| 294 k.set_contents_from_string("") |
| 295 return k |
| 296 else: |
| 297 return value |
| 298 |
| 299 def get_value_for_datastore(self, model_instance): |
| 300 value = Property.get_value_for_datastore(self, model_instance) |
| 301 if value: |
| 302 return "s3://%s/%s" % (value.bucket.name, value.name) |
| 303 else: |
| 304 return None |
| 305 |
| 306 |
| 307 class IntegerProperty(Property): |
| 308 |
| 309 data_type = int |
| 310 type_name = 'Integer' |
| 311 |
| 312 def __init__(self, verbose_name=None, name=None, default=0, required=False, |
| 313 validator=None, choices=None, unique=False, max=2147483647, min
=-2147483648): |
| 314 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 315 self.max = max |
| 316 self.min = min |
| 317 |
| 318 def validate(self, value): |
| 319 value = int(value) |
| 320 value = Property.validate(self, value) |
| 321 if value > self.max: |
| 322 raise ValueError('Maximum value is %d' % self.max) |
| 323 if value < self.min: |
| 324 raise ValueError('Minimum value is %d' % self.min) |
| 325 return value |
| 326 |
| 327 def empty(self, value): |
| 328 return value is None |
| 329 |
| 330 def __set__(self, obj, value): |
| 331 if value == "" or value == None: |
| 332 value = 0 |
| 333 return Property.__set__(self, obj, value) |
| 334 |
| 335 |
| 336 class LongProperty(Property): |
| 337 |
| 338 data_type = long |
| 339 type_name = 'Long' |
| 340 |
| 341 def __init__(self, verbose_name=None, name=None, default=0, required=False, |
| 342 validator=None, choices=None, unique=False): |
| 343 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 344 |
| 345 def validate(self, value): |
| 346 value = long(value) |
| 347 value = Property.validate(self, value) |
| 348 min = -9223372036854775808 |
| 349 max = 9223372036854775807 |
| 350 if value > max: |
| 351 raise ValueError('Maximum value is %d' % max) |
| 352 if value < min: |
| 353 raise ValueError('Minimum value is %d' % min) |
| 354 return value |
| 355 |
| 356 def empty(self, value): |
| 357 return value is None |
| 358 |
| 359 |
| 360 class BooleanProperty(Property): |
| 361 |
| 362 data_type = bool |
| 363 type_name = 'Boolean' |
| 364 |
| 365 def __init__(self, verbose_name=None, name=None, default=False, required=Fal
se, |
| 366 validator=None, choices=None, unique=False): |
| 367 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 368 |
| 369 def empty(self, value): |
| 370 return value is None |
| 371 |
| 372 |
| 373 class FloatProperty(Property): |
| 374 |
| 375 data_type = float |
| 376 type_name = 'Float' |
| 377 |
| 378 def __init__(self, verbose_name=None, name=None, default=0.0, required=False
, |
| 379 validator=None, choices=None, unique=False): |
| 380 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 381 |
| 382 def validate(self, value): |
| 383 value = float(value) |
| 384 value = Property.validate(self, value) |
| 385 return value |
| 386 |
| 387 def empty(self, value): |
| 388 return value is None |
| 389 |
| 390 |
| 391 class DateTimeProperty(Property): |
| 392 """This class handles both the datetime.datetime object |
| 393 And the datetime.date objects. It can return either one, |
| 394 depending on the value stored in the database""" |
| 395 |
| 396 data_type = datetime.datetime |
| 397 type_name = 'DateTime' |
| 398 |
| 399 def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, na
me=None, |
| 400 default=None, required=False, validator=None, choices=None, uni
que=False): |
| 401 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 402 self.auto_now = auto_now |
| 403 self.auto_now_add = auto_now_add |
| 404 |
| 405 def default_value(self): |
| 406 if self.auto_now or self.auto_now_add: |
| 407 return self.now() |
| 408 return Property.default_value(self) |
| 409 |
| 410 def validate(self, value): |
| 411 if value == None: |
| 412 return |
| 413 if isinstance(value, datetime.date): |
| 414 return value |
| 415 return super(DateTimeProperty, self).validate(value) |
| 416 |
| 417 def get_value_for_datastore(self, model_instance): |
| 418 if self.auto_now: |
| 419 setattr(model_instance, self.name, self.now()) |
| 420 return Property.get_value_for_datastore(self, model_instance) |
| 421 |
| 422 def now(self): |
| 423 return datetime.datetime.utcnow() |
| 424 |
| 425 |
| 426 class DateProperty(Property): |
| 427 |
| 428 data_type = datetime.date |
| 429 type_name = 'Date' |
| 430 |
| 431 def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, na
me=None, |
| 432 default=None, required=False, validator=None, choices=None, uni
que=False): |
| 433 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 434 self.auto_now = auto_now |
| 435 self.auto_now_add = auto_now_add |
| 436 |
| 437 def default_value(self): |
| 438 if self.auto_now or self.auto_now_add: |
| 439 return self.now() |
| 440 return Property.default_value(self) |
| 441 |
| 442 def validate(self, value): |
| 443 value = super(DateProperty, self).validate(value) |
| 444 if value == None: |
| 445 return |
| 446 if not isinstance(value, self.data_type): |
| 447 raise TypeError('Validation Error, expecting %s, got %s' % (self.dat
a_type, type(value))) |
| 448 |
| 449 def get_value_for_datastore(self, model_instance): |
| 450 if self.auto_now: |
| 451 setattr(model_instance, self.name, self.now()) |
| 452 val = Property.get_value_for_datastore(self, model_instance) |
| 453 if isinstance(val, datetime.datetime): |
| 454 val = val.date() |
| 455 return val |
| 456 |
| 457 def now(self): |
| 458 return datetime.date.today() |
| 459 |
| 460 |
| 461 class TimeProperty(Property): |
| 462 data_type = datetime.time |
| 463 type_name = 'Time' |
| 464 |
| 465 def __init__(self, verbose_name=None, name=None, |
| 466 default=None, required=False, validator=None, choices=None, uni
que=False): |
| 467 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 468 |
| 469 def validate(self, value): |
| 470 value = super(TimeProperty, self).validate(value) |
| 471 if value is None: |
| 472 return |
| 473 if not isinstance(value, self.data_type): |
| 474 raise TypeError('Validation Error, expecting %s, got %s' % (self.dat
a_type, type(value))) |
| 475 |
| 476 |
| 477 class ReferenceProperty(Property): |
| 478 |
| 479 data_type = Key |
| 480 type_name = 'Reference' |
| 481 |
| 482 def __init__(self, reference_class=None, collection_name=None, |
| 483 verbose_name=None, name=None, default=None, required=False, val
idator=None, choices=None, unique=False): |
| 484 Property.__init__(self, verbose_name, name, default, required, validator
, choices, unique) |
| 485 self.reference_class = reference_class |
| 486 self.collection_name = collection_name |
| 487 |
| 488 def __get__(self, obj, objtype): |
| 489 if obj: |
| 490 value = getattr(obj, self.slot_name) |
| 491 if value == self.default_value(): |
| 492 return value |
| 493 # If the value is still the UUID for the referenced object, we need
to create |
| 494 # the object now that is the attribute has actually been accessed.
This lazy |
| 495 # instantiation saves unnecessary roundtrips to SimpleDB |
| 496 if isinstance(value, str) or isinstance(value, unicode): |
| 497 value = self.reference_class(value) |
| 498 setattr(obj, self.name, value) |
| 499 return value |
| 500 |
| 501 def __set__(self, obj, value): |
| 502 """Don't allow this object to be associated to itself |
| 503 This causes bad things to happen""" |
| 504 if value != None and (obj.id == value or (hasattr(value, "id") and obj.i
d == value.id)): |
| 505 raise ValueError("Can not associate an object with itself!") |
| 506 return super(ReferenceProperty, self).__set__(obj, value) |
| 507 |
| 508 def __property_config__(self, model_class, property_name): |
| 509 Property.__property_config__(self, model_class, property_name) |
| 510 if self.collection_name is None: |
| 511 self.collection_name = '%s_%s_set' % (model_class.__name__.lower(),
self.name) |
| 512 if hasattr(self.reference_class, self.collection_name): |
| 513 raise ValueError('duplicate property: %s' % self.collection_name) |
| 514 setattr(self.reference_class, self.collection_name, |
| 515 _ReverseReferenceProperty(model_class, property_name, self.colle
ction_name)) |
| 516 |
| 517 def check_uuid(self, value): |
| 518 # This does a bit of hand waving to "type check" the string |
| 519 t = value.split('-') |
| 520 if len(t) != 5: |
| 521 raise ValueError |
| 522 |
| 523 def check_instance(self, value): |
| 524 try: |
| 525 obj_lineage = value.get_lineage() |
| 526 cls_lineage = self.reference_class.get_lineage() |
| 527 if obj_lineage.startswith(cls_lineage): |
| 528 return |
| 529 raise TypeError('%s not instance of %s' % (obj_lineage, cls_lineage)
) |
| 530 except: |
| 531 raise ValueError('%s is not a Model' % value) |
| 532 |
| 533 def validate(self, value): |
| 534 if self.validator: |
| 535 self.validator(value) |
| 536 if self.required and value == None: |
| 537 raise ValueError('%s is a required property' % self.name) |
| 538 if value == self.default_value(): |
| 539 return |
| 540 if not isinstance(value, str) and not isinstance(value, unicode): |
| 541 self.check_instance(value) |
| 542 |
| 543 |
| 544 class _ReverseReferenceProperty(Property): |
| 545 data_type = Query |
| 546 type_name = 'query' |
| 547 |
| 548 def __init__(self, model, prop, name): |
| 549 self.__model = model |
| 550 self.__property = prop |
| 551 self.collection_name = prop |
| 552 self.name = name |
| 553 self.item_type = model |
| 554 |
| 555 def __get__(self, model_instance, model_class): |
| 556 """Fetches collection of model instances of this collection property.""" |
| 557 if model_instance is not None: |
| 558 query = Query(self.__model) |
| 559 if isinstance(self.__property, list): |
| 560 props = [] |
| 561 for prop in self.__property: |
| 562 props.append("%s =" % prop) |
| 563 return query.filter(props, model_instance) |
| 564 else: |
| 565 return query.filter(self.__property + ' =', model_instance) |
| 566 else: |
| 567 return self |
| 568 |
| 569 def __set__(self, model_instance, value): |
| 570 """Not possible to set a new collection.""" |
| 571 raise ValueError('Virtual property is read-only') |
| 572 |
| 573 |
| 574 class CalculatedProperty(Property): |
| 575 |
| 576 def __init__(self, verbose_name=None, name=None, default=None, |
| 577 required=False, validator=None, choices=None, |
| 578 calculated_type=int, unique=False, use_method=False): |
| 579 Property.__init__(self, verbose_name, name, default, required, |
| 580 validator, choices, unique) |
| 581 self.calculated_type = calculated_type |
| 582 self.use_method = use_method |
| 583 |
| 584 def __get__(self, obj, objtype): |
| 585 value = self.default_value() |
| 586 if obj: |
| 587 try: |
| 588 value = getattr(obj, self.slot_name) |
| 589 if self.use_method: |
| 590 value = value() |
| 591 except AttributeError: |
| 592 pass |
| 593 return value |
| 594 |
| 595 def __set__(self, obj, value): |
| 596 """Not possible to set a new AutoID.""" |
| 597 pass |
| 598 |
| 599 def _set_direct(self, obj, value): |
| 600 if not self.use_method: |
| 601 setattr(obj, self.slot_name, value) |
| 602 |
| 603 def get_value_for_datastore(self, model_instance): |
| 604 if self.calculated_type in [str, int, bool]: |
| 605 value = self.__get__(model_instance, model_instance.__class__) |
| 606 return value |
| 607 else: |
| 608 return None |
| 609 |
| 610 |
| 611 class ListProperty(Property): |
| 612 |
| 613 data_type = list |
| 614 type_name = 'List' |
| 615 |
| 616 def __init__(self, item_type, verbose_name=None, name=None, default=None, **
kwds): |
| 617 if default is None: |
| 618 default = [] |
| 619 self.item_type = item_type |
| 620 Property.__init__(self, verbose_name, name, default=default, required=Tr
ue, **kwds) |
| 621 |
| 622 def validate(self, value): |
| 623 if self.validator: |
| 624 self.validator(value) |
| 625 if value is not None: |
| 626 if not isinstance(value, list): |
| 627 value = [value] |
| 628 |
| 629 if self.item_type in (int, long): |
| 630 item_type = (int, long) |
| 631 elif self.item_type in (str, unicode): |
| 632 item_type = (str, unicode) |
| 633 else: |
| 634 item_type = self.item_type |
| 635 |
| 636 for item in value: |
| 637 if not isinstance(item, item_type): |
| 638 if item_type == (int, long): |
| 639 raise ValueError('Items in the %s list must all be integers.
' % self.name) |
| 640 else: |
| 641 raise ValueError('Items in the %s list must all be %s instan
ces' % |
| 642 (self.name, self.item_type.__name__)) |
| 643 return value |
| 644 |
| 645 def empty(self, value): |
| 646 return value is None |
| 647 |
| 648 def default_value(self): |
| 649 return list(super(ListProperty, self).default_value()) |
| 650 |
| 651 def __set__(self, obj, value): |
| 652 """Override the set method to allow them to set the property to an insta
nce of the item_type instead of requiring a list to be passed in""" |
| 653 if self.item_type in (int, long): |
| 654 item_type = (int, long) |
| 655 elif self.item_type in (str, unicode): |
| 656 item_type = (str, unicode) |
| 657 else: |
| 658 item_type = self.item_type |
| 659 if isinstance(value, item_type): |
| 660 value = [value] |
| 661 elif value == None: # Override to allow them to set this to "None" to r
emove everything |
| 662 value = [] |
| 663 return super(ListProperty, self).__set__(obj, value) |
| 664 |
| 665 |
| 666 class MapProperty(Property): |
| 667 |
| 668 data_type = dict |
| 669 type_name = 'Map' |
| 670 |
| 671 def __init__(self, item_type=str, verbose_name=None, name=None, default=None
, **kwds): |
| 672 if default is None: |
| 673 default = {} |
| 674 self.item_type = item_type |
| 675 Property.__init__(self, verbose_name, name, default=default, required=Tr
ue, **kwds) |
| 676 |
| 677 def validate(self, value): |
| 678 value = super(MapProperty, self).validate(value) |
| 679 if value is not None: |
| 680 if not isinstance(value, dict): |
| 681 raise ValueError('Value must of type dict') |
| 682 |
| 683 if self.item_type in (int, long): |
| 684 item_type = (int, long) |
| 685 elif self.item_type in (str, unicode): |
| 686 item_type = (str, unicode) |
| 687 else: |
| 688 item_type = self.item_type |
| 689 |
| 690 for key in value: |
| 691 if not isinstance(value[key], item_type): |
| 692 if item_type == (int, long): |
| 693 raise ValueError('Values in the %s Map must all be integers.
' % self.name) |
| 694 else: |
| 695 raise ValueError('Values in the %s Map must all be %s instan
ces' % |
| 696 (self.name, self.item_type.__name__)) |
| 697 return value |
| 698 |
| 699 def empty(self, value): |
| 700 return value is None |
| 701 |
| 702 def default_value(self): |
| 703 return {} |
OLD | NEW |