Skip to content

Commit 384d379

Browse files
authored
API v3: added support for tags in API (#9513)
* added support for tags in API * set tags to be not required by default * removed unrelated fields and updated API docs for tags * pre-commit linting * pre-commit linter * modified tag assretions * added ordering by name to Tag model * modified responses json for sorted tags * trimmed trailing whitespace * used tags from real examples
1 parent 672c0ed commit 384d379

10 files changed

+81
-54
lines changed

docs/user/api/v3.rst

+14-1
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,11 @@ Project create
347347
},
348348
"homepage": "http://template.readthedocs.io/",
349349
"programming_language": "py",
350-
"language": "es"
350+
"language": "es",
351+
"tags": [
352+
"automation",
353+
"sphinx"
354+
]
351355
}
352356

353357
**Example response**:
@@ -413,6 +417,10 @@ Project update
413417
"language": "ja",
414418
"programming_language": "py",
415419
"homepage": "https://readthedocs.org/",
420+
"tags" : [
421+
"extension",
422+
"mkdocs"
423+
]
416424
"default_version": "v0.27.0",
417425
"default_branch": "develop",
418426
"analytics_code": "UA000000",
@@ -422,6 +430,11 @@ Project update
422430

423431
}
424432

433+
.. note::
434+
435+
Adding ``tags`` will replace existing tags with the new list,
436+
and if omitted won't change the tags.
437+
425438
:statuscode 204: Updated successfully
426439

427440

readthedocs/api/v3/serializers.py

+17-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from rest_flex_fields import FlexFieldsModelSerializer
1010
from rest_flex_fields.serializers import FlexFieldsSerializerMixin
1111
from rest_framework import serializers
12+
from taggit.serializers import TaggitSerializer, TagListSerializerField
1213

1314
from readthedocs.builds.models import Build, Version
1415
from readthedocs.core.utils import slugify
@@ -457,21 +458,23 @@ def get_translations(self, obj):
457458
return self._absolute_url(path)
458459

459460

460-
class ProjectCreateSerializerBase(FlexFieldsModelSerializer):
461+
class ProjectCreateSerializerBase(TaggitSerializer, FlexFieldsModelSerializer):
461462

462463
"""Serializer used to Import a Project."""
463464

464465
repository = RepositorySerializer(source='*')
465466
homepage = serializers.URLField(source='project_url', required=False)
467+
tags = TagListSerializerField(required=False)
466468

467469
class Meta:
468470
model = Project
469471
fields = (
470-
'name',
471-
'language',
472-
'programming_language',
473-
'repository',
474-
'homepage',
472+
"name",
473+
"language",
474+
"programming_language",
475+
"repository",
476+
"homepage",
477+
"tags",
475478
)
476479

477480
def _validate_remote_repository(self, data):
@@ -533,7 +536,7 @@ class ProjectCreateSerializer(SettingsOverrideObject):
533536
_default_class = ProjectCreateSerializerBase
534537

535538

536-
class ProjectUpdateSerializerBase(FlexFieldsModelSerializer):
539+
class ProjectUpdateSerializerBase(TaggitSerializer, FlexFieldsModelSerializer):
537540

538541
"""Serializer used to modify a Project once imported."""
539542

@@ -542,17 +545,18 @@ class ProjectUpdateSerializerBase(FlexFieldsModelSerializer):
542545
source='project_url',
543546
required=False,
544547
)
548+
tags = TagListSerializerField(required=False)
545549

