Skip to content

WIP: workaround "'PKOnlyObject' object has no attribute" error for toOne related #492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e858bb4
workaround "'PKOnlyObject' object has no attribute" error
n2ygk Oct 11, 2018
0880575
add new Course and Term models to reproduce "'PKOnlyObject' object ha…
n2ygk Oct 13, 2018
a948813
use RelatedMixin.override_pk_only_optimization=True to prevent Attrib…
n2ygk Oct 13, 2018
8c78bc7
default override_pk_only_optimization True
n2ygk Oct 13, 2018
d645f81
tox, flake8, isort, make test exception a passing test
n2ygk Oct 13, 2018
5dc8e45
Merge remote-tracking branch 'upstream/master' into related_disable_p…
n2ygk Oct 13, 2018
f02c7c4
Merge remote-tracking branch 'upstream/master' into related_disable_p…
n2ygk Oct 15, 2018
d0debda
add drf39 and drfmaster tests (#498)
n2ygk Oct 23, 2018
00fb5dc
Adjust to new flake8 3.6.0 version (#501)
sliverc Oct 24, 2018
b3eed32
Avoid patch on `RelationshipView` deleting relationship instance when…
Alig1493 Oct 25, 2018
860a096
Use pytest instead of py.test (#502)
sliverc Oct 25, 2018
a4d063f
Use pyupio to manage development dependencies (#503)
sliverc Oct 25, 2018
56b5fd7
Initial Update (#506)
pyup-bot Oct 26, 2018
1ce84f8
Adjust depreaction warning to state to move to JSON_API_FORMAT_FIELD_…
sliverc Oct 26, 2018
7d14537
Update pytest from 3.9.2 to 3.9.3 (#508)
pyup-bot Oct 29, 2018
274cb79
Update sphinx from 1.8.1 to 1.8.2 (#511)
pyup-bot Nov 11, 2018
0f09a6e
Update pytest-factoryboy from 2.0.1 to 2.0.2 (#510)
pyup-bot Nov 11, 2018
7cdcff7
Update pytest from 3.9.3 to 3.10.1 (#512)
pyup-bot Nov 12, 2018
7ec8e42
Test support of DRF HyperlinkedIdentityField (#497)
Alig1493 Nov 12, 2018
4d7a655
workaround "'PKOnlyObject' object has no attribute" error
n2ygk Oct 11, 2018
a3ac0d2
add new Course and Term models to reproduce "'PKOnlyObject' object ha…
n2ygk Oct 13, 2018
509edfa
use RelatedMixin.override_pk_only_optimization=True to prevent Attrib…
n2ygk Oct 13, 2018
875b56b
default override_pk_only_optimization True
n2ygk Oct 13, 2018
2262aa6
tox, flake8, isort, make test exception a passing test
n2ygk Oct 13, 2018
dc5d860
Merge branch 'related_disable_pk_only' of github.com:n2ygk/django-res…
n2ygk Nov 12, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
424 changes: 424 additions & 0 deletions example/fixtures/courseterm.json

Large diffs are not rendered by default.

422 changes: 422 additions & 0 deletions example/fixtures/drf_example.json

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions example/migrations/0006_course_term.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Generated by Django 2.1 on 2018-10-13 09:33

from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('example', '0005_auto_20180922_1508'),
]

operations = [
migrations.CreateModel(
name='Course',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('effective_start_date', models.DateField(blank=True, default=None, null=True)),
('effective_end_date', models.DateField(blank=True, default=None, null=True)),
('last_mod_user_name', models.CharField(max_length=80)),
('last_mod_date', models.DateField(auto_now=True)),
('school_bulletin_prefix_code', models.CharField(max_length=10)),
('suffix_two', models.CharField(max_length=2)),
('subject_area_code', models.CharField(max_length=10)),
('course_number', models.CharField(max_length=10)),
('course_identifier', models.CharField(max_length=10, unique=True)),
('course_name', models.CharField(max_length=80)),
('course_description', models.TextField()),
],
options={
'ordering': ['course_number'],
},
),
migrations.CreateModel(
name='Term',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('effective_start_date', models.DateField(blank=True, default=None, null=True)),
('effective_end_date', models.DateField(blank=True, default=None, null=True)),
('last_mod_user_name', models.CharField(max_length=80)),
('last_mod_date', models.DateField(auto_now=True)),
('term_identifier', models.TextField(max_length=10)),
('audit_permitted_code', models.PositiveIntegerField(blank=True, default=0)),
('exam_credit_flag', models.BooleanField(default=True)),
('course', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='course', to='example.Course')),
],
options={
'ordering': ['term_identifier'],
},
),
]
55 changes: 55 additions & 0 deletions example/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- encoding: utf-8 -*-
from __future__ import unicode_literals

import uuid

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
Expand Down Expand Up @@ -152,3 +154,56 @@ class Company(models.Model):

def __str__(self):
return self.name


# the following serializers are to reproduce/confirm fix for this bug:
# https://github.com/django-json-api/django-rest-framework-json-api/issues/489
class CommonModel(models.Model):
"""
Abstract model with common fields for all "real" Models:
- id: globally unique UUID version 4
- effective dates
- last modified dates
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
effective_start_date = models.DateField(default=None, blank=True, null=True)
effective_end_date = models.DateField(default=None, blank=True, null=True)
last_mod_user_name = models.CharField(max_length=80)
last_mod_date = models.DateField(auto_now=True)

class Meta:
abstract = True


class Course(CommonModel):
"""
A course of instruction. e.g. COMSW1002 Computing in Context
"""
school_bulletin_prefix_code = models.CharField(max_length=10)
suffix_two = models.CharField(max_length=2)
subject_area_code = models.CharField(max_length=10)
course_number = models.CharField(max_length=10)
course_identifier = models.CharField(max_length=10, unique=True)
course_name = models.CharField(max_length=80)
course_description = models.TextField()

class Meta:
# verbose_name = "Course"
# verbose_name_plural = "Courses"
ordering = ["course_number"]


class Term(CommonModel):
"""
A specific course term (year+semester) instance.
e.g. 20183COMSW1002
"""
term_identifier = models.TextField(max_length=10)
audit_permitted_code = models.PositiveIntegerField(blank=True, default=0)
exam_credit_flag = models.BooleanField(default=True)
course = models.ForeignKey('example.Course', related_name='terms',
on_delete=models.CASCADE, null=True,
default=None)

class Meta:
ordering = ["term_identifier"]
90 changes: 89 additions & 1 deletion example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
Blog,
Comment,
Company,
Course,
Entry,
Project,
ProjectType,
ResearchProject,
TaggedItem
TaggedItem,
Term
)


Expand Down Expand Up @@ -265,3 +267,89 @@ class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = '__all__'


# the following serializers are to reproduce/confirm fix for this bug:
# https://github.com/django-json-api/django-rest-framework-json-api/issues/489
class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
"""
.models.CommonModel.last_mod_user_name/date should come from auth.user on a POST/PATCH
"""
def _last_mod(self, validated_data):
"""
override any last_mod_user_name or date with current auth user and current date.
"""
validated_data['last_mod_user_name'] = self.context['request'].user
validated_data['last_mod_date'] = datetime.now().date()

def create(self, validated_data):
"""
extended ModelSerializer to set last_mod_user/date
"""
self._last_mod(validated_data)
return super(HyperlinkedModelSerializer, self).create(validated_data)

def update(self, validated_data):
"""
extended ModelSerializer to set last_mod_user/date
"""
self._last_mod(validated_data)
return super(HyperlinkedModelSerializer, self).update(validated_data)


class CourseSerializer(HyperlinkedModelSerializer):
"""
(de-)serialize the Course.
"""
terms = relations.ResourceRelatedField(
model=Term,
many=True,
read_only=False,
allow_null=True,
required=False,
queryset=Term.objects.all(),
self_link_view_name='course-relationships',
related_link_view_name='course-related',
)

# 'included' support (also used for `related_serializers` for DJA 2.6.0)
included_serializers = {
'terms': 'example.serializers.TermSerializer',
}

class Meta:
model = Course
fields = (
'url',
'school_bulletin_prefix_code', 'suffix_two', 'subject_area_code',
'course_number', 'course_identifier', 'course_name', 'course_description',
'effective_start_date', 'effective_end_date',
'last_mod_user_name', 'last_mod_date',
'terms')


class TermSerializer(HyperlinkedModelSerializer):
course = relations.ResourceRelatedField(
model=Course,
many=False, # this breaks new 2.6.0 related support. Only works when True.
read_only=False,
allow_null=True,
required=False,
queryset=Course.objects.all(),
self_link_view_name='term-relationships',
related_link_view_name='term-related',
)

included_serializers = {
'course': 'example.serializers.CourseSerializer',
}

class Meta:
model = Term
fields = (
'url',
'term_identifier', 'audit_permitted_code',
'exam_credit_flag',
'effective_start_date', 'effective_end_date',
'last_mod_user_name', 'last_mod_date',
'course')
36 changes: 35 additions & 1 deletion example/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@
from . import TestBase
from .. import views
from example.factories import AuthorFactory, EntryFactory
from example.models import Author, Blog, Comment, Entry
from example.models import Author, Blog, Comment, Course, Entry, Term
from example.serializers import AuthorBioSerializer, AuthorTypeSerializer, EntrySerializer
from example.views import AuthorViewSet

try:
from unittest import mock
except ImportError:
import mock


class TestRelationshipView(APITestCase):
def setUp(self):
Expand Down Expand Up @@ -231,9 +236,12 @@ def test_delete_to_many_relationship_with_change(self):


class TestRelatedMixin(APITestCase):
fixtures = ('courseterm',)

def setUp(self):
self.author = AuthorFactory()
self.course = Course.objects.all()
self.term = Term.objects.all()

def _get_view(self, kwargs):
factory = APIRequestFactory()
Expand Down Expand Up @@ -319,6 +327,32 @@ def test_retrieve_related_None(self):
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.json(), {'data': None})

# the following test reproduces/confirm fix for this bug:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sliverc here's the test case that reproduces/fixes the bug. I made two new models and put sample data in drf_example as well so you can play around with this in dev as well as reproduce with test code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK good. I will have a look but I am not sure when I will get to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank-you. Some of these blames may help understand it:

(env) django-rest-framework-json-api$ git blame -L136,220 rest_framework_json_api/serializers.py 
d62b455f (Jonathan Senecal          2015-09-28 22:47:55 -0400 136) class ModelSerializer(IncludedResourcesValidationMixin, SparseFieldsetsMixin, ModelSerializer):
. . .
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 194)     def _get_field_representation(self, field, instance):
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 195)         request = self.context.get('request')
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 196)         is_included = field.source in get_included_resources(request)
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 197)         if not is_included and \
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 198)                 isinstance(field, ModelSerializer) and \
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 199)                 hasattr(instance, field.source + '_id'):
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 200)             attribute = getattr(instance, field.source + '_id')
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 201) 
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 202)             if attribute is None:
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 203)                 return None
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 204) 
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 205)             resource_type = get_resource_type_from_serializer(field)
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 206)             if resource_type:
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 207)                 return OrderedDict([('type', resource_type), ('id', attribute)])
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 208) 
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 209)         attribute = field.get_attribute(instance)
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 210) 
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 211)         # We skip `to_representation` for `None` values so that fields do
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 212)         # not have to explicitly deal with that case.
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 213)         #
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 214)         # For related fields with `use_pk_only_optimization` we need to
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 215)         # resolve the pk value.
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 216)         check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 217)         if check_for_none is None:
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 218)             return None
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 219)         else:
22c4587c (Tim Selman                2018-09-04 09:55:17 +0200 220)             return field.to_representation(attribute)
(env) django-rest-framework-json-api$ git blame -L169,199 rest_framework_json_api/relations.py
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 169) class ResourceRelatedField(HyperlinkedMixin, PrimaryKeyRelatedField):
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 170)     _skip_polymorphic_optimization = True
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 171)     self_link_view_name = None
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 172)     related_link_view_name = None
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 173)     related_link_lookup_field = 'pk'
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 174) 
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 175)     default_error_messages = {
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 176)         'required': _('This field is required.'),
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 177)         'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'),
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 178)         'incorrect_type': _(
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 179)             'Incorrect type. Expected resource identifier object, received {data_type}.'
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 180)         ),
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 181)         'incorrect_relation_type': _(
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 182)             'Incorrect relation type. Expected {relation_type}, received {received_type}.'
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 183)         ),
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 184)         'missing_type': _('Invalid resource identifier object: missing \'type\' attribute'),
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 185)         'missing_id': _('Invalid resource identifier object: missing \'id\' attribute'),
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 186)         'no_match': _('Invalid hyperlink - No URL match.'),
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 187)     }
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 188) 
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 189)     def __init__(self, **kwargs):
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 190)         # check for a model class that was passed in for the relation type
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 191)         model = kwargs.pop('model', None)
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 192)         if model:
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 193)             self.model = model
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 194) 
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 195)         super(ResourceRelatedField, self).__init__(**kwargs)
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 196) 
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 197)     def use_pk_only_optimization(self):
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 198)         # We need the real object to determine its type...
cb7f830a (Anton Shutik 2018-07-19 16:06:57 +0300 199)         return self.get_resource_type_from_included_serializer() is not None
(env) django-rest-framework-json-api$ git blame -L308,327 rest_framework_json_api/relations.py
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 308) class PolymorphicResourceRelatedField(ResourceRelatedField):
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 309)     """
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 310)     Inform DRF that the relation must be considered polymorphic.
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 311)     Takes a `polymorphic_serializer` as the first positional argument to
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 312)     retrieve then validate the accepted types set.
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 313)     """
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 314) 
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 315)     _skip_polymorphic_optimization = False
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 316)     default_error_messages = dict(ResourceRelatedField.default_error_messages, **{
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 317)         'incorrect_relation_type': _('Incorrect relation type. Expected one of [{relation_type}], '
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 318)                                      'received {received_type}.'),
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 319)     })
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 320) 
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 321)     def __init__(self, polymorphic_serializer, *args, **kwargs):
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 322)         self.polymorphic_serializer = polymorphic_serializer
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 323)         super(PolymorphicResourceRelatedField, self).__init__(*args, **kwargs)
61406625 (leo-naeka   2017-06-05 22:23:17 +0200 324) 
b4dffb82 (santiavenda 2018-01-08 18:02:47 -0800 325)     def use_pk_only_optimization(self):
b4dffb82 (santiavenda 2018-01-08 18:02:47 -0800 326)         return False
b4dffb82 (santiavenda 2018-01-08 18:02:47 -0800 327) 

I suppose just having ResourceRelatedField.use_pk_only_optimization() return False as was done for PolymorphicResourceRelatedField would do the trick, but I don't understand exactly when the pk_only optimization should apply and when not.

Maybe it should be False in ResourceRelatedField and add relations.SkipDataMixin.use_ok_only_optimization() to return True.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried the above and test_performance.PerformanceTestCase fails.

# https://github.com/django-json-api/django-rest-framework-json-api/issues/489
def test_term_related_course(self):
"""
confirm that the related child data reference the parent
"""
term_id = self.term.first().pk
kwargs = {'pk': term_id, 'related_field': 'course'}
url = reverse('term-related', kwargs=kwargs)
with mock.patch('rest_framework_json_api.views.RelatedMixin.override_pk_only_optimization',
True):
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
dja_response = resp.json()
back_reference = dja_response['data']['relationships']['terms']['data']
self.assertIn({"type": "terms", "id": str(term_id)}, back_reference)

# the following raises AttributeError:
with self.assertRaises(AttributeError) as ae:
with mock.patch(
'rest_framework_json_api.views.RelatedMixin.override_pk_only_optimization',
False):
resp = self.client.get(url)
print(ae.exception)
self.assertIn('`PKOnlyObject`', ae.exception.args[0])


class TestValidationErrorResponses(TestBase):
def test_if_returns_error_on_empty_post(self):
Expand Down
21 changes: 20 additions & 1 deletion example/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
CommentRelationshipView,
CommentViewSet,
CompanyViewset,
CourseRelationshipView,
CourseViewSet,
EntryRelationshipView,
EntryViewSet,
NonPaginatedEntryViewSet,
ProjectTypeViewset,
ProjectViewset
ProjectViewset,
TermRelationshipView,
TermViewSet
)

router = routers.DefaultRouter(trailing_slash=False)
Expand All @@ -27,6 +31,8 @@
router.register(r'companies', CompanyViewset)
router.register(r'projects', ProjectViewset)
router.register(r'project-types', ProjectTypeViewset)
router.register(r'courses', CourseViewSet)
router.register(r'terms', TermViewSet)

urlpatterns = [
url(r'^', include(router.urls)),
Expand Down Expand Up @@ -63,6 +69,19 @@
url(r'^authors/(?P<pk>[^/.]+)/relationships/(?P<related_field>\w+)',
AuthorRelationshipView.as_view(),
name='author-relationships'),

url(r'courses/(?P<pk>[^/.]+)/relationships/(?P<related_field>\w+)',
CourseRelationshipView.as_view(),
name='course-relationships'),
url(r'courses/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$',
CourseViewSet.as_view({'get': 'retrieve_related'}),
name='course-related'),
url(r'terms/(?P<pk>[^/.]+)/relationships/(?P<related_field>\w+)',
TermRelationshipView.as_view(),
name='term-relationships'),
url(r'terms/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$',
TermViewSet.as_view({'get': 'retrieve_related'}),
name='term-related'),
]


Expand Down
21 changes: 20 additions & 1 deletion example/urls_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
CommentRelationshipView,
CommentViewSet,
CompanyViewset,
CourseRelationshipView,
CourseViewSet,
EntryRelationshipView,
EntryViewSet,
FiltersetEntryViewSet,
NoFiltersetEntryViewSet,
NonPaginatedEntryViewSet,
ProjectTypeViewset,
ProjectViewset
ProjectViewset,
TermRelationshipView,
TermViewSet
)

router = routers.DefaultRouter(trailing_slash=False)
Expand All @@ -32,6 +36,8 @@
router.register(r'companies', CompanyViewset)
router.register(r'projects', ProjectViewset)
router.register(r'project-types', ProjectTypeViewset)
router.register(r'courses', CourseViewSet)
router.register(r'terms', TermViewSet)

# for the old tests
router.register(r'identities', Identity)
Expand Down Expand Up @@ -79,4 +85,17 @@
url(r'^authors/(?P<pk>[^/.]+)/relationships/(?P<related_field>\w+)',
AuthorRelationshipView.as_view(),
name='author-relationships'),

url(r'courses/(?P<pk>[^/.]+)/relationships/(?P<related_field>\w+)',
CourseRelationshipView.as_view(),
name='course-relationships'),
url(r'courses/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$',
CourseViewSet.as_view({'get': 'retrieve_related'}),
name='course-related'),
url(r'terms/(?P<pk>[^/.]+)/relationships/(?P<related_field>\w+)',
TermRelationshipView.as_view(),
name='term-relationships'),
url(r'terms/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$',
TermViewSet.as_view({'get': 'retrieve_related'}),
name='term-related'),
]
Loading