PageRenderTime 58ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/mongoengine/fields.py

https://github.com/alex/mongoengine
Python | 515 lines | 331 code | 111 blank | 73 comment | 90 complexity | deb20f0aab5fd0acb16863d91b4cdb89 MD5 | raw file
  1. from base import BaseField, ObjectIdField, ValidationError, get_document
  2. from document import Document, EmbeddedDocument
  3. from connection import _get_db
  4. from operator import itemgetter
  5. import re
  6. import pymongo
  7. import datetime
  8. import decimal
  9. __all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
  10. 'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
  11. 'ObjectIdField', 'ReferenceField', 'ValidationError',
  12. 'DecimalField', 'URLField', 'GenericReferenceField',
  13. 'BinaryField', 'SortedListField', 'EmailField', 'GeoLocationField']
  14. RECURSIVE_REFERENCE_CONSTANT = 'self'
  15. class StringField(BaseField):
  16. """A unicode string field.
  17. """
  18. def __init__(self, regex=None, max_length=None, min_length=None, **kwargs):
  19. self.regex = re.compile(regex) if regex else None
  20. self.max_length = max_length
  21. self.min_length = min_length
  22. super(StringField, self).__init__(**kwargs)
  23. def to_python(self, value):
  24. return unicode(value)
  25. def validate(self, value):
  26. assert isinstance(value, (str, unicode))
  27. if self.max_length is not None and len(value) > self.max_length:
  28. raise ValidationError('String value is too long')
  29. if self.min_length is not None and len(value) < self.min_length:
  30. raise ValidationError('String value is too short')
  31. if self.regex is not None and self.regex.match(value) is None:
  32. message = 'String value did not match validation regex'
  33. raise ValidationError(message)
  34. def lookup_member(self, member_name):
  35. return None
  36. def prepare_query_value(self, op, value):
  37. if not isinstance(op, basestring):
  38. return value
  39. if op.lstrip('i') in ('startswith', 'endswith', 'contains'):
  40. flags = 0
  41. if op.startswith('i'):
  42. flags = re.IGNORECASE
  43. op = op.lstrip('i')
  44. regex = r'%s'
  45. if op == 'startswith':
  46. regex = r'^%s'
  47. elif op == 'endswith':
  48. regex = r'%s$'
  49. value = re.compile(regex % value, flags)
  50. return value
  51. class URLField(StringField):
  52. """A field that validates input as an URL.
  53. .. versionadded:: 0.3
  54. """
  55. URL_REGEX = re.compile(
  56. r'^https?://'
  57. r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
  58. r'localhost|'
  59. r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
  60. r'(?::\d+)?'
  61. r'(?:/?|[/?]\S+)$', re.IGNORECASE
  62. )
  63. def __init__(self, verify_exists=False, **kwargs):
  64. self.verify_exists = verify_exists
  65. super(URLField, self).__init__(**kwargs)
  66. def validate(self, value):
  67. if not URLField.URL_REGEX.match(value):
  68. raise ValidationError('Invalid URL: %s' % value)
  69. if self.verify_exists:
  70. import urllib2
  71. try:
  72. request = urllib2.Request(value)
  73. response = urllib2.urlopen(request)
  74. except Exception, e:
  75. message = 'This URL appears to be a broken link: %s' % e
  76. raise ValidationError(message)
  77. class EmailField(StringField):
  78. """A field that validates input as an E-Mail-Address.
  79. """
  80. EMAIL_REGEX = re.compile(
  81. r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
  82. r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
  83. r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE # domain
  84. )
  85. def validate(self, value):
  86. if not EmailField.EMAIL_REGEX.match(value):
  87. raise ValidationError('Invalid Mail-address: %s' % value)
  88. class IntField(BaseField):
  89. """An integer field.
  90. """
  91. def __init__(self, min_value=None, max_value=None, **kwargs):
  92. self.min_value, self.max_value = min_value, max_value
  93. super(IntField, self).__init__(**kwargs)
  94. def to_python(self, value):
  95. return int(value)
  96. def validate(self, value):
  97. try:
  98. value = int(value)
  99. except:
  100. raise ValidationError('%s could not be converted to int' % value)
  101. if self.min_value is not None and value < self.min_value:
  102. raise ValidationError('Integer value is too small')
  103. if self.max_value is not None and value > self.max_value:
  104. raise ValidationError('Integer value is too large')
  105. class FloatField(BaseField):
  106. """An floating point number field.
  107. """
  108. def __init__(self, min_value=None, max_value=None, **kwargs):
  109. self.min_value, self.max_value = min_value, max_value
  110. super(FloatField, self).__init__(**kwargs)
  111. def to_python(self, value):
  112. return float(value)
  113. def validate(self, value):
  114. if isinstance(value, int):
  115. value = float(value)
  116. assert isinstance(value, float)
  117. if self.min_value is not None and value < self.min_value:
  118. raise ValidationError('Float value is too small')
  119. if self.max_value is not None and value > self.max_value:
  120. raise ValidationError('Float value is too large')
  121. class DecimalField(BaseField):
  122. """A fixed-point decimal number field.
  123. .. versionadded:: 0.3
  124. """
  125. def __init__(self, min_value=None, max_value=None, **kwargs):
  126. self.min_value, self.max_value = min_value, max_value
  127. super(DecimalField, self).__init__(**kwargs)
  128. def to_python(self, value):
  129. if not isinstance(value, basestring):
  130. value = unicode(value)
  131. return decimal.Decimal(value)
  132. def to_mongo(self, value):
  133. return unicode(value)
  134. def validate(self, value):
  135. if not isinstance(value, decimal.Decimal):
  136. if not isinstance(value, basestring):
  137. value = str(value)
  138. try:
  139. value = decimal.Decimal(value)
  140. except Exception, exc:
  141. raise ValidationError('Could not convert to decimal: %s' % exc)
  142. if self.min_value is not None and value < self.min_value:
  143. raise ValidationError('Decimal value is too small')
  144. if self.max_value is not None and value > self.max_value:
  145. raise ValidationError('Decimal value is too large')
  146. class BooleanField(BaseField):
  147. """A boolean field type.
  148. .. versionadded:: 0.1.2
  149. """
  150. def to_python(self, value):
  151. return bool(value)
  152. def validate(self, value):
  153. assert isinstance(value, bool)
  154. class DateTimeField(BaseField):
  155. """A datetime field.
  156. """
  157. def validate(self, value):
  158. assert isinstance(value, datetime.datetime)
  159. class EmbeddedDocumentField(BaseField):
  160. """An embedded document field. Only valid values are subclasses of
  161. :class:`~mongoengine.EmbeddedDocument`.
  162. """
  163. def __init__(self, document, **kwargs):
  164. if not issubclass(document, EmbeddedDocument):
  165. raise ValidationError('Invalid embedded document class provided '
  166. 'to an EmbeddedDocumentField')
  167. self.document = document
  168. super(EmbeddedDocumentField, self).__init__(**kwargs)
  169. def to_python(self, value):
  170. if not isinstance(value, self.document):
  171. return self.document._from_son(value)
  172. return value
  173. def to_mongo(self, value):
  174. return self.document.to_mongo(value)
  175. def validate(self, value):
  176. """Make sure that the document instance is an instance of the
  177. EmbeddedDocument subclass provided when the document was defined.
  178. """
  179. # Using isinstance also works for subclasses of self.document
  180. if not isinstance(value, self.document):
  181. raise ValidationError('Invalid embedded document instance '
  182. 'provided to an EmbeddedDocumentField')
  183. self.document.validate(value)
  184. def lookup_member(self, member_name):
  185. return self.document._fields.get(member_name)
  186. def prepare_query_value(self, op, value):
  187. return self.to_mongo(value)
  188. class ListField(BaseField):
  189. """A list field that wraps a standard field, allowing multiple instances
  190. of the field to be used as a list in the database.
  191. """
  192. # ListFields cannot be indexed with _types - MongoDB doesn't support this
  193. _index_with_types = False
  194. def __init__(self, field, **kwargs):
  195. if not isinstance(field, BaseField):
  196. raise ValidationError('Argument to ListField constructor must be '
  197. 'a valid field')
  198. self.field = field
  199. super(ListField, self).__init__(**kwargs)
  200. def __get__(self, instance, owner):
  201. """Descriptor to automatically dereference references.
  202. """
  203. if instance is None:
  204. # Document class being used rather than a document object
  205. return self
  206. if isinstance(self.field, ReferenceField):
  207. referenced_type = self.field.document_type
  208. # Get value from document instance if available
  209. value_list = instance._data.get(self.name)
  210. if value_list:
  211. deref_list = []
  212. for value in value_list:
  213. # Dereference DBRefs
  214. if isinstance(value, (pymongo.dbref.DBRef)):
  215. value = _get_db().dereference(value)
  216. deref_list.append(referenced_type._from_son(value))
  217. else:
  218. deref_list.append(value)
  219. instance._data[self.name] = deref_list
  220. if isinstance(self.field, GenericReferenceField):
  221. value_list = instance._data.get(self.name)
  222. if value_list:
  223. deref_list = []
  224. for value in value_list:
  225. # Dereference DBRefs
  226. if isinstance(value, (dict, pymongo.son.SON)):
  227. deref_list.append(self.field.dereference(value))
  228. else:
  229. deref_list.append(value)
  230. instance._data[self.name] = deref_list
  231. return super(ListField, self).__get__(instance, owner)
  232. def to_python(self, value):
  233. return [self.field.to_python(item) for item in value]
  234. def to_mongo(self, value):
  235. return [self.field.to_mongo(item) for item in value]
  236. def validate(self, value):
  237. """Make sure that a list of valid fields is being used.
  238. """
  239. if not isinstance(value, (list, tuple)):
  240. raise ValidationError('Only lists and tuples may be used in a '
  241. 'list field')
  242. try:
  243. [self.field.validate(item) for item in value]
  244. except Exception, err:
  245. raise ValidationError('Invalid ListField item (%s)' % str(err))
  246. def prepare_query_value(self, op, value):
  247. if op in ('set', 'unset'):
  248. return [self.field.to_mongo(v) for v in value]
  249. return self.field.to_mongo(value)
  250. def lookup_member(self, member_name):
  251. return self.field.lookup_member(member_name)
  252. class SortedListField(ListField):
  253. """A ListField that sorts the contents of its list before writing to
  254. the database in order to ensure that a sorted list is always
  255. retrieved.
  256. """
  257. _ordering = None
  258. def __init__(self, field, **kwargs):
  259. if 'ordering' in kwargs.keys():
  260. self._ordering = kwargs.pop('ordering')
  261. super(SortedListField, self).__init__(field, **kwargs)
  262. def to_mongo(self, value):
  263. if self._ordering is not None:
  264. return sorted([self.field.to_mongo(item) for item in value], key=itemgetter(self._ordering))
  265. return sorted([self.field.to_mongo(item) for item in value])
  266. class DictField(BaseField):
  267. """A dictionary field that wraps a standard Python dictionary. This is
  268. similar to an embedded document, but the structure is not defined.
  269. .. versionadded:: 0.3
  270. """
  271. def validate(self, value):
  272. """Make sure that a list of valid fields is being used.
  273. """
  274. if not isinstance(value, dict):
  275. raise ValidationError('Only dictionaries may be used in a '
  276. 'DictField')
  277. if any(('.' in k or '$' in k) for k in value):
  278. raise ValidationError('Invalid dictionary key name - keys may not '
  279. 'contain "." or "$" characters')
  280. def lookup_member(self, member_name):
  281. return BaseField(db_field=member_name)
  282. class GeoLocationField(DictField):
  283. """Supports geobased fields"""
  284. def validate(self, value):
  285. """Make sure that a geo-value is of type (x, y)
  286. """
  287. if not isinstance(value, tuple) and not isinstance(value, list):
  288. raise ValidationError('GeoLocationField can only hold tuples or lists of (x, y)')
  289. if len(value) <> 2:
  290. raise ValidationError('GeoLocationField must have exactly two elements (x, y)')
  291. def to_mongo(self, value):
  292. return {'x': value[0], 'y': value[1]}
  293. def to_python(self, value):
  294. return value.keys()
  295. class ReferenceField(BaseField):
  296. """A reference to a document that will be automatically dereferenced on
  297. access (lazily).
  298. """
  299. def __init__(self, document_type, **kwargs):
  300. if not isinstance(document_type, basestring):
  301. if not issubclass(document_type, (Document, basestring)):
  302. raise ValidationError('Argument to ReferenceField constructor '
  303. 'must be a document class or a string')
  304. self.document_type_obj = document_type
  305. self.document_obj = None
  306. super(ReferenceField, self).__init__(**kwargs)
  307. @property
  308. def document_type(self):
  309. if isinstance(self.document_type_obj, basestring):
  310. if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
  311. self.document_type_obj = self.owner_document
  312. else:
  313. self.document_type_obj = get_document(self.document_type_obj)
  314. return self.document_type_obj
  315. def __get__(self, instance, owner):
  316. """Descriptor to allow lazy dereferencing.
  317. """
  318. if instance is None:
  319. # Document class being used rather than a document object
  320. return self
  321. # Get value from document instance if available
  322. value = instance._data.get(self.name)
  323. # Dereference DBRefs
  324. if isinstance(value, (pymongo.dbref.DBRef)):
  325. value = _get_db().dereference(value)
  326. if value is not None:
  327. instance._data[self.name] = self.document_type._from_son(value)
  328. return super(ReferenceField, self).__get__(instance, owner)
  329. def to_mongo(self, document):
  330. id_field_name = self.document_type._meta['id_field']
  331. id_field = self.document_type._fields[id_field_name]
  332. if isinstance(document, Document):
  333. # We need the id from the saved object to create the DBRef
  334. id_ = document.id
  335. if id_ is None:
  336. raise ValidationError('You can only reference documents once '
  337. 'they have been saved to the database')
  338. else:
  339. id_ = document
  340. id_ = id_field.to_mongo(id_)
  341. collection = self.document_type._meta['collection']
  342. return pymongo.dbref.DBRef(collection, id_)
  343. def prepare_query_value(self, op, value):
  344. return self.to_mongo(value)
  345. def validate(self, value):
  346. assert isinstance(value, (self.document_type, pymongo.dbref.DBRef))
  347. def lookup_member(self, member_name):
  348. return self.document_type._fields.get(member_name)
  349. class GenericReferenceField(BaseField):
  350. """A reference to *any* :class:`~mongoengine.document.Document` subclass
  351. that will be automatically dereferenced on access (lazily).
  352. .. versionadded:: 0.3
  353. """
  354. def __get__(self, instance, owner):
  355. if instance is None:
  356. return self
  357. value = instance._data.get(self.name)
  358. if isinstance(value, (dict, pymongo.son.SON)):
  359. instance._data[self.name] = self.dereference(value)
  360. return super(GenericReferenceField, self).__get__(instance, owner)
  361. def dereference(self, value):
  362. doc_cls = get_document(value['_cls'])
  363. reference = value['_ref']
  364. doc = _get_db().dereference(reference)
  365. if doc is not None:
  366. doc = doc_cls._from_son(doc)
  367. return doc
  368. def to_mongo(self, document):
  369. id_field_name = document.__class__._meta['id_field']
  370. id_field = document.__class__._fields[id_field_name]
  371. if isinstance(document, Document):
  372. # We need the id from the saved object to create the DBRef
  373. id_ = document.id
  374. if id_ is None:
  375. raise ValidationError('You can only reference documents once '
  376. 'they have been saved to the database')
  377. else:
  378. id_ = document
  379. id_ = id_field.to_mongo(id_)
  380. collection = document._meta['collection']
  381. ref = pymongo.dbref.DBRef(collection, id_)
  382. return {'_cls': document.__class__.__name__, '_ref': ref}
  383. def prepare_query_value(self, op, value):
  384. return self.to_mongo(value)['_ref']
  385. class BinaryField(BaseField):
  386. """A binary data field.
  387. """
  388. def __init__(self, max_bytes=None, **kwargs):
  389. self.max_bytes = max_bytes
  390. super(BinaryField, self).__init__(**kwargs)
  391. def to_mongo(self, value):
  392. return pymongo.binary.Binary(value)
  393. def to_python(self, value):
  394. # Returns str not unicode as this is binary data
  395. return str(value)
  396. def validate(self, value):
  397. assert isinstance(value, str)
  398. if self.max_bytes is not None and len(value) > self.max_bytes:
  399. raise ValidationError('Binary value is too long')