Skip to content

Commit 801f63b

Browse files
authored
Merge pull request #5879 from readthedocs/humitos/api-v3-redirect-crud
APIv3 CRUD for Redirect objects
2 parents b153e28 + ba61d0c commit 801f63b

18 files changed

+399
-30
lines changed

readthedocs/api/v3/mixins.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from django.shortcuts import get_object_or_404
2-
from rest_framework.exceptions import NotFound
32

43
from readthedocs.builds.models import Version
54
from readthedocs.projects.models import Project
@@ -62,7 +61,10 @@ def listing_objects(self, queryset, user):
6261
return queryset.none()
6362

6463
def has_admin_permission(self, user, project):
65-
if project in self.admin_projects(user):
64+
# Use .only for small optimization
65+
admin_projects = self.admin_projects(user).only('id')
66+
67+
if project in admin_projects:
6668
return True
6769

6870
return False

readthedocs/api/v3/permissions.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from rest_framework.permissions import IsAuthenticated
1+
from rest_framework.permissions import IsAuthenticated, BasePermission
22

33

44
class PublicDetailPrivateListing(IsAuthenticated):
@@ -29,3 +29,13 @@ def has_permission(self, request, view):
2929
return True
3030

3131
return False
32+
33+
34+
class IsProjectAdmin(BasePermission):
35+
36+
"""Grant permission if user has admin rights on the Project."""
37+
38+
def has_permission(self, request, view):
39+
project = view._get_parent_project()
40+
if view.has_admin_permission(request.user, project):
41+
return True

readthedocs/api/v3/serializers.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from readthedocs.builds.models import Build, Version
1212
from readthedocs.projects.constants import LANGUAGES, PROGRAMMING_LANGUAGES
1313
from readthedocs.projects.models import Project
14+
from readthedocs.redirects.models import Redirect, TYPE_CHOICES as REDIRECT_TYPE_CHOICES
1415

1516

1617
class UserSerializer(FlexFieldsModelSerializer):
@@ -321,6 +322,7 @@ class ProjectLinksSerializer(BaseLinksSerializer):
321322

322323
versions = serializers.SerializerMethodField()
323324
builds = serializers.SerializerMethodField()
325+
redirects = serializers.SerializerMethodField()
324326
subprojects = serializers.SerializerMethodField()
325327
superproject = serializers.SerializerMethodField()
326328
translations = serializers.SerializerMethodField()
@@ -338,6 +340,15 @@ def get_versions(self, obj):
338340
)
339341
return self._absolute_url(path)
340342

