|
1 | 1 | from rest_framework.exceptions import ValidationError |
| 2 | +from rest_framework.fields import MISSING_ERROR_MESSAGE |
2 | 3 | from rest_framework.relations import * |
3 | | -from rest_framework_json_api.utils import format_relation_name, get_related_resource_type, \ |
4 | | - get_resource_type_from_queryset, get_resource_type_from_instance |
5 | 4 | from django.utils.translation import ugettext_lazy as _ |
6 | 5 |
|
| 6 | +from rest_framework_json_api.exceptions import Conflict |
| 7 | +from rest_framework_json_api.utils import format_relation_name, Hyperlink, \ |
| 8 | + get_resource_type_from_queryset, get_resource_type_from_instance |
| 9 | + |
7 | 10 |
|
8 | 11 | class HyperlinkedRelatedField(HyperlinkedRelatedField): |
9 | 12 | """ |
@@ -40,22 +43,108 @@ def to_internal_value(self, data): |
40 | 43 |
|
41 | 44 |
|
42 | 45 | class ResourceRelatedField(PrimaryKeyRelatedField): |
| 46 | + self_link_view_name = None |
| 47 | + related_link_view_name = None |
| 48 | + related_link_lookup_field = 'pk' |
| 49 | + |
43 | 50 | default_error_messages = { |
44 | 51 | 'required': _('This field is required.'), |
45 | 52 | 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'), |
46 | | - 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'), |
| 53 | + 'incorrect_type': _('Incorrect type. Expected resource identifier object, received {data_type}.'), |
47 | 54 | 'incorrect_relation_type': _('Incorrect relation type. Expected {relation_type}, received {received_type}.'), |
| 55 | + 'no_match': _('Invalid hyperlink - No URL match.'), |
48 | 56 | } |
49 | 57 |
|
| 58 | + def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwargs): |
| 59 | + if self_link_view_name is not None: |
| 60 | + self.self_link_view_name = self_link_view_name |
| 61 | + if related_link_view_name is not None: |
| 62 | + self.related_link_view_name = related_link_view_name |
| 63 | + |
| 64 | + self.related_link_lookup_field = kwargs.pop('related_link_lookup_field', self.related_link_lookup_field) |
| 65 | + self.related_link_url_kwarg = kwargs.pop('related_link_url_kwarg', self.related_link_lookup_field) |
| 66 | + |
| 67 | + # We include this simply for dependency injection in tests. |
| 68 | + # We can't add it as a class attributes or it would expect an |
| 69 | + # implicit `self` argument to be passed. |
| 70 | + self.reverse = reverse |
| 71 | + |
| 72 | + super(ResourceRelatedField, self).__init__(**kwargs) |
| 73 | + |
| 74 | + def use_pk_only_optimization(self): |
| 75 | + # We need the real object to determine its type... |
| 76 | + return False |
| 77 | + |
| 78 | + def conflict(self, key, **kwargs): |
| 79 | + """ |
| 80 | + A helper method that simply raises a validation error. |
| 81 | + """ |
| 82 | + try: |
| 83 | + msg = self.error_messages[key] |
| 84 | + except KeyError: |
| 85 | + class_name = self.__class__.__name__ |
| 86 | + msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key) |
| 87 | + raise AssertionError(msg) |
| 88 | + message_string = msg.format(**kwargs) |
| 89 | + raise Conflict(message_string) |
| 90 | + |
| 91 | + def get_url(self, name, view_name, kwargs, request): |
| 92 | + """ |
| 93 | + Given a name, view name and kwargs, return the URL that hyperlinks to the object. |
| 94 | +
|
| 95 | + May raise a `NoReverseMatch` if the `view_name` and `lookup_field` |
| 96 | + attributes are not configured to correctly match the URL conf. |
| 97 | + """ |
| 98 | + |
| 99 | + # Return None if the view name is not supplied |
| 100 | + if not view_name: |
| 101 | + return None |
| 102 | + |
| 103 | + # Return the hyperlink, or error if incorrectly configured. |
| 104 | + try: |
| 105 | + url = self.reverse(view_name, kwargs=kwargs, request=request) |
| 106 | + except NoReverseMatch: |
| 107 | + msg = ( |
| 108 | + 'Could not resolve URL for hyperlinked relationship using ' |
| 109 | + 'view name "%s".' |
| 110 | + ) |
| 111 | + raise ImproperlyConfigured(msg % view_name) |
| 112 | + |
| 113 | + if url is None: |
| 114 | + return None |
| 115 | + |
| 116 | + return Hyperlink(url, name) |
| 117 | + |
| 118 | + def get_links(self): |
| 119 | + request = self.context.get('request', None) |
| 120 | + view = self.context.get('view', None) |
| 121 | + return_data = OrderedDict() |
| 122 | + self_kwargs = view.kwargs.copy() |
| 123 | + self_kwargs.update({'related_field': self.field_name if self.field_name else self.parent.field_name}) |
| 124 | + self_link = self.get_url('self', self.self_link_view_name, self_kwargs, request) |
| 125 | + |
| 126 | + related_kwargs = {self.related_link_url_kwarg: view.kwargs[self.related_link_lookup_field]} |
| 127 | + related_link = self.get_url('related', self.related_link_view_name, related_kwargs, request) |
| 128 | + |
| 129 | + if self_link: |
| 130 | + return_data.update({'self': self_link}) |
| 131 | + if related_link: |
| 132 | + return_data.update({'related': related_link}) |
| 133 | + return return_data |
| 134 | + |
50 | 135 | def to_internal_value(self, data): |
51 | 136 | expected_relation_type = get_resource_type_from_queryset(self.queryset) |
| 137 | + if not isinstance(data, dict): |
| 138 | + self.fail('incorrect_type', data_type=type(data).__name__) |
52 | 139 | if data['type'] != expected_relation_type: |
53 | | - self.fail('incorrect_relation_type', relation_type=expected_relation_type, received_type=data['type']) |
| 140 | + self.conflict('incorrect_relation_type', relation_type=expected_relation_type, received_type=data['type']) |
54 | 141 | return super(ResourceRelatedField, self).to_internal_value(data['id']) |
55 | 142 |
|
56 | 143 | def to_representation(self, value): |
57 | | - return { |
58 | | - 'type': format_relation_name(get_resource_type_from_instance(value)), |
59 | | - 'id': str(value.pk) |
60 | | - } |
| 144 | + if getattr(self, 'pk_field', None) is not None: |
| 145 | + pk = self.pk_field.to_representation(value.pk) |
| 146 | + else: |
| 147 | + pk = value.pk |
| 148 | + |
| 149 | + return OrderedDict([('type', format_relation_name(get_resource_type_from_instance(value))), ('id', str(pk))]) |
61 | 150 |
|
0 commit comments