546550
class Meta:
547551
model = Project
548552
fields = (
549553
# Settings
550-
'name',
551-
'repository',
552-
'language',
553-
'programming_language',
554-
'homepage',
555-
554+
"name",
555+
"repository",
556+
"language",
557+
"programming_language",
558+
"homepage",
559+
"tags",
556560
# Advanced Settings -> General Settings
557561
'default_version',
558562
'default_branch',

readthedocs/api/v3/tests/responses/projects-detail.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@
8989
"slug": "project",
9090
"subproject_of": null,
9191
"tags": [
92-
"tag",
9392
"project",
93+
"tag",
9494
"test"
9595
],
9696
"translation_of": null,

readthedocs/api/v3/tests/responses/projects-list.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
"versions": "https://readthedocs.org/projects/project/versions/"
3434
},
3535
"tags": [
36-
"tag",
3736
"project",
37+
"tag",
3838
"test"
3939
],
4040
"users": [

readthedocs/api/v3/tests/responses/projects-list_POST.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
},
3131
"slug": "test-project",
3232
"subproject_of": null,
33-
"tags": [],
33+
"tags": ["template tag", "test tag"],
3434
"translation_of": null,
3535
"urls": {
3636
"builds": "https://readthedocs.org/projects/test-project/builds/",

readthedocs/api/v3/tests/responses/projects-superproject.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
"slug": "project",
3232
"subproject_of": null,
3333
"tags": [
34-
"tag",
3534
"project",
35+
"tag",
3636
"test"
3737
],
3838
"translation_of": null,

readthedocs/api/v3/tests/responses/projects-versions-builds-list_POST.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
"slug": "project",
5858
"subproject_of": null,
5959
"tags": [
60-
"tag",
6160
"project",
61+
"tag",
6262
"test"
6363
],
6464
"translation_of": null,

readthedocs/api/v3/tests/responses/remoterepositories-list.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"repository": { "type": "git", "url": "https://github.com/rtfd/project" },
4040
"slug": "project",
4141
"subproject_of": null,
42-
"tags": ["tag", "project", "test"],
42+
"tags": ["project", "tag", "test"],
4343
"translation_of": null,
4444
"urls": {
4545
"builds": "https://readthedocs.org/projects/project/builds/",

readthedocs/api/v3/tests/test_projects.py

+43-33
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import django_dynamic_fixture as fixture
44
from django.urls import reverse
55

6-
from readthedocs.projects.models import Project
76
from readthedocs.oauth.models import RemoteRepository
7+
from readthedocs.projects.models import Project
88

99
from .mixins import APIEndpointMixin
1010

@@ -118,8 +118,9 @@ def test_import_project(self):
118118
'url': 'https://github.com/rtfd/template',
119119
'type': 'git',
120120
},
121-
'homepage': 'http://template.readthedocs.io/',
122-
'programming_language': 'py',
121+
"homepage": "http://template.readthedocs.io/",
122+
"programming_language": "py",
123+
"tags": ["test tag", "template tag"],
123124
}
124125

125126
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
@@ -131,12 +132,13 @@ def test_import_project(self):
131132

132133
project = query.first()
133134
self.assertIsNone(project.remote_repository)
134-
self.assertEqual(project.name, 'Test Project')
135-
self.assertEqual(project.slug, 'test-project')
136-
self.assertEqual(project.repo, 'https://github.com/rtfd/template')
137-
self.assertEqual(project.language, 'en')
138-
self.assertEqual(project.programming_language, 'py')
139-
self.assertEqual(project.project_url, 'http://template.readthedocs.io/')
135+
self.assertEqual(project.name, "Test Project")
136+
self.assertEqual(project.slug, "test-project")
137+
self.assertEqual(project.repo, "https://github.com/rtfd/template")
138+
self.assertEqual(project.language, "en")
139+
self.assertEqual(project.programming_language, "py")
140+
self.assertEqual(project.project_url, "http://template.readthedocs.io/")
141+
self.assertEqual(list(project.tags.names()), ["template tag", "test tag"])
140142
self.assertIn(self.me, project.users.all())
141143
self.assertEqual(project.builds.count(), 1)
142144

@@ -243,15 +245,16 @@ def test_update_project(self):
243245
'url': 'https://bitbucket.com/rtfd/updated-repository',
244246
'type': 'hg',
245247
},
246-
'language': 'es',
247-
'programming_language': 'js',
248-
'homepage': 'https://updated-homepage.org',
249-
'default_version': 'stable',
250-
'default_branch': 'updated-default-branch',
251-
'analytics_code': 'UA-XXXXXX',
252-
'show_version_warning': False,
253-
'single_version': True,
254-
"external_builds_enabled": True
248+
"language": "es",
249+
"programming_language": "js",
250+
"homepage": "https://updated-homepage.org",
251+
"tags": ["updated tag", "test tag"],
252+
"default_version": "stable",
253+
"default_branch": "updated-default-branch",
254+
"analytics_code": "UA-XXXXXX",
255+
"show_version_warning": False,
256+
"single_version": True,
257+
"external_builds_enabled": True,
255258
}
256259

