Skip to content

Schemas should automatically respect API root location #4401

Closed
@nick-allen

Description

@nick-allen

Creating a schema should automatically provide any path prefixes to the registered router urls instead of defaulting to /.

It's possible to manually set this prefix using the DefaultRouter schema_url paramter, but this requires knowing where the router will be wired into Django URLs.

Steps to reproduce

Install django, rest framework, and coreapi. Configure normal Django boilerplate

Create a viewset

# viewsets.py

from rest_framework.viewsets import ModelViewSet
from rest_framework.serializers import ModelSerializer
from django.contrib.auth import get_user_model

User = get_user_model()


class UserSerializer(ModelSerializer):
    class Meta:
        model = User


class UserViewSet(ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserSerializer

Create a router with the schema_title argument to setup automatic CoreAPI renderer and register viewsets.
Include router URLs under a path not at root /

# urls.py
from django.conf.urls import url, include

from rest_framework.routers import DefaultRouter

from .viewsets import UserViewSet

v1_router = DefaultRouter(
    schema_title='Example API v1',
)

v1_router.register('users', UserViewSet)

urlpatterns = [
    url(r'^api/v1/', include(v1_router.urls, namespace='v1')),
]

Get schema document and verify url does not include API path prefix

$ curl http://localhost:8000/api/v1/?format=corejson
{
    "_type": "document",
    "_meta": {
        "title": "Example API v1"
    },
    "users": {
        "create": {
            "_type": "link",
            "url": "/users/",
            "action": "post",
            "encoding": "application/json",
            "fields": [
                {
                    "name": "password",
                    "required": true,
                    "location": "form"
                },
                {
                    "name": "last_login",
                    "location": "form"
                },
                {
                    "name": "is_superuser",
                    "location": "form",
                    "description": "Designates that this user has all permissions without explicitly assigning them."
                },
                {
                    "name": "username",
                    "required": true,
                    "location": "form",
                    "description": "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."
                },
                {
                    "name": "first_name",
                    "location": "form"
                },
                {
                    "name": "last_name",
                    "location": "form"
                },
                {
                    "name": "email",
                    "location": "form"
                },
                {
                    "name": "is_staff",
                    "location": "form",
                    "description": "Designates whether the user can log into this admin site."
                },
                {
                    "name": "is_active",
                    "location": "form",
                    "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
                },
                {
                    "name": "date_joined",
                    "location": "form"
                },
                {
                    "name": "groups",
                    "location": "form",
                    "description": "The groups this user belongs to. A user will get all permissions granted to each of their groups."
                },
                {
                    "name": "user_permissions",
                    "location": "form",
                    "description": "Specific permissions for this user."
                }
            ]
        },
        "destroy": {
            "_type": "link",
            "url": "/users/{pk}/",
            "action": "delete",
            "fields": [
                {
                    "name": "pk",
                    "required": true,
                    "location": "path"
                }
            ]
        },
        "list": {
            "_type": "link",
            "url": "/users/",
            "action": "get",
            "fields": [
                {
                    "name": "page",
                    "location": "query"
                }
            ]
        },
        "partial_update": {
            "_type": "link",
            "url": "/users/{pk}/",
            "action": "patch",
            "encoding": "application/json",
            "fields": [
                {
                    "name": "pk",
                    "required": true,
                    "location": "path"
                },
                {
                    "name": "password",
                    "location": "form"
                },
                {
                    "name": "last_login",
                    "location": "form"
                },
                {
                    "name": "is_superuser",
                    "location": "form",
                    "description": "Designates that this user has all permissions without explicitly assigning them."
                },
                {
                    "name": "username",
                    "location": "form",
                    "description": "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."
                },
                {
                    "name": "first_name",
                    "location": "form"
                },
                {
                    "name": "last_name",
                    "location": "form"
                },
                {
                    "name": "email",
                    "location": "form"
                },
                {
                    "name": "is_staff",
                    "location": "form",
                    "description": "Designates whether the user can log into this admin site."
                },
                {
                    "name": "is_active",
                    "location": "form",
                    "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
                },
                {
                    "name": "date_joined",
                    "location": "form"
                },
                {
                    "name": "groups",
                    "location": "form",
                    "description": "The groups this user belongs to. A user will get all permissions granted to each of their groups."
                },
                {
                    "name": "user_permissions",
                    "location": "form",
                    "description": "Specific permissions for this user."
                }
            ]
        },
        "retrieve": {
            "_type": "link",
            "url": "/users/{pk}/",
            "action": "get",
            "fields": [
                {
                    "name": "pk",
                    "required": true,
                    "location": "path"
                }
            ]
        },
        "update": {
            "_type": "link",
            "url": "/users/{pk}/",
            "action": "put",
            "encoding": "application/json",
            "fields": [
                {
                    "name": "pk",
                    "required": true,
                    "location": "path"
                },
                {
                    "name": "password",
                    "required": true,
                    "location": "form"
                },
                {
                    "name": "last_login",
                    "location": "form"
                },
                {
                    "name": "is_superuser",
                    "location": "form",
                    "description": "Designates that this user has all permissions without explicitly assigning them."
                },
                {
                    "name": "username",
                    "required": true,
                    "location": "form",
                    "description": "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."
                },
                {
                    "name": "first_name",
                    "location": "form"
                },
                {
                    "name": "last_name",
                    "location": "form"
                },
                {
                    "name": "email",
                    "location": "form"
                },
                {
                    "name": "is_staff",
                    "location": "form",
                    "description": "Designates whether the user can log into this admin site."
                },
                {
                    "name": "is_active",
                    "location": "form",
                    "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
                },
                {
                    "name": "date_joined",
                    "location": "form"
                },
                {
                    "name": "groups",
                    "location": "form",
                    "description": "The groups this user belongs to. A user will get all permissions granted to each of their groups."
                },
                {
                    "name": "user_permissions",
                    "location": "form",
                    "description": "Specific permissions for this user."
                }
            ]
        }
    }
}

Expected behavior

Using coreapi cli, use any actions following schema tutorial in docs (http://www.django-rest-framework.org/tutorial/7-schemas-and-client-libraries/)

Get schema document

$ coreapi get http://localhost:8000/api/v1/
<Example API v1 "http://localhost:8000/api/v1/">
    users: {
        create(password, username, [last_login], [is_superuser], [first_name], [last_name], [email], [is_staff], [is_active], [date_joined], [groups], [user_permissions])
        destroy(pk)
        list([page])
        partial_update(pk, [password], [last_login], [is_superuser], [username], [first_name], [last_name], [email], [is_staff], [is_active], [date_joined], [groups], [user_permissions])
        retrieve(pk)
        update(pk, password, username, [last_login], [is_superuser], [first_name], [last_name], [email], [is_staff], [is_active], [date_joined], [groups], [user_permissions])
    }

Run list action

$ coreapi action users list
[
    {
        "id": 1,
        "password": "!YOnSaMeJECJU0oeULffq6tp6TB3WAlTLTngqXBpT",
        "last_login": null,
        "is_superuser": false,
        "username": "user",
        "first_name": "",
        "last_name": "",
        "email": "",
        "is_staff": false,
        "is_active": true,
        "date_joined": "2016-08-14T18:10:37.880469Z",
        "groups": [],
        "user_permissions": []
    }
]

Actual behavior

Get schema document

$ coreapi get http://localhost:8000/api/v1/

<Example API v1 "http://localhost:8000/api/v1/">
    users: {
        create(password, username, [last_login], [is_superuser], [first_name], [last_name], [email], [is_staff], [is_active], [date_joined], [groups], [user_permissions])
        destroy(pk)
        list([page])
        partial_update(pk, [password], [last_login], [is_superuser], [username], [first_name], [last_name], [email], [is_staff], [is_active], [date_joined], [groups], [user_permissions])
        retrieve(pk)
        update(pk, password, username, [last_login], [is_superuser], [first_name], [last_name], [email], [is_staff], [is_active], [date_joined], [groups], [user_permissions])
    }

Run list action

$ coreapi action users list
<Error: Not Found>
    message: "
              <!DOCTYPE html>
              <html lang=\"en\">
              <head>
                <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">
                <title>Page not found at /users/</title>
                <meta name=\"robots\" content=\"NONE,NOARCHIVE\">
                <style type=\"text/css\">
                  html * { padding:0; margin:0; }
                  body * { padding:10px 20px; }
                  body * * { padding:0; }
                  body { font:small sans-serif; background:#eee; }
                  body>div { border-bottom:1px solid #ddd; }
                  h1 { font-weight:normal; margin-bottom:.4em; }
                  h1 span { font-size:60%; color:#666; font-weight:normal; }
                  table { border:none; border-collapse: collapse; width:100%; }
                  td, th { vertical-align:top; padding:2px 3px; }
                  th { width:12em; text-align:right; color:#666; padding-right:.5em; }
                  #info { background:#f6f6f6; }
                  #info ol { margin: 0.5em 4em; }
                  #info ol li { font-family: monospace; }
                  #summary { background: #ffc; }
                  #explanation { background:#eee; border-bottom: 0px none; }
                </style>
              </head>
              <body>
                <div id=\"summary\">
                  <h1>Page not found <span>(404)</span></h1>
                  <table class=\"meta\">
                    <tr>
                      <th>Request Method:</th>
                      <td>GET</td>
                    </tr>
                    <tr>
                      <th>Request URL:</th>
                      <td>http://localhost:8000/users/</td>
                    </tr>

                  </table>
                </div>
                <div id=\"info\">

                    <p>
                    Using the URLconf defined in <code>drfbug.urls</code>,
                    Django tried these URL patterns, in this order:
                    </p>
                    <ol>

                        <li>

                              ^api/v1/


                        </li>

                    </ol>
                    <p>The current URL, <code>users/</code>, didn't match any of these.</p>

                </div>

                <div id=\"explanation\">
                  <p>
                    You're seeing this error because you have <code>DEBUG = True</code> in
                    your Django settings file. Change that to <code>False</code>, and Django
                    will display a standard 404 page.
                  </p>
                </div>
              </body>
              </html>
              "

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions