Skip to content

Commit 0e97aa4

Browse files
authored
Merge pull request readthedocs#6275 from saadmk11/subproject-bug
API V3 Subproject Creation Bug fix
2 parents c3331bd + 8007a7c commit 0e97aa4

File tree

3 files changed

+122
-8
lines changed

3 files changed

+122
-8
lines changed

readthedocs/api/v3/serializers.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,19 @@ def validate_child(self, value):
569569
user = self.context['request'].user
570570
if user not in value.users.all():
571571
raise serializers.ValidationError(
572-
'You do not have permissions on the child project',
572+
_('You do not have permissions on the child project'),
573+
)
574+
575+
# Check the child project is not a subproject already
576+
if value.superprojects.exists():
577+
raise serializers.ValidationError(
578+
_('Child is already a subproject of another project'),
579+
)
580+
581+
# Check the child project is already a superproject
582+
if value.subprojects.exists():
583+
raise serializers.ValidationError(
584+
_('Child is already a superproject'),
573585
)
574586
return value
575587

@@ -578,7 +590,7 @@ def validate_alias(self, value):
578590
subproject = self.parent_project.subprojects.filter(alias=value)
579591
if subproject.exists():
580592
raise serializers.ValidationError(
581-
'A subproject with this alias already exists',
593+
_('A subproject with this alias already exists'),
582594
)
583595
return value
584596

@@ -587,13 +599,13 @@ def validate(self, data):
587599
# Check the parent and child are not the same project
588600
if data['child'].slug == self.parent_project.slug:
589601
raise serializers.ValidationError(
590-
'Project can not be subproject of itself',
602+
_('Project can not be subproject of itself'),
591603
)
592604

593605
# Check the parent project is not a subproject already
594606
if self.parent_project.superprojects.exists():
595607
raise serializers.ValidationError(
596-
'Subproject nesting is not supported',
608+
_('Subproject nesting is not supported'),
597609
)
598610
return data
599611

readthedocs/api/v3/tests/test_subprojects.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,18 @@ def test_projects_subprojects_list_post_with_others_as_child(self):
8989
self.assertEqual(self.project.subprojects.count(), 1)
9090

9191
def test_projects_subprojects_list_post_with_subproject_of_itself(self):
92-
self.assertEqual(self.project.subprojects.count(), 1)
92+
newproject = self._create_new_project()
93+
self.assertEqual(newproject.subprojects.count(), 0)
9394
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
9495
data = {
95-
'child': self.project.slug,
96+
'child': newproject.slug,
9697
'alias': 'subproject-alias',
9798
}
9899
response = self.client.post(
99100
reverse(
100101
'projects-subprojects-list',
101102
kwargs={
102-
'parent_lookup_parent__slug': self.project.slug,
103+
'parent_lookup_parent__slug': newproject.slug,
103104
},
104105
),
105106
data,
@@ -109,7 +110,57 @@ def test_projects_subprojects_list_post_with_subproject_of_itself(self):
109110
'Project can not be subproject of itself',
110111
response.json()['non_field_errors'],
111112
)
112-
self.assertEqual(self.project.subprojects.count(), 1)
113+
self.assertEqual(newproject.subprojects.count(), 0)
114+
115+
def test_projects_subprojects_list_post_with_child_already_superproject(self):
116+
newproject = self._create_new_project()
117+
self.assertEqual(newproject.subprojects.count(), 0)
118+
self.assertTrue(self.project.subprojects.exists())
119+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
120+
data = {
121+
'child': self.project.slug,
122+
'alias': 'subproject-alias',
123+
}
124+
response = self.client.post(
125+
reverse(
126+
'projects-subprojects-list',
127+
kwargs={
128+
'parent_lookup_parent__slug': newproject.slug,
129+
},
130+
),
131+
data,
132+
)
133+
self.assertEqual(response.status_code, 400)
134+
self.assertIn(
135+
'Child is already a superproject',
136+
response.json()['child'],
137+
)
138+
self.assertEqual(newproject.subprojects.count(), 0)
139+
140+
def test_projects_subprojects_list_post_with_child_already_subproject(self):
141+
newproject = self._create_new_project()
142+
self.assertEqual(newproject.subprojects.count(), 0)
143+
self.assertTrue(self.subproject.superprojects.exists())
144+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
145+
data = {
146+
'child': self.subproject.slug,
147+
'alias': 'subproject-alias',
148+
}
149+
response = self.client.post(
150+
reverse(
151+
'projects-subprojects-list',
152+
kwargs={
153+
'parent_lookup_parent__slug': newproject.slug,
154+
},
155+
),
156+
data,
157+
)
158+
self.assertEqual(response.status_code, 400)
159+
self.assertIn(
160+
'Child is already a subproject of another project',
161+
response.json()['child'],
162+
)
163+
self.assertEqual(newproject.subprojects.count(), 0)
113164

114165
def test_projects_subprojects_list_post_nested_subproject(self):
115166
newproject = self._create_new_project()

readthedocs/rtd_tests/tests/test_subprojects.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,57 @@ def test_excludes_existing_subprojects(self):
150150
[''],
151151
)
152152

153+
def test_subproject_cant_be_subproject(self):
154+
user = fixture.get(User)
155+
project = fixture.get(Project, users=[user])
156+
another_project = fixture.get(Project, users=[user])
157+
subproject = fixture.get(Project, users=[user])
158+
relation = fixture.get(
159+
ProjectRelationship, parent=project, child=subproject,
160+
)
161+
162+
form = ProjectRelationshipForm(
163+
{'child': subproject.pk},
164+
project=project,
165+
user=user,
166+
)
167+
self.assertFalse(form.is_valid())
168+
self.assertRegex(
169+
form.errors['child'][0],
170+
'Select a valid choice',
171+
)
172+
173+
form = ProjectRelationshipForm(
174+
{'child': subproject.pk},
175+
project=another_project,
176+
user=user,
177+
)
178+
self.assertFalse(form.is_valid())
179+
self.assertRegex(
180+
form.errors['child'][0],
181+
'Select a valid choice',
182+
)
183+
184+
def test_superproject_cant_be_subproject(self):
185+
user = fixture.get(User)
186+
project = fixture.get(Project, users=[user])
187+
another_project = fixture.get(Project, users=[user])
188+
subproject = fixture.get(Project, users=[user])
189+
relation = fixture.get(
190+
ProjectRelationship, parent=project, child=subproject,
191+
)
192+
193+
form = ProjectRelationshipForm(
194+
{'child': project.pk},
195+
project=another_project,
196+
user=user,
197+
)
198+
self.assertFalse(form.is_valid())
199+
self.assertRegex(
200+
form.errors['child'][0],
201+
'Select a valid choice',
202+
)
203+
153204
def test_exclude_self_project_as_subproject(self):
154205
user = fixture.get(User)
155206
project = fixture.get(Project, users=[user])

0 commit comments

Comments
 (0)