343+
def get_redirects(self, obj):
344+
path = reverse(
345+
'projects-redirects-list',
346+
kwargs={
347+
'parent_lookup_project__slug': obj.slug,
348+
},
349+
)
350+
return self._absolute_url(path)
351+
341352
def get_builds(self, obj):
342353
path = reverse(
343354
'projects-builds-list',
@@ -451,3 +462,70 @@ def get_subproject_of(self, obj):
451462
return self.__class__(obj.superprojects.first().parent).data
452463
except Exception:
453464
return None
465+
466+
467+
class RedirectLinksSerializer(BaseLinksSerializer):
468+
_self = serializers.SerializerMethodField()
469+
project = serializers.SerializerMethodField()
470+
471+
def get__self(self, obj):
472+
path = reverse(
473+
'projects-redirects-detail',
474+
kwargs={
475+
'parent_lookup_project__slug': obj.project.slug,
476+
'redirect_pk': obj.pk,
477+
},
478+
)
479+
return self._absolute_url(path)
480+
481+
def get_project(self, obj):
482+
path = reverse(
483+
'projects-detail',
484+
kwargs={
485+
'project_slug': obj.project.slug,
486+
},
487+
)
488+
return self._absolute_url(path)
489+
490+
491+
class RedirectSerializerBase(serializers.ModelSerializer):
492+
493+
project = serializers.SlugRelatedField(slug_field='slug', read_only=True)
494+
created = serializers.DateTimeField(source='create_dt', read_only=True)
495+
modified = serializers.DateTimeField(source='update_dt', read_only=True)
496+
_links = RedirectLinksSerializer(source='*', read_only=True)
497+
498+
type = serializers.ChoiceField(source='redirect_type', choices=REDIRECT_TYPE_CHOICES)
499+
500+
class Meta:
501+
model = Redirect
502+
fields = [
503+
'pk',
504+
'created',
505+
'modified',
506+
'project',
507+
'type',
508+
'from_url',
509+
'to_url',
510+
'_links',
511+
]
512+
513+
514+
class RedirectCreateSerializer(RedirectSerializerBase):
515+
pass
516+
517+
518+
class RedirectDetailSerializer(RedirectSerializerBase):
519+
520+
"""Override RedirectSerializerBase to sanitize the empty fields."""
521+
522+
from_url = serializers.SerializerMethodField()
523+
to_url = serializers.SerializerMethodField()
524+
525+
def get_from_url(self, obj):
526+
# Overridden only to return ``None`` when the description is ``''``
527+
return obj.from_url or None
528+
529+
def get_to_url(self, obj):
530+
# Overridden only to return ``None`` when the description is ``''``
531+
return obj.to_url or None

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"_links": {
6161
"_self": "https://readthedocs.org/api/v3/projects/project/",
6262
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
63+
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
6364
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
6465
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
6566
"translations": "https://readthedocs.org/api/v3/projects/project/translations/",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"_self": "https://readthedocs.org/api/v3/projects/project/",
5050
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
5151
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
52+
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
5253
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
5354
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
5455
"translations": "https://readthedocs.org/api/v3/projects/project/translations/"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"_links": {
3+
"_self": "https://readthedocs.org/api/v3/projects/project/redirects/1/",
4+
"project": "https://readthedocs.org/api/v3/projects/project/"
5+
},
6+
"created": "2019-04-29T10:00:00Z",
7+
"modified": "2019-04-29T12:00:00Z",
8+
"from_url": "/docs/",
9+
"pk": 1,
10+
"project": "project",
11+
"type": "page",
12+
"to_url": "/documentation/"
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"_links": {
3+
"_self": "https://readthedocs.org/api/v3/projects/project/redirects/1/",
4+
"project": "https://readthedocs.org/api/v3/projects/project/"
5+
},
6+
"created": "2019-04-29T10:00:00Z",
7+
"modified": "2019-04-29T12:00:00Z",
8+
"from_url": "/changed/",
9+
"pk": 1,
10+
"project": "project",
11+
"type": "page",
12+
"to_url": "/toanother/"
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"count": 1,
3+
"next": null,
4+
"previous": null,
5+
"results": [
6+
{
7+
"_links": {
8+
"_self": "https://readthedocs.org/api/v3/projects/project/redirects/1/",
9+
"project": "https://readthedocs.org/api/v3/projects/project/"
10+
},
11+
"created": "2019-04-29T10:00:00Z",
12+
"modified": "2019-04-29T12:00:00Z",
13+
"from_url": "/docs/",
14+
"pk": 1,
15+
"project": "project",
16+
"type": "page",
17+
"to_url": "/documentation/"
18+
}
19+
]
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"_links": {
3+
"_self": "https://readthedocs.org/api/v3/projects/project/redirects/2/",
4+
"project": "https://readthedocs.org/api/v3/projects/project/"
5+
},
6+
"created": "2019-04-29T10:00:00Z",
7+
"modified": "2019-04-29T12:00:00Z",
8+
"from_url": "/page/",
9+
"pk": 2,
10+
"project": "project",
11+
"type": "page",
12+
"to_url": "/another/"
13+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"_self": "https://readthedocs.org/api/v3/projects/project/",
6969
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
7070
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
71+
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
7172
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
7273
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
7374
"translations": "https://readthedocs.org/api/v3/projects/project/translations/"
@@ -90,6 +91,7 @@
9091
"_self": "https://readthedocs.org/api/v3/projects/subproject/",
9192
"versions": "https://readthedocs.org/api/v3/projects/subproject/versions/",
9293
"builds": "https://readthedocs.org/api/v3/projects/subproject/builds/",
94+
"redirects": "https://readthedocs.org/api/v3/projects/subproject/redirects/",
9395
"subprojects": "https://readthedocs.org/api/v3/projects/subproject/subprojects/",
9496
"superproject": "https://readthedocs.org/api/v3/projects/subproject/superproject/",
9597
"translations": "https://readthedocs.org/api/v3/projects/subproject/translations/"

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"_links": {
1212
"_self": "https://readthedocs.org/api/v3/projects/project/",
1313
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
14+
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
1415
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
1516
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
1617
"translations": "https://readthedocs.org/api/v3/projects/project/translations/",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"_links": {
3333
"_self": "https://readthedocs.org/api/v3/projects/project/",
3434
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
35+
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
3536
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
3637
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
3738
"translations": "https://readthedocs.org/api/v3/projects/project/translations/",

0 commit comments

Comments
 (0)