257260
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
@@ -267,16 +270,19 @@ def test_update_project(self):
267270
self.assertEqual(response.status_code, 204)
268271

269272
self.project.refresh_from_db()
270-
self.assertEqual(self.project.name, 'Updated name')
271-
self.assertEqual(self.project.slug, 'project')
272-
self.assertEqual(self.project.repo, 'https://bitbucket.com/rtfd/updated-repository')
273-
self.assertEqual(self.project.repo_type, 'hg')
274-
self.assertEqual(self.project.language, 'es')
275-
self.assertEqual(self.project.programming_language, 'js')
276-
self.assertEqual(self.project.project_url, 'https://updated-homepage.org')
277-
self.assertEqual(self.project.default_version, 'stable')
278-
self.assertEqual(self.project.default_branch, 'updated-default-branch')
279-
self.assertEqual(self.project.analytics_code, 'UA-XXXXXX')
273+
self.assertEqual(self.project.name, "Updated name")
274+
self.assertEqual(self.project.slug, "project")
275+
self.assertEqual(
276+
self.project.repo, "https://bitbucket.com/rtfd/updated-repository"
277+
)
278+
self.assertEqual(self.project.repo_type, "hg")
279+
self.assertEqual(self.project.language, "es")
280+
self.assertEqual(self.project.programming_language, "js")
281+
self.assertEqual(self.project.project_url, "https://updated-homepage.org")
282+
self.assertEqual(list(self.project.tags.names()), ["test tag", "updated tag"])
283+
self.assertEqual(self.project.default_version, "stable")
284+
self.assertEqual(self.project.default_branch, "updated-default-branch")
285+
self.assertEqual(self.project.analytics_code, "UA-XXXXXX")
280286
self.assertEqual(self.project.show_version_warning, False)
281287
self.assertEqual(self.project.single_version, True)
282288
self.assertEqual(self.project.external_builds_enabled, True)
@@ -287,7 +293,8 @@ def test_partial_update_project(self):
287293
'repository': {
288294
'url': 'https://github.com/rtfd/updated-repository',
289295
},
290-
'default_branch': 'updated-default-branch',
296+
"default_branch": "updated-default-branch",
297+
"tags": ["partial tags", "updated"],
291298
}
292299

293300
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
@@ -303,10 +310,13 @@ def test_partial_update_project(self):
303310
self.assertEqual(response.status_code, 204)
304311

305312
self.project.refresh_from_db()
306-
self.assertEqual(self.project.name, 'Updated name')
307-
self.assertEqual(self.project.slug, 'project')
308-
self.assertEqual(self.project.repo, 'https://github.com/rtfd/updated-repository')
309-
self.assertNotEqual(self.project.default_version, 'updated-default-branch')
313+
self.assertEqual(self.project.name, "Updated name")
314+
self.assertEqual(self.project.slug, "project")
315+
self.assertEqual(
316+
self.project.repo, "https://github.com/rtfd/updated-repository"
317+
)
318+
self.assertEqual(list(self.project.tags.names()), ["partial tags", "updated"])
319+
self.assertNotEqual(self.project.default_version, "updated-default-branch")
310320

311321
def test_partial_update_others_project(self):
312322
data = {

readthedocs/projects/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ class Project(models.Model):
442442
help_text=_('This project has been successfully cloned'),
443443
)
444444

445-
tags = TaggableManager(blank=True)
445+
tags = TaggableManager(blank=True, ordering=["name"])
446446
history = ExtraHistoricalRecords()
447447
objects = ProjectQuerySet.as_manager()
448448

0 commit comments

Comments
 (0)