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

/rest_framework/relations.py

https://github.com/yangdy/rftut
Python | 644 lines | 603 code | 16 blank | 25 comment | 23 complexity | 80bde4a29da321311ec09543227a0ac5 MD5 | raw file
  1. """
  2. Serializer fields that deal with relationships.
  3. These fields allow you to specify the style that should be used to represent
  4. model relationships, including hyperlinks, primary keys, or slugs.
  5. """
  6. from __future__ import unicode_literals
  7. from django.core.exceptions import ObjectDoesNotExist, ValidationError
  8. from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch
  9. from django import forms
  10. from django.db.models.fields import BLANK_CHOICE_DASH
  11. from django.forms import widgets
  12. from django.forms.models import ModelChoiceIterator
  13. from django.utils.translation import ugettext_lazy as _
  14. from rest_framework.fields import Field, WritableField, get_component, is_simple_callable
  15. from rest_framework.reverse import reverse
  16. from rest_framework.compat import urlparse
  17. from rest_framework.compat import smart_text
  18. import warnings
  19. ##### Relational fields #####
  20. # Not actually Writable, but subclasses may need to be.
  21. class RelatedField(WritableField):
  22. """
  23. Base class for related model fields.
  24. This represents a relationship using the unicode representation of the target.
  25. """
  26. widget = widgets.Select
  27. many_widget = widgets.SelectMultiple
  28. form_field_class = forms.ChoiceField
  29. many_form_field_class = forms.MultipleChoiceField
  30. null_values = (None, '', 'None')
  31. cache_choices = False
  32. empty_label = None
  33. read_only = True
  34. many = False
  35. def __init__(self, *args, **kwargs):
  36. # 'null' is to be deprecated in favor of 'required'
  37. if 'null' in kwargs:
  38. warnings.warn('The `null` keyword argument is deprecated. '
  39. 'Use the `required` keyword argument instead.',
  40. DeprecationWarning, stacklevel=2)
  41. kwargs['required'] = not kwargs.pop('null')
  42. queryset = kwargs.pop('queryset', None)
  43. self.many = kwargs.pop('many', self.many)
  44. if self.many:
  45. self.widget = self.many_widget
  46. self.form_field_class = self.many_form_field_class
  47. kwargs['read_only'] = kwargs.pop('read_only', self.read_only)
  48. super(RelatedField, self).__init__(*args, **kwargs)
  49. if not self.required:
  50. self.empty_label = BLANK_CHOICE_DASH[0][1]
  51. self.queryset = queryset
  52. def initialize(self, parent, field_name):
  53. super(RelatedField, self).initialize(parent, field_name)
  54. if self.queryset is None and not self.read_only:
  55. manager = getattr(self.parent.opts.model, self.source or field_name)
  56. if hasattr(manager, 'related'): # Forward
  57. self.queryset = manager.related.model._default_manager.all()
  58. else: # Reverse
  59. self.queryset = manager.field.rel.to._default_manager.all()
  60. ### We need this stuff to make form choices work...
  61. def prepare_value(self, obj):
  62. return self.to_native(obj)
  63. def label_from_instance(self, obj):
  64. """
  65. Return a readable representation for use with eg. select widgets.
  66. """
  67. desc = smart_text(obj)
  68. ident = smart_text(self.to_native(obj))
  69. if desc == ident:
  70. return desc
  71. return "%s - %s" % (desc, ident)
  72. def _get_queryset(self):
  73. return self._queryset
  74. def _set_queryset(self, queryset):
  75. self._queryset = queryset
  76. self.widget.choices = self.choices
  77. queryset = property(_get_queryset, _set_queryset)
  78. def _get_choices(self):
  79. # If self._choices is set, then somebody must have manually set
  80. # the property self.choices. In this case, just return self._choices.
  81. if hasattr(self, '_choices'):
  82. return self._choices
  83. # Otherwise, execute the QuerySet in self.queryset to determine the
  84. # choices dynamically. Return a fresh ModelChoiceIterator that has not been
  85. # consumed. Note that we're instantiating a new ModelChoiceIterator *each*
  86. # time _get_choices() is called (and, thus, each time self.choices is
  87. # accessed) so that we can ensure the QuerySet has not been consumed. This
  88. # construct might look complicated but it allows for lazy evaluation of
  89. # the queryset.
  90. return ModelChoiceIterator(self)
  91. def _set_choices(self, value):
  92. # Setting choices also sets the choices on the widget.
  93. # choices can be any iterable, but we call list() on it because
  94. # it will be consumed more than once.
  95. self._choices = self.widget.choices = list(value)
  96. choices = property(_get_choices, _set_choices)
  97. ### Default value handling
  98. def get_default_value(self):
  99. default = super(RelatedField, self).get_default_value()
  100. if self.many and default is None:
  101. return []
  102. return default
  103. ### Regular serializer stuff...
  104. def field_to_native(self, obj, field_name):
  105. try:
  106. if self.source == '*':
  107. return self.to_native(obj)
  108. source = self.source or field_name
  109. value = obj
  110. for component in source.split('.'):
  111. if value is None:
  112. break
  113. value = get_component(value, component)
  114. except ObjectDoesNotExist:
  115. return None
  116. if value is None:
  117. return None
  118. if self.many:
  119. if is_simple_callable(getattr(value, 'all', None)):
  120. return [self.to_native(item) for item in value.all()]
  121. else:
  122. # Also support non-queryset iterables.
  123. # This allows us to also support plain lists of related items.
  124. return [self.to_native(item) for item in value]
  125. return self.to_native(value)
  126. def field_from_native(self, data, files, field_name, into):
  127. if self.read_only:
  128. return
  129. try:
  130. if self.many:
  131. try:
  132. # Form data
  133. value = data.getlist(field_name)
  134. if value == [''] or value == []:
  135. raise KeyError
  136. except AttributeError:
  137. # Non-form data
  138. value = data[field_name]
  139. else:
  140. value = data[field_name]
  141. except KeyError:
  142. if self.partial:
  143. return
  144. value = self.get_default_value()
  145. if value in self.null_values:
  146. if self.required:
  147. raise ValidationError(self.error_messages['required'])
  148. into[(self.source or field_name)] = None
  149. elif self.many:
  150. into[(self.source or field_name)] = [self.from_native(item) for item in value]
  151. else:
  152. into[(self.source or field_name)] = self.from_native(value)
  153. ### PrimaryKey relationships
  154. class PrimaryKeyRelatedField(RelatedField):
  155. """
  156. Represents a relationship as a pk value.
  157. """
  158. read_only = False
  159. default_error_messages = {
  160. 'does_not_exist': _("Invalid pk '%s' - object does not exist."),
  161. 'incorrect_type': _('Incorrect type. Expected pk value, received %s.'),
  162. }
  163. # TODO: Remove these field hacks...
  164. def prepare_value(self, obj):
  165. return self.to_native(obj.pk)
  166. def label_from_instance(self, obj):
  167. """
  168. Return a readable representation for use with eg. select widgets.
  169. """
  170. desc = smart_text(obj)
  171. ident = smart_text(self.to_native(obj.pk))
  172. if desc == ident:
  173. return desc
  174. return "%s - %s" % (desc, ident)
  175. # TODO: Possibly change this to just take `obj`, through prob less performant
  176. def to_native(self, pk):
  177. return pk
  178. def from_native(self, data):
  179. if self.queryset is None:
  180. raise Exception('Writable related fields must include a `queryset` argument')
  181. try:
  182. return self.queryset.get(pk=data)
  183. except ObjectDoesNotExist:
  184. msg = self.error_messages['does_not_exist'] % smart_text(data)
  185. raise ValidationError(msg)
  186. except (TypeError, ValueError):
  187. received = type(data).__name__
  188. msg = self.error_messages['incorrect_type'] % received
  189. raise ValidationError(msg)
  190. def field_to_native(self, obj, field_name):
  191. if self.many:
  192. # To-many relationship
  193. queryset = None
  194. if not self.source:
  195. # Prefer obj.serializable_value for performance reasons
  196. try:
  197. queryset = obj.serializable_value(field_name)
  198. except AttributeError:
  199. pass
  200. if queryset is None:
  201. # RelatedManager (reverse relationship)
  202. source = self.source or field_name
  203. queryset = obj
  204. for component in source.split('.'):
  205. if queryset is None:
  206. return []
  207. queryset = get_component(queryset, component)
  208. # Forward relationship
  209. if is_simple_callable(getattr(queryset, 'all', None)):
  210. return [self.to_native(item.pk) for item in queryset.all()]
  211. else:
  212. # Also support non-queryset iterables.
  213. # This allows us to also support plain lists of related items.
  214. return [self.to_native(item.pk) for item in queryset]
  215. # To-one relationship
  216. try:
  217. # Prefer obj.serializable_value for performance reasons
  218. pk = obj.serializable_value(self.source or field_name)
  219. except AttributeError:
  220. # RelatedObject (reverse relationship)
  221. try:
  222. pk = getattr(obj, self.source or field_name).pk
  223. except (ObjectDoesNotExist, AttributeError):
  224. return None
  225. # Forward relationship
  226. return self.to_native(pk)
  227. ### Slug relationships
  228. class SlugRelatedField(RelatedField):
  229. """
  230. Represents a relationship using a unique field on the target.
  231. """
  232. read_only = False
  233. default_error_messages = {
  234. 'does_not_exist': _("Object with %s=%s does not exist."),
  235. 'invalid': _('Invalid value.'),
  236. }
  237. def __init__(self, *args, **kwargs):
  238. self.slug_field = kwargs.pop('slug_field', None)
  239. assert self.slug_field, 'slug_field is required'
  240. super(SlugRelatedField, self).__init__(*args, **kwargs)
  241. def to_native(self, obj):
  242. return getattr(obj, self.slug_field)
  243. def from_native(self, data):
  244. if self.queryset is None:
  245. raise Exception('Writable related fields must include a `queryset` argument')
  246. try:
  247. return self.queryset.get(**{self.slug_field: data})
  248. except ObjectDoesNotExist:
  249. raise ValidationError(self.error_messages['does_not_exist'] %
  250. (self.slug_field, smart_text(data)))
  251. except (TypeError, ValueError):
  252. msg = self.error_messages['invalid']
  253. raise ValidationError(msg)
  254. ### Hyperlinked relationships
  255. class HyperlinkedRelatedField(RelatedField):
  256. """
  257. Represents a relationship using hyperlinking.
  258. """
  259. read_only = False
  260. lookup_field = 'pk'
  261. default_error_messages = {
  262. 'no_match': _('Invalid hyperlink - No URL match'),
  263. 'incorrect_match': _('Invalid hyperlink - Incorrect URL match'),
  264. 'configuration_error': _('Invalid hyperlink due to configuration error'),
  265. 'does_not_exist': _("Invalid hyperlink - object does not exist."),
  266. 'incorrect_type': _('Incorrect type. Expected url string, received %s.'),
  267. }
  268. # These are all pending deprecation
  269. pk_url_kwarg = 'pk'
  270. slug_field = 'slug'
  271. slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
  272. def __init__(self, *args, **kwargs):
  273. try:
  274. self.view_name = kwargs.pop('view_name')
  275. except KeyError:
  276. raise ValueError("Hyperlinked field requires 'view_name' kwarg")
  277. self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
  278. self.format = kwargs.pop('format', None)
  279. # These are pending deprecation
  280. if 'pk_url_kwarg' in kwargs:
  281. msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.'
  282. warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
  283. if 'slug_url_kwarg' in kwargs:
  284. msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.'
  285. warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
  286. if 'slug_field' in kwargs:
  287. msg = 'slug_field is pending deprecation. Use lookup_field instead.'
  288. warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
  289. self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg)
  290. self.slug_field = kwargs.pop('slug_field', self.slug_field)
  291. default_slug_kwarg = self.slug_url_kwarg or self.slug_field
  292. self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg)
  293. super(HyperlinkedRelatedField, self).__init__(*args, **kwargs)
  294. def get_url(self, obj, view_name, request, format):
  295. """
  296. Given an object, return the URL that hyperlinks to the object.
  297. May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
  298. attributes are not configured to correctly match the URL conf.
  299. """
  300. lookup_field = getattr(obj, self.lookup_field)
  301. kwargs = {self.lookup_field: lookup_field}
  302. try:
  303. return reverse(view_name, kwargs=kwargs, request=request, format=format)
  304. except NoReverseMatch:
  305. pass
  306. if self.pk_url_kwarg != 'pk':
  307. # Only try pk if it has been explicitly set.
  308. # Otherwise, the default `lookup_field = 'pk'` has us covered.
  309. pk = obj.pk
  310. kwargs = {self.pk_url_kwarg: pk}
  311. try:
  312. return reverse(view_name, kwargs=kwargs, request=request, format=format)
  313. except NoReverseMatch:
  314. pass
  315. slug = getattr(obj, self.slug_field, None)
  316. if slug is not None:
  317. # Only try slug if it corresponds to an attribute on the object.
  318. kwargs = {self.slug_url_kwarg: slug}
  319. try:
  320. ret = reverse(view_name, kwargs=kwargs, request=request, format=format)
  321. if self.slug_field == 'slug' and self.slug_url_kwarg == 'slug':
  322. # If the lookup succeeds using the default slug params,
  323. # then `slug_field` is being used implicitly, and we
  324. # we need to warn about the pending deprecation.
  325. msg = 'Implicit slug field hyperlinked fields are pending deprecation.' \
  326. 'You should set `lookup_field=slug` on the HyperlinkedRelatedField.'
  327. warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
  328. return ret
  329. except NoReverseMatch:
  330. pass
  331. raise NoReverseMatch()
  332. def get_object(self, queryset, view_name, view_args, view_kwargs):
  333. """
  334. Return the object corresponding to a matched URL.
  335. Takes the matched URL conf arguments, and the queryset, and should
  336. return an object instance, or raise an `ObjectDoesNotExist` exception.
  337. """
  338. lookup = view_kwargs.get(self.lookup_field, None)
  339. pk = view_kwargs.get(self.pk_url_kwarg, None)
  340. slug = view_kwargs.get(self.slug_url_kwarg, None)
  341. if lookup is not None:
  342. filter_kwargs = {self.lookup_field: lookup}
  343. elif pk is not None:
  344. filter_kwargs = {'pk': pk}
  345. elif slug is not None:
  346. filter_kwargs = {self.slug_field: slug}
  347. else:
  348. raise ObjectDoesNotExist()
  349. return queryset.get(**filter_kwargs)
  350. def to_native(self, obj):
  351. view_name = self.view_name
  352. request = self.context.get('request', None)
  353. format = self.format or self.context.get('format', None)
  354. if request is None:
  355. msg = (
  356. "Using `HyperlinkedRelatedField` without including the request "
  357. "in the serializer context is deprecated. "
  358. "Add `context={'request': request}` when instantiating "
  359. "the serializer."
  360. )
  361. warnings.warn(msg, DeprecationWarning, stacklevel=4)
  362. # If the object has not yet been saved then we cannot hyperlink to it.
  363. if getattr(obj, 'pk', None) is None:
  364. return
  365. # Return the hyperlink, or error if incorrectly configured.
  366. try:
  367. return self.get_url(obj, view_name, request, format)
  368. except NoReverseMatch:
  369. msg = (
  370. 'Could not resolve URL for hyperlinked relationship using '
  371. 'view name "%s". You may have failed to include the related '
  372. 'model in your API, or incorrectly configured the '
  373. '`lookup_field` attribute on this field.'
  374. )
  375. raise Exception(msg % view_name)
  376. def from_native(self, value):
  377. # Convert URL -> model instance pk
  378. # TODO: Use values_list
  379. queryset = self.queryset
  380. if queryset is None:
  381. raise Exception('Writable related fields must include a `queryset` argument')
  382. try:
  383. http_prefix = value.startswith(('http:', 'https:'))
  384. except AttributeError:
  385. msg = self.error_messages['incorrect_type']
  386. raise ValidationError(msg % type(value).__name__)
  387. if http_prefix:
  388. # If needed convert absolute URLs to relative path
  389. value = urlparse.urlparse(value).path
  390. prefix = get_script_prefix()
  391. if value.startswith(prefix):
  392. value = '/' + value[len(prefix):]
  393. try:
  394. match = resolve(value)
  395. except Exception:
  396. raise ValidationError(self.error_messages['no_match'])
  397. if match.view_name != self.view_name:
  398. raise ValidationError(self.error_messages['incorrect_match'])
  399. try:
  400. return self.get_object(queryset, match.view_name,
  401. match.args, match.kwargs)
  402. except (ObjectDoesNotExist, TypeError, ValueError):
  403. raise ValidationError(self.error_messages['does_not_exist'])
  404. class HyperlinkedIdentityField(Field):
  405. """
  406. Represents the instance, or a property on the instance, using hyperlinking.
  407. """
  408. lookup_field = 'pk'
  409. read_only = True
  410. # These are all pending deprecation
  411. pk_url_kwarg = 'pk'
  412. slug_field = 'slug'
  413. slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
  414. def __init__(self, *args, **kwargs):
  415. try:
  416. self.view_name = kwargs.pop('view_name')
  417. except KeyError:
  418. msg = "HyperlinkedIdentityField requires 'view_name' argument"
  419. raise ValueError(msg)
  420. self.format = kwargs.pop('format', None)
  421. lookup_field = kwargs.pop('lookup_field', None)
  422. self.lookup_field = lookup_field or self.lookup_field
  423. # These are pending deprecation
  424. if 'pk_url_kwarg' in kwargs:
  425. msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.'
  426. warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
  427. if 'slug_url_kwarg' in kwargs:
  428. msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.'
  429. warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
  430. if 'slug_field' in kwargs:
  431. msg = 'slug_field is pending deprecation. Use lookup_field instead.'
  432. warnings.warn(msg, PendingDeprecationWarning, stacklevel=2)
  433. self.slug_field = kwargs.pop('slug_field', self.slug_field)
  434. default_slug_kwarg = self.slug_url_kwarg or self.slug_field
  435. self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg)
  436. self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg)
  437. super(HyperlinkedIdentityField, self).__init__(*args, **kwargs)
  438. def field_to_native(self, obj, field_name):
  439. request = self.context.get('request', None)
  440. format = self.context.get('format', None)
  441. view_name = self.view_name
  442. if request is None:
  443. warnings.warn("Using `HyperlinkedIdentityField` without including the "
  444. "request in the serializer context is deprecated. "
  445. "Add `context={'request': request}` when instantiating the serializer.",
  446. DeprecationWarning, stacklevel=4)
  447. # By default use whatever format is given for the current context
  448. # unless the target is a different type to the source.
  449. #
  450. # Eg. Consider a HyperlinkedIdentityField pointing from a json
  451. # representation to an html property of that representation...
  452. #
  453. # '/snippets/1/' should link to '/snippets/1/highlight/'
  454. # ...but...
  455. # '/snippets/1/.json' should link to '/snippets/1/highlight/.html'
  456. if format and self.format and self.format != format:
  457. format = self.format
  458. # Return the hyperlink, or error if incorrectly configured.
  459. try:
  460. return self.get_url(obj, view_name, request, format)
  461. except NoReverseMatch:
  462. msg = (
  463. 'Could not resolve URL for hyperlinked relationship using '
  464. 'view name "%s". You may have failed to include the related '
  465. 'model in your API, or incorrectly configured the '
  466. '`lookup_field` attribute on this field.'
  467. )
  468. raise Exception(msg % view_name)
  469. def get_url(self, obj, view_name, request, format):
  470. """
  471. Given an object, return the URL that hyperlinks to the object.
  472. May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
  473. attributes are not configured to correctly match the URL conf.
  474. """
  475. lookup_field = getattr(obj, self.lookup_field, None)
  476. kwargs = {self.lookup_field: lookup_field}
  477. # Handle unsaved object case
  478. if lookup_field is None:
  479. return None
  480. try:
  481. return reverse(view_name, kwargs=kwargs, request=request, format=format)
  482. except NoReverseMatch:
  483. pass
  484. if self.pk_url_kwarg != 'pk':
  485. # Only try pk lookup if it has been explicitly set.
  486. # Otherwise, the default `lookup_field = 'pk'` has us covered.
  487. kwargs = {self.pk_url_kwarg: obj.pk}
  488. try:
  489. return reverse(view_name, kwargs=kwargs, request=request, format=format)
  490. except NoReverseMatch:
  491. pass
  492. slug = getattr(obj, self.slug_field, None)
  493. if slug:
  494. # Only use slug lookup if a slug field exists on the model
  495. kwargs = {self.slug_url_kwarg: slug}
  496. try:
  497. return reverse(view_name, kwargs=kwargs, request=request, format=format)
  498. except NoReverseMatch:
  499. pass
  500. raise NoReverseMatch()
  501. ### Old-style many classes for backwards compat
  502. class ManyRelatedField(RelatedField):
  503. def __init__(self, *args, **kwargs):
  504. warnings.warn('`ManyRelatedField()` is deprecated. '
  505. 'Use `RelatedField(many=True)` instead.',
  506. DeprecationWarning, stacklevel=2)
  507. kwargs['many'] = True
  508. super(ManyRelatedField, self).__init__(*args, **kwargs)
  509. class ManyPrimaryKeyRelatedField(PrimaryKeyRelatedField):
  510. def __init__(self, *args, **kwargs):
  511. warnings.warn('`ManyPrimaryKeyRelatedField()` is deprecated. '
  512. 'Use `PrimaryKeyRelatedField(many=True)` instead.',
  513. DeprecationWarning, stacklevel=2)
  514. kwargs['many'] = True
  515. super(ManyPrimaryKeyRelatedField, self).__init__(*args, **kwargs)
  516. class ManySlugRelatedField(SlugRelatedField):
  517. def __init__(self, *args, **kwargs):
  518. warnings.warn('`ManySlugRelatedField()` is deprecated. '
  519. 'Use `SlugRelatedField(many=True)` instead.',
  520. DeprecationWarning, stacklevel=2)
  521. kwargs['many'] = True
  522. super(ManySlugRelatedField, self).__init__(*args, **kwargs)
  523. class ManyHyperlinkedRelatedField(HyperlinkedRelatedField):
  524. def __init__(self, *args, **kwargs):
  525. warnings.warn('`ManyHyperlinkedRelatedField()` is deprecated. '
  526. 'Use `HyperlinkedRelatedField(many=True)` instead.',
  527. DeprecationWarning, stacklevel=2)
  528. kwargs['many'] = True
  529. super(ManyHyperlinkedRelatedField, self).__init__(*args, **kwargs)