Browse Source

Run testproj in a Heroku demo app (#38)

* Add Heroku configuration
* Add links in API description
* Read database connection string from DATABASE_URL environment variable
* Restructure settings files for production
* Run server using gunicorn and servce static files with whitenoise
* Install drf-yasg from source instead of pypi in testproj
* Add readme links to demo app
pull/42/head
Cristi Vîjdea 1 year ago
parent
commit
c4379dc6a7
No account linked to committer's email address

+ 1
- 0
.gitignore View File

@@ -1,5 +1,6 @@
node_modules/
testproj/db.sqlite3
testproj/staticfiles
.vscode/

# Created by .ignore support plugin (hsz.mobi)

+ 2
- 2
.idea/drf-yasg.iml View File

@@ -4,7 +4,7 @@
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$/testproj" />
<option name="settingsModule" value="testproj/settings.py" />
<option name="settingsModule" value="testproj/settings/local.py" />
<option name="manageScript" value="manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
@@ -32,4 +32,4 @@
<option name="projectConfiguration" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>
</module>

+ 1
- 1
.travis.yml View File

@@ -38,7 +38,7 @@ jobs:
distributions: "sdist bdist_wheel"

allow_failures:
- env: TOXENV=flake8
- env: TOXENV=lint
- env: DRF=master

fast_finish: true

+ 2
- 0
Procfile View File

@@ -0,0 +1,2 @@
release: python testproj/manage.py migrate && python testproj/manage.py shell -c "import createsuperuser"
web: gunicorn --chdir testproj testproj.wsgi --log-file -

+ 5
- 0
README.rst View File

@@ -20,6 +20,11 @@ Resources:
* **Source**: https://github.com/axnsan12/drf-yasg/
* **Documentation**: https://drf-yasg.readthedocs.io/
* **Changelog**: https://drf-yasg.readthedocs.io/en/stable/changelog.html
* **Live demo**: https://drf-yasg-demo.herokuapp.com/

.. image:: https://www.herokucdn.com/deploy/button.svg
:target: https://heroku.com/deploy?template=https://github.com/axnsan12/drf-yasg
:alt: heroku deploy button

********
Features

+ 17
- 0
app.json View File

@@ -0,0 +1,17 @@
{
"name": "drf-yasg Demo app",
"description": "A demonstrative app using https://github.com/axnsan12/drf-yasg",
"repository": "https://github.com/axnsan12/drf-yasg",
"logo": "https://swaggerhub.com/wp-content/uploads/2017/10/Swagger-Icon.svg",
"keywords": [
"django",
"django-rest-framework",
"swagger",
"openapi"
],
"env": {
"DJANGO_SETTINGS_MODULE": "testproj.settings.heroku",
"DJANGO_SECRET_KEY": "m76=^#=z7xv5^(o%4dv9w7+1_c)y2m6)1ogjx%s@9$1^nupry="
},
"success_url": "/"
}

+ 1
- 1
docs/conf.py View File

@@ -205,7 +205,7 @@ sys.path.insert(0, os.path.abspath('../src'))

# activate the Django testproj to be able to succesfully import drf_yasg
sys.path.insert(0, os.path.abspath('../testproj'))
os.putenv('DJANGO_SETTINGS_MODULE', 'testproj.settings')
os.putenv('DJANGO_SETTINGS_MODULE', 'testproj.settings.local')

from django.conf import settings # noqa: E402


+ 1
- 0
requirements.txt View File

@@ -0,0 +1 @@
-r requirements/heroku.txt

+ 3
- 0
requirements/dev.txt View File

@@ -2,3 +2,6 @@
-r tox.txt
-r test.txt
-r lint.txt

tox-battery>=0.5
detox>=0.11

+ 9
- 0
requirements/heroku.txt View File

@@ -0,0 +1,9 @@
# requirements necessary when deploying the test project to heroku
.[validation]
Django>=1.11.7,<2.0; python_version <= "2.7"
Django>=1.11.7; python_version >= "3.4"

-r testproj.txt
psycopg2>=2.7.3
gunicorn>=19.7.1
whitenoise>=3.3.1

+ 2
- 0
requirements/testproj.txt View File

@@ -5,3 +5,5 @@ django-cors-headers>=2.1.0
django-filter>=1.1.0,<2.0; python_version == "2.7"
django-filter>=1.1.0; python_version >= "3.4"
djangorestframework-camel-case>=0.2.0
dj-database-url>=0.4.2
user_agents>=1.1.0

+ 0
- 2
requirements/tox.txt View File

@@ -1,6 +1,4 @@
# requirements for building and running tox
tox>=2.9.1
tox-battery>=0.5
detox>=0.11

-r setup.txt

+ 1
- 0
runtime.txt View File

@@ -0,0 +1 @@
python-3.6.4

+ 24
- 8
setup.py View File

@@ -3,6 +3,8 @@
import distutils.core
import io
import os
import random
import string
import sys
from setuptools import find_packages, setup

@@ -32,14 +34,14 @@ def _install_setup_requires(attrs):
dist.fetch_build_eggs(dist.setup_requires)


if 'sdist' in sys.argv:
try:
# try to install setuptools_scm before setuptools does it, otherwise our monkey patch below will come too early
# (setuptools_scm adds find_files hooks into setuptools on install)
_install_setup_requires({'setup_requires': requirements_setup})
except Exception:
pass
try:
# try to install setuptools_scm before setuptools does it, otherwise our monkey patch below will come too early
# (setuptools_scm adds find_files hooks into setuptools on install)
_install_setup_requires({'setup_requires': requirements_setup})
except Exception:
pass

if 'sdist' in sys.argv:
try:
# see https://github.com/pypa/setuptools_scm/issues/190, setuptools_scm includes ALL versioned files from
# the git repo into the sdist by default, and there is no easy way to provide an opt-out;
@@ -51,9 +53,22 @@ if 'sdist' in sys.argv:
except ImportError:
pass

try:
# this is a workaround for being able to install the package from source without working from a git checkout
# it is needed for building succesfully on Heroku
from setuptools_scm import get_version

version = get_version()
version_kwargs = {'use_scm_version': True}
except LookupError:
if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv:
raise

rnd = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
version_kwargs = {'version': '0.0.0.dummy+' + rnd}

setup(
name='drf-yasg',
use_scm_version=True,
packages=find_packages('src'),
package_dir={'': 'src'},
include_package_data=True,
@@ -89,4 +104,5 @@ setup(
'Topic :: Documentation',
'Topic :: Software Development :: Code Generators',
],
**version_kwargs
)

+ 4
- 2
src/drf_yasg/inspectors/field.py View File

@@ -462,8 +462,10 @@ else:
"""Converts property names to camelCase if ``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer`` are used."""

def is_camel_case(self):
return any(issubclass(parser, CamelCaseJSONParser) for parser in self.view.parser_classes) \
or any(issubclass(renderer, CamelCaseJSONRenderer) for renderer in self.view.renderer_classes)
return (
any(issubclass(parser, CamelCaseJSONParser) for parser in self.view.parser_classes) or
any(issubclass(renderer, CamelCaseJSONRenderer) for renderer in self.view.renderer_classes)
)

def process_result(self, result, method_name, obj, **kwargs):
if isinstance(result, openapi.Schema.OR_REF) and self.is_camel_case():

+ 7
- 3
testproj/createsuperuser.py View File

@@ -1,11 +1,15 @@
from __future__ import print_function

from django.contrib.auth.models import User
from django.db.utils import IntegrityError

username = 'admin'
email = 'admin@admin.admin'
password = 'passwordadmin'
User.objects.filter(username=username).delete()
User.objects.create_superuser(username, email, password)

print("Created superuser '%s <%s>' with password '%s'" % (username, email, password))
try:
User.objects.create_superuser(username, email, password)
except IntegrityError:
print("User '%s <%s>' already exists" % (username, email))
else:
print("Created superuser '%s <%s>' with password '%s'" % (username, email, password))

+ 1
- 1
testproj/manage.py View File

@@ -3,7 +3,7 @@ import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings.local")
try:
from django.core.management import execute_from_command_line
except ImportError:

+ 3
- 2
testproj/requirements.txt View File

@@ -1,3 +1,4 @@
drf-yasg[validation]
Django>=1.11.7
..[validation]
Django>=1.11.7,<2.0; python_version <= "2.7"
Django>=1.11.7; python_version >= "3.4"
-r ../requirements/testproj.txt

+ 0
- 0
testproj/testproj/settings/__init__.py View File


testproj/testproj/settings.py → testproj/testproj/settings/base.py View File

@@ -1,16 +1,7 @@
import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '!z1yj(9uz)zk0gg@5--j)bc4h^i!8))r^dezco8glf190e0&#p'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

ALLOWED_HOSTS = [
'127.0.0.1',
@@ -69,16 +60,6 @@ TEMPLATES = [

WSGI_APPLICATION = 'testproj.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

@@ -97,16 +78,19 @@ AUTH_PASSWORD_VALIDATORS = [
},
]

# Django Rest Framework

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}

# drf-yasg

SWAGGER_SETTINGS = {
'LOGIN_URL': '/admin/login',
'LOGOUT_URL': '/admin/logout',
'VALIDATOR_URL': 'http://localhost:8189',

'DEFAULT_INFO': 'testproj.urls.swagger_info'
}
@@ -128,9 +112,14 @@ USE_TZ = True
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# Testing

TEST_RUNNER = 'testproj.runner.PytestTestRunner'

# Logging configuration

LOGGING = {
'version': 1,
'disable_existing_loggers': True,

+ 35
- 0
testproj/testproj/settings/heroku.py View File

@@ -0,0 +1,35 @@
import dj_database_url

from .base import * # noqa: F403

DEBUG = True

ALLOWED_HOSTS.append('.herokuapp.com')

SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
assert SECRET_KEY, 'DJANGO_SECRET_KEY environment variable must be set'

SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'

# Simplified static file serving.
# https://warehouse.python.org/project/whitenoise/

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
MIDDLEWARE.insert(0, 'whitenoise.middleware.WhiteNoiseMiddleware')

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
'default': dj_database_url.config(conn_max_age=600)
}

SILENCED_SYSTEM_CHECKS = [
'security.W004', # SECURE_HSTS_SECONDS
'security.W008', # SECURE_SSL_REDIRECT
]

+ 24
- 0
testproj/testproj/settings/local.py View File

@@ -0,0 +1,24 @@
import os

import dj_database_url

from .base import * # noqa: F403

SWAGGER_SETTINGS.update({'VALIDATOR_URL': 'http://localhost:8189'})

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

db_path = os.path.join(BASE_DIR, 'db.sqlite3')
DATABASES = {
'default': dj_database_url.parse('sqlite:///' + db_path)
}

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '!z1yj(9uz)zk0gg@5--j)bc4h^i!8))r^dezco8glf190e0&#p'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

+ 23
- 1
testproj/testproj/urls.py View File

@@ -1,5 +1,7 @@
import user_agents
from django.conf.urls import include, url
from django.contrib import admin
from django.shortcuts import redirect
from rest_framework import permissions
from rest_framework.decorators import api_view

@@ -9,7 +11,13 @@ from drf_yasg.views import get_schema_view
swagger_info = openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
description="""This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg) Django Rest Framework library.

The `swagger-ui` view can be found [here](/cached/swagger).
The `ReDoc` view can be found [here](/cached/redoc).
The swagger YAML document can be found [here](/cached/swagger.yaml).

You can log in using the pre-existing `admin` user with password `passwordadmin`.""", # noqa
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@snippets.local"),
license=openapi.License(name="BSD License"),
@@ -27,6 +35,18 @@ def plain_view(request):
pass


def root_redirect(request):
user_agent_string = request.META.get('HTTP_USER_AGENT', '')
user_agent = user_agents.parse(user_agent_string)

if user_agent.is_mobile:
schema_view = 'cschema-redoc'
else:
schema_view = 'cschema-swagger-ui'

return redirect(schema_view, permanent=True)


urlpatterns = [
url(r'^swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
@@ -35,6 +55,8 @@ urlpatterns = [
url(r'^cached/swagger/$', SchemaView.with_ui('swagger', cache_timeout=None), name='cschema-swagger-ui'),
url(r'^cached/redoc/$', SchemaView.with_ui('redoc', cache_timeout=None), name='cschema-redoc'),

url(r'^$', root_redirect),

url(r'^admin/', admin.site.urls),
url(r'^snippets/', include('snippets.urls')),
url(r'^articles/', include('articles.urls')),

+ 1
- 1
testproj/testproj/wsgi.py View File

@@ -2,6 +2,6 @@ import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings.local")

application = get_wsgi_application()

+ 5
- 1
tests/reference.yaml View File

@@ -1,7 +1,11 @@
swagger: '2.0'
info:
title: Snippets API
description: Test description
description: "This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg)\
\ Django Rest Framework library.\n\nThe `swagger-ui` view can be found [here](/cached/swagger).\
\ \nThe `ReDoc` view can be found [here](/cached/redoc). \nThe swagger YAML\
\ document can be found [here](/cached/swagger.yaml). \n\nYou can log in using\
\ the pre-existing `admin` user with password `passwordadmin`."
termsOfService: https://www.google.com/policies/terms/
contact:
email: contact@snippets.local

+ 6
- 4
tox.ini View File

@@ -46,15 +46,16 @@ deps =
-rrequirements/docs.txt
commands =
python setup.py check --restructuredtext --metadata --strict
sphinx-build -WnEa -b html docs docs\_build\html
sphinx-build -WnEa -b html docs docs/_build/html

[pytest]
DJANGO_SETTINGS_MODULE = testproj.settings
DJANGO_SETTINGS_MODULE = testproj.settings.local
python_paths = testproj

[flake8]
max-line-length = 120
exclude = **/migrations/*
ignore = F405

[isort]
skip = .eggs,.tox,docs,env,venv
@@ -68,6 +69,7 @@ known_standard_library =
collections,copy,distutils,functools,inspect,io,json,logging,operator,os,pkg_resources,re,setuptools,sys,
types,warnings
known_third_party =
coreapi,coreschema,datadiff,django,django_filters,djangorestframework_camel_case,flex,inflection,pygments,
pytest,rest_framework,ruamel,setuptools_scm,swagger_spec_validator,uritemplate
coreapi,coreschema,datadiff,dj_database_url,django,django_filters,djangorestframework_camel_case,flex,gunicorn,
inflection,pygments,pytest,rest_framework,ruamel,setuptools_scm,swagger_spec_validator,uritemplate,user_agents,
whitenoise
known_first_party = drf_yasg,testproj,articles,snippets,users,urlconfs

Loading…
Cancel
Save