Fix schema generation with OneToOneFields (#81)

* Fix: OneToOneRel, used by OneToOneField doesn't have help_text nor primary_key attributes, thus breaking OpenAPISchemaGenerator; use hasattr() as safe-guard.
* Fix: use getattr() with a default value instead of hasattr() + acessing the value
* Add: 'people' app that breaks drf_yasg without previous commits
* Update tests/references.yaml + run isort and flake8
* Fix: set on_delete for Person.identity as Django-2+ requires it
openapi3
ko-pp 2018-03-18 17:30:36 +00:00 committed by Cristi Vîjdea
parent a7fbba4967
commit 309a6eb8cd
13 changed files with 276 additions and 2 deletions

View File

@ -409,9 +409,9 @@ class OpenAPISchemaGenerator(object):
if getattr(view_cls, 'lookup_field', None) == variable and attrs['type'] == openapi.TYPE_STRING: if getattr(view_cls, 'lookup_field', None) == variable and attrs['type'] == openapi.TYPE_STRING:
attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None)) attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None))
if model_field and model_field.help_text: if model_field and getattr(model_field, 'help_text', False):
description = force_text(model_field.help_text) description = force_text(model_field.help_text)
elif model_field and model_field.primary_key: elif model_field and getattr(model_field, 'primary_key', False):
description = get_pk_description(model, model_field) description = get_pk_description(model, model_field)
else: else:
description = None description = None

View File

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class PeopleConfig(AppConfig):
name = 'people'

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2018-03-18 16:22
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Identity',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('firstName', models.CharField(max_length=30, null=True)),
('lastName', models.CharField(max_length=30, null=True)),
],
),
migrations.CreateModel(
name='Person',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('Identity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='person', to='people.Identity')),
],
),
]

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2018-03-18 17:04
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('people', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='person',
name='Identity',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='person', to='people.Identity'),
),
]

View File

@ -0,0 +1,11 @@
from django.db import models
class Identity(models.Model):
firstName = models.CharField(max_length=30, null=True)
lastName = models.CharField(max_length=30, null=True)
class Person(models.Model):
Identity = models.OneToOneField(Identity, related_name='person',
on_delete=models.PROTECT)

View File

@ -0,0 +1,23 @@
from rest_framework import serializers
from .models import Identity, Person
class IdentitySerializer(serializers.ModelSerializer):
class Meta:
model = Identity
fields = '__all__'
class PersonSerializer(serializers.ModelSerializer):
identity = IdentitySerializer(read_only=True)
class Meta:
model = Person
fields = '__all__'
def create(self, validated_data):
identity = Identity(**validated_data['identity'])
identity.save()
validated_data['identity'] = identity
return super().create(validated_data)

View File

@ -0,0 +1,26 @@
from django.conf.urls import url
from .views import IdentityViewSet, PersonViewSet
person_list = PersonViewSet.as_view({
'get': 'list',
'post': 'create'
})
person_detail = PersonViewSet.as_view({
'get': 'retrieve',
'patch': 'partial_update',
'delete': 'destroy'
})
identity_detail = IdentityViewSet.as_view({
'get': 'retrieve',
'patch': 'partial_update',
})
urlpatterns = (
url(r'^$', person_list, name='people-list'),
url(r'^(?P<pk>[0-9]+)$', person_detail, name='person-detail'),
url(r'^(?P<person>[0-9]+)/identity$', identity_detail,
name='person-identity'),
)

View File

@ -0,0 +1,16 @@
from rest_framework import viewsets
from .models import Identity, Person
from .serializers import IdentitySerializer, PersonSerializer
class PersonViewSet(viewsets.ModelViewSet):
model = Person
queryset = Person.objects
serializer_class = PersonSerializer
class IdentityViewSet(viewsets.ModelViewSet):
model = Identity
queryset = Identity.objects
serializer_class = IdentitySerializer

View File

@ -27,6 +27,7 @@ INSTALLED_APPS = [
'users', 'users',
'articles', 'articles',
'todo', 'todo',
'people'
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -63,5 +63,6 @@ urlpatterns = [
url(r'^articles/', include('articles.urls')), url(r'^articles/', include('articles.urls')),
url(r'^users/', include('users.urls')), url(r'^users/', include('users.urls')),
url(r'^todo/', include('todo.urls')), url(r'^todo/', include('todo.urls')),
url(r'^people/', include('people.urls')),
url(r'^plain/', plain_view), url(r'^plain/', plain_view),
] ]

View File

@ -234,6 +234,113 @@ paths:
type: string type: string
format: slug format: slug
pattern: '[a-z0-9]+(?:-[a-z0-9]+)' pattern: '[a-z0-9]+(?:-[a-z0-9]+)'
/people/:
get:
operationId: people_list
description: ''
parameters: []
responses:
'200':
description: ''
schema:
type: array
items:
$ref: '#/definitions/Person'
tags:
- people
post:
operationId: people_create
description: ''
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Person'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/Person'
tags:
- people
parameters: []
/people/{id}:
get:
operationId: people_read
description: ''
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Person'
tags:
- people
patch:
operationId: people_partial_update
description: ''
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Person'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Person'
tags:
- people
delete:
operationId: people_delete
description: ''
parameters: []
responses:
'204':
description: ''
tags:
- people
parameters:
- name: id
in: path
description: A unique integer value identifying this person.
required: true
type: integer
/people/{person}/identity:
get:
operationId: people_identity_read
description: ''
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Identity'
tags:
- people
patch:
operationId: people_identity_partial_update
description: ''
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Identity'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Identity'
tags:
- people
parameters:
- name: person
in: path
required: true
type: string
/plain/: /plain/:
get: get:
operationId: plain_list operationId: plain_list
@ -609,6 +716,37 @@ definitions:
type: string type: string
format: uuid format: uuid
readOnly: true readOnly: true
Identity:
title: Identity
type: object
properties:
id:
title: ID
type: integer
readOnly: true
firstName:
title: FirstName
type: string
maxLength: 30
lastName:
title: LastName
type: string
maxLength: 30
readOnly: true
Person:
required:
- Identity
type: object
properties:
id:
title: ID
type: integer
readOnly: true
identity:
$ref: '#/definitions/Identity'
Identity:
title: Identity
type: integer
Project: Project:
required: required:
- projectName - projectName