Description
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>
"