PageRenderTime 31ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/mongoengine/fields.py

http://github.com/hmarr/mongoengine
Python | 1433 lines | 1121 code | 152 blank | 160 comment | 150 complexity | b75492dc710b0f11f8d9ae0fa5ff9691 MD5 | raw file
  1. import datetime
  2. import decimal
  3. import re
  4. import time
  5. import uuid
  6. import warnings
  7. import itertools
  8. from operator import itemgetter
  9. import gridfs
  10. from bson import Binary, DBRef, SON, ObjectId
  11. from mongoengine.python_support import (PY3, bin_type, txt_type,
  12. str_types, StringIO)
  13. from base import (BaseField, ComplexBaseField, ObjectIdField,
  14. ValidationError, get_document, BaseDocument)
  15. from queryset import DO_NOTHING, QuerySet
  16. from document import Document, EmbeddedDocument
  17. from connection import get_db, DEFAULT_CONNECTION_NAME
  18. try:
  19. from PIL import Image, ImageOps
  20. except ImportError:
  21. Image = None
  22. ImageOps = None
  23. __all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
  24. 'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
  25. 'ObjectIdField', 'ReferenceField', 'ValidationError', 'MapField',
  26. 'DecimalField', 'ComplexDateTimeField', 'URLField', 'DynamicField',
  27. 'GenericReferenceField', 'FileField', 'BinaryField',
  28. 'SortedListField', 'EmailField', 'GeoPointField', 'ImageField',
  29. 'SequenceField', 'UUIDField', 'GenericEmbeddedDocumentField']
  30. RECURSIVE_REFERENCE_CONSTANT = 'self'
  31. class StringField(BaseField):
  32. """A unicode string field.
  33. """
  34. def __init__(self, regex=None, max_length=None, min_length=None, **kwargs):
  35. self.regex = re.compile(regex) if regex else None
  36. self.max_length = max_length
  37. self.min_length = min_length
  38. super(StringField, self).__init__(**kwargs)
  39. def to_python(self, value):
  40. if isinstance(value, unicode):
  41. return value
  42. try:
  43. value = value.decode('utf-8')
  44. except:
  45. pass
  46. return value
  47. def validate(self, value):
  48. if not isinstance(value, basestring):
  49. self.error('StringField only accepts string values')
  50. if self.max_length is not None and len(value) > self.max_length:
  51. self.error('String value is too long')
  52. if self.min_length is not None and len(value) < self.min_length:
  53. self.error('String value is too short')
  54. if self.regex is not None and self.regex.match(value) is None:
  55. self.error('String value did not match validation regex')
  56. def lookup_member(self, member_name):
  57. return None
  58. def prepare_query_value(self, op, value):
  59. if not isinstance(op, basestring):
  60. return value
  61. if op.lstrip('i') in ('startswith', 'endswith', 'contains', 'exact'):
  62. flags = 0
  63. if op.startswith('i'):
  64. flags = re.IGNORECASE
  65. op = op.lstrip('i')
  66. regex = r'%s'
  67. if op == 'startswith':
  68. regex = r'^%s'
  69. elif op == 'endswith':
  70. regex = r'%s$'
  71. elif op == 'exact':
  72. regex = r'^%s$'
  73. # escape unsafe characters which could lead to a re.error
  74. value = re.escape(value)
  75. value = re.compile(regex % value, flags)
  76. return value
  77. class URLField(StringField):
  78. """A field that validates input as an URL.
  79. .. versionadded:: 0.3
  80. """
  81. URL_REGEX = re.compile(
  82. r'^https?://'
  83. r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
  84. r'localhost|'
  85. r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
  86. r'(?::\d+)?'
  87. r'(?:/?|[/?]\S+)$', re.IGNORECASE
  88. )
  89. def __init__(self, verify_exists=False, **kwargs):
  90. self.verify_exists = verify_exists
  91. super(URLField, self).__init__(**kwargs)
  92. def validate(self, value):
  93. if not URLField.URL_REGEX.match(value):
  94. self.error('Invalid URL: %s' % value)
  95. if self.verify_exists:
  96. import urllib2
  97. try:
  98. request = urllib2.Request(value)
  99. urllib2.urlopen(request)
  100. except Exception, e:
  101. self.error('This URL appears to be a broken link: %s' % e)
  102. class EmailField(StringField):
  103. """A field that validates input as an E-Mail-Address.
  104. .. versionadded:: 0.4
  105. """
  106. EMAIL_REGEX = re.compile(
  107. r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
  108. r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
  109. r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE # domain
  110. )
  111. def validate(self, value):
  112. if not EmailField.EMAIL_REGEX.match(value):
  113. self.error('Invalid Mail-address: %s' % value)
  114. class IntField(BaseField):
  115. """An integer field.
  116. """
  117. def __init__(self, min_value=None, max_value=None, **kwargs):
  118. self.min_value, self.max_value = min_value, max_value
  119. super(IntField, self).__init__(**kwargs)
  120. def to_python(self, value):
  121. try:
  122. value = int(value)
  123. except ValueError:
  124. pass
  125. return value
  126. def validate(self, value):
  127. try:
  128. value = int(value)
  129. except:
  130. self.error('%s could not be converted to int' % value)
  131. if self.min_value is not None and value < self.min_value:
  132. self.error('Integer value is too small')
  133. if self.max_value is not None and value > self.max_value:
  134. self.error('Integer value is too large')
  135. def prepare_query_value(self, op, value):
  136. if value is None:
  137. return value
  138. return int(value)
  139. class FloatField(BaseField):
  140. """An floating point number field.
  141. """
  142. def __init__(self, min_value=None, max_value=None, **kwargs):
  143. self.min_value, self.max_value = min_value, max_value
  144. super(FloatField, self).__init__(**kwargs)
  145. def to_python(self, value):
  146. try:
  147. value = float(value)
  148. except ValueError:
  149. pass
  150. return value
  151. def validate(self, value):
  152. if isinstance(value, int):
  153. value = float(value)
  154. if not isinstance(value, float):
  155. self.error('FloatField only accepts float values')
  156. if self.min_value is not None and value < self.min_value:
  157. self.error('Float value is too small')
  158. if self.max_value is not None and value > self.max_value:
  159. self.error('Float value is too large')
  160. def prepare_query_value(self, op, value):
  161. if value is None:
  162. return value
  163. return float(value)
  164. class DecimalField(BaseField):
  165. """A fixed-point decimal number field.
  166. .. versionadded:: 0.3
  167. """
  168. def __init__(self, min_value=None, max_value=None, **kwargs):
  169. self.min_value, self.max_value = min_value, max_value
  170. super(DecimalField, self).__init__(**kwargs)
  171. def to_python(self, value):
  172. original_value = value
  173. if not isinstance(value, basestring):
  174. value = unicode(value)
  175. try:
  176. value = decimal.Decimal(value)
  177. except ValueError:
  178. return original_value
  179. return value
  180. def to_mongo(self, value):
  181. return unicode(value)
  182. def validate(self, value):
  183. if not isinstance(value, decimal.Decimal):
  184. if not isinstance(value, basestring):
  185. value = str(value)
  186. try:
  187. value = decimal.Decimal(value)
  188. except Exception, exc:
  189. self.error('Could not convert value to decimal: %s' % exc)
  190. if self.min_value is not None and value < self.min_value:
  191. self.error('Decimal value is too small')
  192. if self.max_value is not None and value > self.max_value:
  193. self.error('Decimal value is too large')
  194. class BooleanField(BaseField):
  195. """A boolean field type.
  196. .. versionadded:: 0.1.2
  197. """
  198. def to_python(self, value):
  199. try:
  200. value = bool(value)
  201. except ValueError:
  202. pass
  203. return value
  204. def validate(self, value):
  205. if not isinstance(value, bool):
  206. self.error('BooleanField only accepts boolean values')
  207. class DateTimeField(BaseField):
  208. """A datetime field.
  209. Note: Microseconds are rounded to the nearest millisecond.
  210. Pre UTC microsecond support is effecively broken.
  211. Use :class:`~mongoengine.fields.ComplexDateTimeField` if you
  212. need accurate microsecond support.
  213. """
  214. def validate(self, value):
  215. if not isinstance(value, (datetime.datetime, datetime.date)):
  216. self.error(u'cannot parse date "%s"' % value)
  217. def to_mongo(self, value):
  218. return self.prepare_query_value(None, value)
  219. def prepare_query_value(self, op, value):
  220. if value is None:
  221. return value
  222. if isinstance(value, datetime.datetime):
  223. return value
  224. if isinstance(value, datetime.date):
  225. return datetime.datetime(value.year, value.month, value.day)
  226. # Attempt to parse a datetime:
  227. # value = smart_str(value)
  228. # split usecs, because they are not recognized by strptime.
  229. if '.' in value:
  230. try:
  231. value, usecs = value.split('.')
  232. usecs = int(usecs)
  233. except ValueError:
  234. return None
  235. else:
  236. usecs = 0
  237. kwargs = {'microsecond': usecs}
  238. try: # Seconds are optional, so try converting seconds first.
  239. return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6],
  240. **kwargs)
  241. except ValueError:
  242. try: # Try without seconds.
  243. return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5],
  244. **kwargs)
  245. except ValueError: # Try without hour/minutes/seconds.
  246. try:
  247. return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
  248. **kwargs)
  249. except ValueError:
  250. return None
  251. class ComplexDateTimeField(StringField):
  252. """
  253. ComplexDateTimeField handles microseconds exactly instead of rounding
  254. like DateTimeField does.
  255. Derives from a StringField so you can do `gte` and `lte` filtering by
  256. using lexicographical comparison when filtering / sorting strings.
  257. The stored string has the following format:
  258. YYYY,MM,DD,HH,MM,SS,NNNNNN
  259. Where NNNNNN is the number of microseconds of the represented `datetime`.
  260. The `,` as the separator can be easily modified by passing the `separator`
  261. keyword when initializing the field.
  262. .. versionadded:: 0.5
  263. """
  264. def __init__(self, separator=',', **kwargs):
  265. self.names = ['year', 'month', 'day', 'hour', 'minute', 'second',
  266. 'microsecond']
  267. self.separtor = separator
  268. super(ComplexDateTimeField, self).__init__(**kwargs)
  269. def _leading_zero(self, number):
  270. """
  271. Converts the given number to a string.
  272. If it has only one digit, a leading zero so as it has always at least
  273. two digits.
  274. """
  275. if int(number) < 10:
  276. return "0%s" % number
  277. else:
  278. return str(number)
  279. def _convert_from_datetime(self, val):
  280. """
  281. Convert a `datetime` object to a string representation (which will be
  282. stored in MongoDB). This is the reverse function of
  283. `_convert_from_string`.
  284. >>> a = datetime(2011, 6, 8, 20, 26, 24, 192284)
  285. >>> RealDateTimeField()._convert_from_datetime(a)
  286. '2011,06,08,20,26,24,192284'
  287. """
  288. data = []
  289. for name in self.names:
  290. data.append(self._leading_zero(getattr(val, name)))
  291. return ','.join(data)
  292. def _convert_from_string(self, data):
  293. """
  294. Convert a string representation to a `datetime` object (the object you
  295. will manipulate). This is the reverse function of
  296. `_convert_from_datetime`.
  297. >>> a = '2011,06,08,20,26,24,192284'
  298. >>> ComplexDateTimeField()._convert_from_string(a)
  299. datetime.datetime(2011, 6, 8, 20, 26, 24, 192284)
  300. """
  301. data = data.split(',')
  302. data = map(int, data)
  303. values = {}
  304. for i in range(7):
  305. values[self.names[i]] = data[i]
  306. return datetime.datetime(**values)
  307. def __get__(self, instance, owner):
  308. data = super(ComplexDateTimeField, self).__get__(instance, owner)
  309. if data == None:
  310. return datetime.datetime.now()
  311. if isinstance(data, datetime.datetime):
  312. return data
  313. return self._convert_from_string(data)
  314. def __set__(self, instance, value):
  315. value = self._convert_from_datetime(value) if value else value
  316. return super(ComplexDateTimeField, self).__set__(instance, value)
  317. def validate(self, value):
  318. if not isinstance(value, datetime.datetime):
  319. self.error('Only datetime objects may used in a '
  320. 'ComplexDateTimeField')
  321. def to_python(self, value):
  322. original_value = value
  323. try:
  324. return self._convert_from_string(value)
  325. except:
  326. return original_value
  327. def to_mongo(self, value):
  328. return self._convert_from_datetime(value)
  329. def prepare_query_value(self, op, value):
  330. return self._convert_from_datetime(value)
  331. class EmbeddedDocumentField(BaseField):
  332. """An embedded document field - with a declared document_type.
  333. Only valid values are subclasses of :class:`~mongoengine.EmbeddedDocument`.
  334. """
  335. def __init__(self, document_type, **kwargs):
  336. if not isinstance(document_type, basestring):
  337. if not issubclass(document_type, EmbeddedDocument):
  338. self.error('Invalid embedded document class provided to an '
  339. 'EmbeddedDocumentField')
  340. self.document_type_obj = document_type
  341. super(EmbeddedDocumentField, self).__init__(**kwargs)
  342. @property
  343. def document_type(self):
  344. if isinstance(self.document_type_obj, basestring):
  345. if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
  346. self.document_type_obj = self.owner_document
  347. else:
  348. self.document_type_obj = get_document(self.document_type_obj)
  349. return self.document_type_obj
  350. def to_python(self, value):
  351. if not isinstance(value, self.document_type):
  352. return self.document_type._from_son(value)
  353. return value
  354. def to_mongo(self, value):
  355. if not isinstance(value, self.document_type):
  356. return value
  357. return self.document_type.to_mongo(value)
  358. def validate(self, value):
  359. """Make sure that the document instance is an instance of the
  360. EmbeddedDocument subclass provided when the document was defined.
  361. """
  362. # Using isinstance also works for subclasses of self.document
  363. if not isinstance(value, self.document_type):
  364. self.error('Invalid embedded document instance provided to an '
  365. 'EmbeddedDocumentField')
  366. self.document_type.validate(value)
  367. def lookup_member(self, member_name):
  368. return self.document_type._fields.get(member_name)
  369. def prepare_query_value(self, op, value):
  370. return self.to_mongo(value)
  371. class GenericEmbeddedDocumentField(BaseField):
  372. """A generic embedded document field - allows any
  373. :class:`~mongoengine.EmbeddedDocument` to be stored.
  374. Only valid values are subclasses of :class:`~mongoengine.EmbeddedDocument`.
  375. .. note ::
  376. You can use the choices param to limit the acceptable
  377. EmbeddedDocument types
  378. """
  379. def prepare_query_value(self, op, value):
  380. return self.to_mongo(value)
  381. def to_python(self, value):
  382. if isinstance(value, dict):
  383. doc_cls = get_document(value['_cls'])
  384. value = doc_cls._from_son(value)
  385. return value
  386. def validate(self, value):
  387. if not isinstance(value, EmbeddedDocument):
  388. self.error('Invalid embedded document instance provided to an '
  389. 'GenericEmbeddedDocumentField')
  390. value.validate()
  391. def to_mongo(self, document):
  392. if document is None:
  393. return None
  394. data = document.to_mongo()
  395. if not '_cls' in data:
  396. data['_cls'] = document._class_name
  397. return data
  398. class DynamicField(BaseField):
  399. """A truly dynamic field type capable of handling different and varying
  400. types of data.
  401. Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
  402. def to_mongo(self, value):
  403. """Convert a Python type to a MongoDBcompatible type.
  404. """
  405. if isinstance(value, basestring):
  406. return value
  407. if hasattr(value, 'to_mongo'):
  408. return value.to_mongo()
  409. if not isinstance(value, (dict, list, tuple)):
  410. return value
  411. is_list = False
  412. if not hasattr(value, 'items'):
  413. is_list = True
  414. value = dict([(k, v) for k, v in enumerate(value)])
  415. data = {}
  416. for k, v in value.items():
  417. data[k] = self.to_mongo(v)
  418. if is_list: # Convert back to a list
  419. value = [v for k, v in sorted(data.items(), key=itemgetter(0))]
  420. else:
  421. value = data
  422. return value
  423. def lookup_member(self, member_name):
  424. return member_name
  425. def prepare_query_value(self, op, value):
  426. if isinstance(value, basestring):
  427. from mongoengine.fields import StringField
  428. return StringField().prepare_query_value(op, value)
  429. return self.to_mongo(value)
  430. class ListField(ComplexBaseField):
  431. """A list field that wraps a standard field, allowing multiple instances
  432. of the field to be used as a list in the database.
  433. If using with ReferenceFields see: :ref:`one-to-many-with-listfields`
  434. .. note::
  435. Required means it cannot be empty - as the default for ListFields is []
  436. """
  437. # ListFields cannot be indexed with _types - MongoDB doesn't support this
  438. _index_with_types = False
  439. def __init__(self, field=None, **kwargs):
  440. self.field = field
  441. kwargs.setdefault('default', lambda: [])
  442. super(ListField, self).__init__(**kwargs)
  443. def validate(self, value):
  444. """Make sure that a list of valid fields is being used.
  445. """
  446. if (not isinstance(value, (list, tuple, QuerySet)) or
  447. isinstance(value, basestring)):
  448. self.error('Only lists and tuples may be used in a list field')
  449. super(ListField, self).validate(value)
  450. def prepare_query_value(self, op, value):
  451. if self.field:
  452. if op in ('set', 'unset') and (not isinstance(value, basestring)
  453. and not isinstance(value, BaseDocument)
  454. and hasattr(value, '__iter__')):
  455. return [self.field.prepare_query_value(op, v) for v in value]
  456. return self.field.prepare_query_value(op, value)
  457. return super(ListField, self).prepare_query_value(op, value)
  458. class SortedListField(ListField):
  459. """A ListField that sorts the contents of its list before writing to
  460. the database in order to ensure that a sorted list is always
  461. retrieved.
  462. .. warning::
  463. There is a potential race condition when handling lists. If you set /
  464. save the whole list then other processes trying to save the whole list
  465. as well could overwrite changes. The safest way to append to a list is
  466. to perform a push operation.
  467. .. versionadded:: 0.4
  468. .. versionchanged:: 0.6 - added reverse keyword
  469. """
  470. _ordering = None
  471. _order_reverse = False
  472. def __init__(self, field, **kwargs):
  473. if 'ordering' in kwargs.keys():
  474. self._ordering = kwargs.pop('ordering')
  475. if 'reverse' in kwargs.keys():
  476. self._order_reverse = kwargs.pop('reverse')
  477. super(SortedListField, self).__init__(field, **kwargs)
  478. def to_mongo(self, value):
  479. value = super(SortedListField, self).to_mongo(value)
  480. if self._ordering is not None:
  481. return sorted(value, key=itemgetter(self._ordering), reverse=self._order_reverse)
  482. return sorted(value, reverse=self._order_reverse)
  483. class DictField(ComplexBaseField):
  484. """A dictionary field that wraps a standard Python dictionary. This is
  485. similar to an embedded document, but the structure is not defined.
  486. .. note::
  487. Required means it cannot be empty - as the default for ListFields is []
  488. .. versionadded:: 0.3
  489. .. versionchanged:: 0.5 - Can now handle complex / varying types of data
  490. """
  491. def __init__(self, basecls=None, field=None, *args, **kwargs):
  492. self.field = field
  493. self.basecls = basecls or BaseField
  494. if not issubclass(self.basecls, BaseField):
  495. self.error('DictField only accepts dict values')
  496. kwargs.setdefault('default', lambda: {})
  497. super(DictField, self).__init__(*args, **kwargs)
  498. def validate(self, value):
  499. """Make sure that a list of valid fields is being used.
  500. """
  501. if not isinstance(value, dict):
  502. self.error('Only dictionaries may be used in a DictField')
  503. if any(k for k in value.keys() if not isinstance(k, basestring)):
  504. self.error('Invalid dictionary key - documents must have only string keys')
  505. if any(('.' in k or '$' in k) for k in value.keys()):
  506. self.error('Invalid dictionary key name - keys may not contain "."'
  507. ' or "$" characters')
  508. super(DictField, self).validate(value)
  509. def lookup_member(self, member_name):
  510. return DictField(basecls=self.basecls, db_field=member_name)
  511. def prepare_query_value(self, op, value):
  512. match_operators = ['contains', 'icontains', 'startswith',
  513. 'istartswith', 'endswith', 'iendswith',
  514. 'exact', 'iexact']
  515. if op in match_operators and isinstance(value, basestring):
  516. return StringField().prepare_query_value(op, value)
  517. return super(DictField, self).prepare_query_value(op, value)
  518. class MapField(DictField):
  519. """A field that maps a name to a specified field type. Similar to
  520. a DictField, except the 'value' of each item must match the specified
  521. field type.
  522. .. versionadded:: 0.5
  523. """
  524. def __init__(self, field=None, *args, **kwargs):
  525. if not isinstance(field, BaseField):
  526. self.error('Argument to MapField constructor must be a valid '
  527. 'field')
  528. super(MapField, self).__init__(field=field, *args, **kwargs)
  529. class ReferenceField(BaseField):
  530. """A reference to a document that will be automatically dereferenced on
  531. access (lazily).
  532. Use the `reverse_delete_rule` to handle what should happen if the document
  533. the field is referencing is deleted. EmbeddedDocuments, DictFields and
  534. MapFields do not support reverse_delete_rules and an `InvalidDocumentError`
  535. will be raised if trying to set on one of these Document / Field types.
  536. The options are:
  537. * DO_NOTHING - don't do anything (default).
  538. * NULLIFY - Updates the reference to null.
  539. * CASCADE - Deletes the documents associated with the reference.
  540. * DENY - Prevent the deletion of the reference object.
  541. * PULL - Pull the reference from a :class:`~mongoengine.ListField`
  542. of references
  543. Alternative syntax for registering delete rules (useful when implementing
  544. bi-directional delete rules)
  545. .. code-block:: python
  546. class Bar(Document):
  547. content = StringField()
  548. foo = ReferenceField('Foo')
  549. Bar.register_delete_rule(Foo, 'bar', NULLIFY)
  550. .. note ::
  551. `reverse_delete_rules` do not trigger pre / post delete signals to be
  552. triggered.
  553. .. versionchanged:: 0.5 added `reverse_delete_rule`
  554. """
  555. def __init__(self, document_type, dbref=None,
  556. reverse_delete_rule=DO_NOTHING, **kwargs):
  557. """Initialises the Reference Field.
  558. :param dbref: Store the reference as :class:`~pymongo.dbref.DBRef`
  559. or as the :class:`~pymongo.objectid.ObjectId`.id .
  560. :param reverse_delete_rule: Determines what to do when the referring
  561. object is deleted
  562. """
  563. if not isinstance(document_type, basestring):
  564. if not issubclass(document_type, (Document, basestring)):
  565. self.error('Argument to ReferenceField constructor must be a '
  566. 'document class or a string')
  567. if dbref is None:
  568. msg = ("ReferenceFields will default to using ObjectId "
  569. " strings in 0.8, set DBRef=True if this isn't desired")
  570. warnings.warn(msg, FutureWarning)
  571. self.dbref = dbref if dbref is not None else True # To change in 0.8
  572. self.document_type_obj = document_type
  573. self.reverse_delete_rule = reverse_delete_rule
  574. super(ReferenceField, self).__init__(**kwargs)
  575. @property
  576. def document_type(self):
  577. if isinstance(self.document_type_obj, basestring):
  578. if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
  579. self.document_type_obj = self.owner_document
  580. else:
  581. self.document_type_obj = get_document(self.document_type_obj)
  582. return self.document_type_obj
  583. def __get__(self, instance, owner):
  584. """Descriptor to allow lazy dereferencing.
  585. """
  586. if instance is None:
  587. # Document class being used rather than a document object
  588. return self
  589. # Get value from document instance if available
  590. value = instance._data.get(self.name)
  591. # Dereference DBRefs
  592. if isinstance(value, DBRef):
  593. value = self.document_type._get_db().dereference(value)
  594. if value is not None:
  595. instance._data[self.name] = self.document_type._from_son(value)
  596. return super(ReferenceField, self).__get__(instance, owner)
  597. def to_mongo(self, document):
  598. if isinstance(document, DBRef):
  599. if not self.dbref:
  600. return DBRef.id
  601. return document
  602. elif not self.dbref and isinstance(document, basestring):
  603. return document
  604. id_field_name = self.document_type._meta['id_field']
  605. id_field = self.document_type._fields[id_field_name]
  606. if isinstance(document, Document):
  607. # We need the id from the saved object to create the DBRef
  608. id_ = document.pk
  609. if id_ is None:
  610. self.error('You can only reference documents once they have'
  611. ' been saved to the database')
  612. else:
  613. id_ = document
  614. id_ = id_field.to_mongo(id_)
  615. if self.dbref:
  616. collection = self.document_type._get_collection_name()
  617. return DBRef(collection, id_)
  618. return id_
  619. def to_python(self, value):
  620. """Convert a MongoDB-compatible type to a Python type.
  621. """
  622. if (not self.dbref and
  623. not isinstance(value, (DBRef, Document, EmbeddedDocument))):
  624. collection = self.document_type._get_collection_name()
  625. value = DBRef(collection, self.document_type.id.to_python(value))
  626. return value
  627. def prepare_query_value(self, op, value):
  628. if value is None:
  629. return None
  630. return self.to_mongo(value)
  631. def validate(self, value):
  632. if not isinstance(value, (self.document_type, DBRef)):
  633. self.error("A ReferenceField only accepts DBRef or documents")
  634. if isinstance(value, Document) and value.id is None:
  635. self.error('You can only reference documents once they have been '
  636. 'saved to the database')
  637. def lookup_member(self, member_name):
  638. return self.document_type._fields.get(member_name)
  639. class GenericReferenceField(BaseField):
  640. """A reference to *any* :class:`~mongoengine.document.Document` subclass
  641. that will be automatically dereferenced on access (lazily).
  642. .. note ::
  643. * Any documents used as a generic reference must be registered in the
  644. document registry. Importing the model will automatically register
  645. it.
  646. * You can use the choices param to limit the acceptable Document types
  647. .. versionadded:: 0.3
  648. """
  649. def __get__(self, instance, owner):
  650. if instance is None:
  651. return self
  652. value = instance._data.get(self.name)
  653. if isinstance(value, (dict, SON)):
  654. instance._data[self.name] = self.dereference(value)
  655. return super(GenericReferenceField, self).__get__(instance, owner)
  656. def validate(self, value):
  657. if not isinstance(value, (Document, DBRef)):
  658. self.error('GenericReferences can only contain documents')
  659. # We need the id from the saved object to create the DBRef
  660. if isinstance(value, Document) and value.id is None:
  661. self.error('You can only reference documents once they have been'
  662. ' saved to the database')
  663. def dereference(self, value):
  664. doc_cls = get_document(value['_cls'])
  665. reference = value['_ref']
  666. doc = doc_cls._get_db().dereference(reference)
  667. if doc is not None:
  668. doc = doc_cls._from_son(doc)
  669. return doc
  670. def to_mongo(self, document):
  671. if document is None:
  672. return None
  673. if isinstance(document, (dict, SON)):
  674. return document
  675. id_field_name = document.__class__._meta['id_field']
  676. id_field = document.__class__._fields[id_field_name]
  677. if isinstance(document, Document):
  678. # We need the id from the saved object to create the DBRef
  679. id_ = document.id
  680. if id_ is None:
  681. self.error('You can only reference documents once they have'
  682. ' been saved to the database')
  683. else:
  684. id_ = document
  685. id_ = id_field.to_mongo(id_)
  686. collection = document._get_collection_name()
  687. ref = DBRef(collection, id_)
  688. return {'_cls': document._class_name, '_ref': ref}
  689. def prepare_query_value(self, op, value):
  690. if value is None:
  691. return None
  692. return self.to_mongo(value)
  693. class BinaryField(BaseField):
  694. """A binary data field.
  695. """
  696. def __init__(self, max_bytes=None, **kwargs):
  697. self.max_bytes = max_bytes
  698. super(BinaryField, self).__init__(**kwargs)
  699. def __set__(self, instance, value):
  700. """Handle bytearrays in python 3.1"""
  701. if PY3 and isinstance(value, bytearray):
  702. value = bin_type(value)
  703. return super(BinaryField, self).__set__(instance, value)
  704. def to_mongo(self, value):
  705. return Binary(value)
  706. def validate(self, value):
  707. if not isinstance(value, (bin_type, txt_type, Binary)):
  708. self.error("BinaryField only accepts instances of "
  709. "(%s, %s, Binary)" % (
  710. bin_type.__name__, txt_type.__name__))
  711. if self.max_bytes is not None and len(value) > self.max_bytes:
  712. self.error('Binary value is too long')
  713. class GridFSError(Exception):
  714. pass
  715. class GridFSProxy(object):
  716. """Proxy object to handle writing and reading of files to and from GridFS
  717. .. versionadded:: 0.4
  718. .. versionchanged:: 0.5 - added optional size param to read
  719. .. versionchanged:: 0.6 - added collection name param
  720. """
  721. _fs = None
  722. def __init__(self, grid_id=None, key=None,
  723. instance=None,
  724. db_alias=DEFAULT_CONNECTION_NAME,
  725. collection_name='fs'):
  726. self.grid_id = grid_id # Store GridFS id for file
  727. self.key = key
  728. self.instance = instance
  729. self.db_alias = db_alias
  730. self.collection_name = collection_name
  731. self.newfile = None # Used for partial writes
  732. self.gridout = None
  733. def __getattr__(self, name):
  734. attrs = ('_fs', 'grid_id', 'key', 'instance', 'db_alias',
  735. 'collection_name', 'newfile', 'gridout')
  736. if name in attrs:
  737. return self.__getattribute__(name)
  738. obj = self.get()
  739. if name in dir(obj):
  740. return getattr(obj, name)
  741. raise AttributeError
  742. def __get__(self, instance, value):
  743. return self
  744. def __nonzero__(self):
  745. return bool(self.grid_id)
  746. def __getstate__(self):
  747. self_dict = self.__dict__
  748. self_dict['_fs'] = None
  749. return self_dict
  750. def __repr__(self):
  751. return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
  752. def __eq__(self, other):
  753. if isinstance(other, GridFSProxy):
  754. return ((self.grid_id == other.grid_id) and
  755. (self.collection_name == other.collection_name) and
  756. (self.db_alias == other.db_alias))
  757. else:
  758. return False
  759. @property
  760. def fs(self):
  761. if not self._fs:
  762. self._fs = gridfs.GridFS(get_db(self.db_alias), self.collection_name)
  763. return self._fs
  764. def get(self, id=None):
  765. if id:
  766. self.grid_id = id
  767. if self.grid_id is None:
  768. return None
  769. try:
  770. if self.gridout is None:
  771. self.gridout = self.fs.get(self.grid_id)
  772. return self.gridout
  773. except:
  774. # File has been deleted
  775. return None
  776. def new_file(self, **kwargs):
  777. self.newfile = self.fs.new_file(**kwargs)
  778. self.grid_id = self.newfile._id
  779. def put(self, file_obj, **kwargs):
  780. if self.grid_id:
  781. raise GridFSError('This document already has a file. Either delete '
  782. 'it or call replace to overwrite it')
  783. self.grid_id = self.fs.put(file_obj, **kwargs)
  784. self._mark_as_changed()
  785. def write(self, string):
  786. if self.grid_id:
  787. if not self.newfile:
  788. raise GridFSError('This document already has a file. Either '
  789. 'delete it or call replace to overwrite it')
  790. else:
  791. self.new_file()
  792. self.newfile.write(string)
  793. def writelines(self, lines):
  794. if not self.newfile:
  795. self.new_file()
  796. self.grid_id = self.newfile._id
  797. self.newfile.writelines(lines)
  798. def read(self, size=-1):
  799. gridout = self.get()
  800. if gridout is None:
  801. return None
  802. else:
  803. try:
  804. return gridout.read(size)
  805. except:
  806. return ""
  807. def delete(self):
  808. # Delete file from GridFS, FileField still remains
  809. self.fs.delete(self.grid_id)
  810. self.grid_id = None
  811. self.gridout = None
  812. self._mark_as_changed()
  813. def replace(self, file_obj, **kwargs):
  814. self.delete()
  815. self.put(file_obj, **kwargs)
  816. def close(self):
  817. if self.newfile:
  818. self.newfile.close()
  819. def _mark_as_changed(self):
  820. """Inform the instance that `self.key` has been changed"""
  821. if self.instance:
  822. self.instance._mark_as_changed(self.key)
  823. class FileField(BaseField):
  824. """A GridFS storage field.
  825. .. versionadded:: 0.4
  826. .. versionchanged:: 0.5 added optional size param for read
  827. .. versionchanged:: 0.6 added db_alias for multidb support
  828. """
  829. proxy_class = GridFSProxy
  830. def __init__(self,
  831. db_alias=DEFAULT_CONNECTION_NAME,
  832. collection_name="fs", **kwargs):
  833. super(FileField, self).__init__(**kwargs)
  834. self.collection_name = collection_name
  835. self.db_alias = db_alias
  836. def __get__(self, instance, owner):
  837. if instance is None:
  838. return self
  839. # Check if a file already exists for this model
  840. grid_file = instance._data.get(self.name)
  841. if not isinstance(grid_file, self.proxy_class):
  842. grid_file = self.proxy_class(key=self.name, instance=instance,
  843. db_alias=self.db_alias,
  844. collection_name=self.collection_name)
  845. instance._data[self.name] = grid_file
  846. if not grid_file.key:
  847. grid_file.key = self.name
  848. grid_file.instance = instance
  849. return grid_file
  850. def __set__(self, instance, value):
  851. key = self.name
  852. if ((hasattr(value, 'read') and not
  853. isinstance(value, GridFSProxy)) or isinstance(value, str_types)):
  854. # using "FileField() = file/string" notation
  855. grid_file = instance._data.get(self.name)
  856. # If a file already exists, delete it
  857. if grid_file:
  858. try:
  859. grid_file.delete()
  860. except:
  861. pass
  862. # Create a new file with the new data
  863. grid_file.put(value)
  864. else:
  865. # Create a new proxy object as we don't already have one
  866. instance._data[key] = self.proxy_class(key=key, instance=instance,
  867. collection_name=self.collection_name)
  868. instance._data[key].put(value)
  869. else:
  870. instance._data[key] = value
  871. instance._mark_as_changed(key)
  872. def to_mongo(self, value):
  873. # Store the GridFS file id in MongoDB
  874. if isinstance(value, self.proxy_class) and value.grid_id is not None:
  875. return value.grid_id
  876. return None
  877. def to_python(self, value):
  878. if value is not None:
  879. return self.proxy_class(value,
  880. collection_name=self.collection_name,
  881. db_alias=self.db_alias)
  882. def validate(self, value):
  883. if value.grid_id is not None:
  884. if not isinstance(value, self.proxy_class):
  885. self.error('FileField only accepts GridFSProxy values')
  886. if not isinstance(value.grid_id, ObjectId):
  887. self.error('Invalid GridFSProxy value')
  888. class ImageGridFsProxy(GridFSProxy):
  889. """
  890. Proxy for ImageField
  891. versionadded: 0.6
  892. """
  893. def put(self, file_obj, **kwargs):
  894. """
  895. Insert a image in database
  896. applying field properties (size, thumbnail_size)
  897. """
  898. field = self.instance._fields[self.key]
  899. try:
  900. img = Image.open(file_obj)
  901. img_format = img.format
  902. except:
  903. raise ValidationError('Invalid image')
  904. if (field.size and (img.size[0] > field.size['width'] or
  905. img.size[1] > field.size['height'])):
  906. size = field.size
  907. if size['force']:
  908. img = ImageOps.fit(img,
  909. (size['width'],
  910. size['height']),
  911. Image.ANTIALIAS)
  912. else:
  913. img.thumbnail((size['width'],
  914. size['height']),
  915. Image.ANTIALIAS)
  916. thumbnail = None
  917. if field.thumbnail_size:
  918. size = field.thumbnail_size
  919. if size['force']:
  920. thumbnail = ImageOps.fit(img,
  921. (size['width'],
  922. size['height']),
  923. Image.ANTIALIAS)
  924. else:
  925. thumbnail = img.copy()
  926. thumbnail.thumbnail((size['width'],
  927. size['height']),
  928. Image.ANTIALIAS)
  929. if thumbnail:
  930. thumb_id = self._put_thumbnail(thumbnail,
  931. img_format)
  932. else:
  933. thumb_id = None
  934. w, h = img.size
  935. io = StringIO()
  936. img.save(io, img_format)
  937. io.seek(0)
  938. return super(ImageGridFsProxy, self).put(io,
  939. width=w,
  940. height=h,
  941. format=img_format,
  942. thumbnail_id=thumb_id,
  943. **kwargs)
  944. def delete(self, *args, **kwargs):
  945. #deletes thumbnail
  946. out = self.get()
  947. if out and out.thumbnail_id:
  948. self.fs.delete(out.thumbnail_id)
  949. return super(ImageGridFsProxy, self).delete(*args, **kwargs)
  950. def _put_thumbnail(self, thumbnail, format, **kwargs):
  951. w, h = thumbnail.size
  952. io = StringIO()
  953. thumbnail.save(io, format)
  954. io.seek(0)
  955. return self.fs.put(io, width=w,
  956. height=h,
  957. format=format,
  958. **kwargs)
  959. @property
  960. def size(self):
  961. """
  962. return a width, height of image
  963. """
  964. out = self.get()
  965. if out:
  966. return out.width, out.height
  967. @property
  968. def format(self):
  969. """
  970. return format of image
  971. ex: PNG, JPEG, GIF, etc
  972. """
  973. out = self.get()
  974. if out:
  975. return out.format
  976. @property
  977. def thumbnail(self):
  978. """
  979. return a gridfs.grid_file.GridOut
  980. representing a thumbnail of Image
  981. """
  982. out = self.get()
  983. if out and out.thumbnail_id:
  984. return self.fs.get(out.thumbnail_id)
  985. def write(self, *args, **kwargs):
  986. raise RuntimeError("Please use \"put\" method instead")
  987. def writelines(self, *args, **kwargs):
  988. raise RuntimeError("Please use \"put\" method instead")
  989. class ImproperlyConfigured(Exception):
  990. pass
  991. class ImageField(FileField):
  992. """
  993. A Image File storage field.
  994. @size (width, height, force):
  995. max size to store images, if larger will be automatically resized
  996. ex: size=(800, 600, True)
  997. @thumbnail (width, height, force):
  998. size to generate a thumbnail
  999. .. versionadded:: 0.6
  1000. """
  1001. proxy_class = ImageGridFsProxy
  1002. def __init__(self, size=None, thumbnail_size=None,
  1003. collection_name='images', **kwargs):
  1004. if not Image:
  1005. raise ImproperlyConfigured("PIL library was not found")
  1006. params_size = ('width', 'height', 'force')
  1007. extra_args = dict(size=size, thumbnail_size=thumbnail_size)
  1008. for att_name, att in extra_args.items():
  1009. value = None
  1010. if isinstance(att, (tuple, list)):
  1011. if PY3:
  1012. value = dict(itertools.zip_longest(params_size, att,
  1013. fillvalue=None))
  1014. else:
  1015. value = dict(map(None, params_size, att))
  1016. setattr(self, att_name, value)
  1017. super(ImageField, self).__init__(
  1018. collection_name=collection_name,
  1019. **kwargs)
  1020. class GeoPointField(BaseField):
  1021. """A list storing a latitude and longitude.
  1022. .. versionadded:: 0.4
  1023. """
  1024. _geo_index = True
  1025. def validate(self, value):
  1026. """Make sure that a geo-value is of type (x, y)
  1027. """
  1028. if not isinstance(value, (list, tuple)):
  1029. self.error('GeoPointField can only accept tuples or lists '
  1030. 'of (x, y)')
  1031. if not len(value) == 2:
  1032. self.error('Value must be a two-dimensional point')
  1033. if (not isinstance(value[0], (float, int)) and
  1034. not isinstance(value[1], (float, int))):
  1035. self.error('Both values in point must be float or int')
  1036. class SequenceField(IntField):
  1037. """Provides a sequental counter (see http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-SequenceNumbers)
  1038. .. note::
  1039. Although traditional databases often use increasing sequence
  1040. numbers for primary keys. In MongoDB, the preferred approach is to
  1041. use Object IDs instead. The concept is that in a very large
  1042. cluster of machines, it is easier to create an object ID than have
  1043. global, uniformly increasing sequence numbers.
  1044. .. versionadded:: 0.5
  1045. """
  1046. def __init__(self, collection_name=None, db_alias = None, sequence_name = None, *args, **kwargs):
  1047. self.collection_name = collection_name or 'mongoengine.counters'
  1048. self.db_alias = db_alias or DEFAULT_CONNECTION_NAME
  1049. self.sequence_name = sequence_name
  1050. return super(SequenceField, self).__init__(*args, **kwargs)
  1051. def generate_new_value(self):
  1052. """
  1053. Generate and Increment the counter
  1054. """
  1055. sequence_name = self.sequence_name or self.owner_document._get_collection_name()
  1056. sequence_id = "%s.%s" % (sequence_name, self.name)
  1057. collection = get_db(alias=self.db_alias)[self.collection_name]
  1058. counter = collection.find_and_modify(query={"_id": sequence_id},
  1059. update={"$inc": {"next": 1}},
  1060. new=True,
  1061. upsert=True)
  1062. return counter['next']
  1063. def __get__(self, instance, owner):
  1064. if instance is None:
  1065. return self
  1066. if not instance._data:
  1067. return
  1068. value = instance._data.get(self.name)
  1069. if not value and instance._initialised:
  1070. value = self.generate_new_value()
  1071. instance._data[self.name] = value
  1072. instance._mark_as_changed(self.name)
  1073. return int(value) if value else None
  1074. def __set__(self, instance, value):
  1075. if value is None and instance._initialised:
  1076. value = self.generate_new_value()
  1077. return super(SequenceField, self).__set__(instance, value)
  1078. def to_python(self, value):
  1079. if value is None:
  1080. value = self.generate_new_value()
  1081. return value
  1082. class UUIDField(BaseField):
  1083. """A UUID field.
  1084. .. versionadded:: 0.6
  1085. """
  1086. _binary = None
  1087. def __init__(self, binary=None, **kwargs):
  1088. """
  1089. Store UUID data in the database
  1090. :param binary: (optional) boolean store as binary.
  1091. .. versionchanged:: 0.6.19
  1092. """
  1093. if binary is None:
  1094. binary = False
  1095. msg = ("UUIDFields will soon default to store as binary, please "
  1096. "configure binary=False if you wish to store as a string")
  1097. warnings.warn(msg, FutureWarning)
  1098. self._binary = binary
  1099. super(UUIDField, self).__init__(**kwargs)
  1100. def to_python(self, value):
  1101. if not self._binary:
  1102. original_value = value
  1103. try:
  1104. if not isinstance(value, basestring):
  1105. value = unicode(value)
  1106. return uuid.UUID(value)
  1107. except:
  1108. return original_value
  1109. return value
  1110. def to_mongo(self, value):
  1111. if not self._binary:
  1112. return unicode(value)
  1113. return value
  1114. def prepare_query_value(self, op, value):
  1115. if value is None:
  1116. return None
  1117. return self.to_mongo(value)
  1118. def validate(self, value):
  1119. if not isinstance(value, uuid.UUID):
  1120. if not isinstance(value, basestring):
  1121. value = str(value)
  1122. try:
  1123. value = uuid.UUID(value)
  1124. except Exception, exc:
  1125. self.error('Could not convert to UUID: %s' % exc)