Verified Commit 29c5f96b authored by Ali Harby's avatar Ali Harby 💬
Browse files

override the api view

parents
# Backup files
*.~
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Virtualenv
py2venv
py3venv
venv/
# Pycharm
.idea/
\ No newline at end of file
language: python
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
env:
- DJANGO=1.11 DRF=3.11
- DJANGO=2.2 DRF=3.11
- DJANGO=3.0 DRF=3.11
matrix:
exclude:
- python: "3.5"
env: DJANGO=3.0 DRF=3.11
- python: "3.7"
env: DJANGO=1.11 DRF=3.11
- python: "3.8"
env: DJANGO=1.11 DRF=3.11
install:
- pip install -q Django==$DJANGO
- pip install -q djangorestframework==$DRF
- pip install pep8
- pip install -q -e .
before_script:
- "pep8 drf_generators --exclude=templates && cd tests"
script:
- python manage.py generate api --format apiview --force
- python manage.py test api
- python manage.py generate api --format function --force
- python manage.py test api
- python manage.py generate api --format viewset --force
- python manage.py test api
- python manage.py generate api --format modelviewset --force
- python manage.py test api
The MIT License (MIT)
Copyright (c) 2015 Tobin Brown
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
include LICENSE
include README.rst
recursive-include tests *.py *.md
==============
DRF Generators
==============
Writing APIs can be boring and repetitive work. Don't write another CRUDdy view in `Django Rest Framework <http://github.com/tomchristie/django-rest-framework>`_. With DRF Generators, one simple command will generate all of your Views, Serializers, and even Urls for your Django Rest Framework application!
For a full step-by-step tutorial, check out my `blog post <http://brobin.me/blog/2015/4/13/how-to-quickly-write-an-api-in-django>`_!
This is **not** intended to give you a production quality API. It was intended to jumpstart your development and save you from writing the same code over and over for each model.
---------------
|python| |pypi| |license| |travis| |django| |drf|
---------------
* `Installation`_
* `Usage`_
* `Serializers`_
* `Views`_
* `Urls`_
* `Tests`_
* `License`_
---------------
============
Installation
============
Install with pip:
.. code-block:: bash
$ pip install drf-generators
or Clone the repo and install manually:
.. code-block:: bash
$ git clone https://github.com/brobin/drf-generators.git
$ cd drf-generators
$ python setup.py install
To use DRF Generators, add it your INSTALLED_APPS.
.. code-block:: python
INSTALLED_APPS = (
...
'rest_framework',
'drf_generators',
...
)
*Note*: In order to use the APIView classes, you must have the rest framework DEFAULT_PAGINATION_CLASS and PAGE_SIZE set.
.. code-block:: python
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 15
}
-----------------
=====
Usage
=====
To use the generators, run the following command, where ``app`` is the application to generate an API for.
.. code-block:: bash
$ python manage.py generate {app} {options}
========================== ===================================================
Option Action
========================== ===================================================
``--serializers`` Generate only Serializers for your app.
``--views`` Generate only Views for your app.
``--urls`` Generate only urls for your app.
``--force`` Overwrite existing files without the warning prompt.
``-f``, ``--format`` Format to use when generating views and urls. Valid options: ``viewset``, ``apiview``, ``function``, ``modelviewset``. Default: ``viewset``.
``-d``, ``--depth`` Serialization depth for related models. Default: 0
========================== ===================================================
**Example:** Generate everything for the app ``api`` with function style views, overwriting existing files, with a serialization depth of 2.
.. code-block:: bash
$ python manage.py generate api --format function --force --depth=2
-------------------
===========
Serializers
===========
Drf Generators will create ``serializers.py`` for your application. It currently uses rest framework's ``ModelSerializer`` for serialization of the models defined in ``models.py``.
.. code-block:: python
class ModelSerializer(serializers.ModelSerializer):
class Meta:
model = User
------------------
=====
Views
=====
DRF Generators will create ``views.py`` for your application. It can generate ``ViewSet``, ``APIView`` and function based views. Set the ``--format`` option when running the generator to pick the preferred style
-------
ViewSet
-------
``python manage.py generate api --format viewset``
.. code-block:: python
class ModelViewSet(ViewSet):
def list(self, request):
...
def create(self, request):
...
def retrieve(self, request, pk=None):
...
def update(self, request, pk=None):
...
def destroy(self, request, pk=None):
...
-------
APIView
-------
``python manage.py generate api --format apiview``
.. code-block:: python
class ModelAPIView(APIView):
def get(self, request, id, format=None):
...
def put(self, request, id, format=None):
...
def delete(self, request, id, format=None):
...
class ModelAPIListView(APIView):
def get(self, request, format=None):
...
def post(self, request, format=None):
...
--------
Function
--------
``python manage.py generate api --format function``
.. code-block:: python
@api_view(['GET', 'POST'])
def model_list(request):
if request.method == 'GET':
...
elif request.method == 'POST':
...
@api_view(['GET', 'PUT', 'DELETE'])
def model_detail(request, pk):
if request.method == 'GET':
...
elif request.method == 'PUT':
...
elif request.method == 'DELETE':
...
-------------
ModelViewSet
-------------
``python manage.py generate api --format modelviewset``
.. code-block:: python
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
-----------------
====
Urls
====
Finally, DRF Generator will create you a default ``urls.py`` to match the View format you are using.
----------------------------
ViewSet & ModeViewSet Routes
----------------------------
.. code-block:: python
router = SimpleRouter()
router.register(r'model', views.ModelViewSet, 'Model')
urlpatterns = router.urls
------------
APIView urls
------------
.. code-block:: python
url(r'^model/([0-9]+)$', views.ModelAPIView.as_view()),
url(r'^model', views.ModelAPIListView.as_view()),
-------------
Function urls
-------------
.. code-block:: python
urlpatterns = [
url(r'^model/(?P<pk>[0-9]+)$', views.model_detail),
url(r'^model/$', views.model_list),
]
urlpatterns = format_suffix_patterns(urlpatterns)
=====
Tests
=====
A full application built with drf-generators can be found in the `tests directory <http://github.com/brobin/drf-generators/tree/master/tests>`_. Instructions on running the tests can be found in the test project's README.
=======
License
=======
MIT License. See `LICENSE <https://github.com/brobin/drf-generators/blob/master/LICENSE>`_.
.. |python| image:: https://img.shields.io/pypi/v/drf-generators.svg?style=flat-square
:target: https://pypi.python.org/pypi/drf-generators/
:alt: Supported Python versions
.. |pypi| image:: https://img.shields.io/pypi/pyversions/drf-generators.svg?style=flat-square
:target: https://pypi.python.org/pypi/drf-generators/
:alt: Latest Version
.. |license| image:: https://img.shields.io/pypi/l/drf-generators.svg?style=flat-square
:target: https://pypi.python.org/pypi/drf-generators/
:alt: License
.. |travis| image:: https://img.shields.io/travis/Brobin/drf-generators.svg?style=flat-square
:target: https://travis-ci.org/Brobin/drf-generators/
:alt: Travis CI
.. |django| image:: https://img.shields.io/badge/Django-1.11, 2.2,3.0-orange.svg?style=flat-square
:target: http://djangoproject.com/
:alt: Django 1.11, 2.2, 3.0
.. |drf| image:: https://img.shields.io/badge/DRF-3.11-orange.svg?style=flat-square
:target: http://www.django-rest-framework.org/
:alt: DRF 3.11
from django.template import Template, Context
import os.path
from drf_generators.templates.serializer import SERIALIZER
from drf_generators.templates.apiview import API_URL, API_VIEW
from drf_generators.templates.viewset import VIEW_SET_URL, VIEW_SET_VIEW
from drf_generators.templates.function import FUNCTION_URL, FUNCTION_VIEW
from drf_generators.templates.modelviewset import MODEL_URL, MODEL_VIEW
__all__ = ['BaseGenerator', 'APIViewGenerator', 'ViewSetGenerator',
'FunctionViewGenerator', 'ModelViewSetGenerator']
class BaseGenerator(object):
def __init__(self, app_config, force):
self.app_config = app_config
self.force = force
self.app = app_config.models_module
self.name = app_config.name
self.serializer_template = Template(SERIALIZER)
self.models = self.get_model_names()
self.serializers = self.get_serializer_names()
def generate_serializers(self, depth):
content = self.serializer_content(depth)
filename = 'serializers.py'
if self.write_file(content, filename):
return ' - writing %s' % filename
else:
return 'Serializer generation cancelled'
def generate_views(self):
content = self.view_content()
filename = 'views.py'
if self.write_file(content, filename):
return ' - writing %s' % filename
else:
return 'View generation cancelled'
def generate_urls(self):
content = self.url_content()
filename = 'urls.py'
if self.write_file(content, filename):
return ' - writing %s' % filename
else:
return 'Url generation cancelled'
def serializer_content(self, depth):
context = Context({'app': self.name, 'models': self.models,
'depth': depth})
return self.serializer_template.render(context)
def view_content(self):
context = Context({'app': self.name, 'models': self.models,
'serializers': self.serializers})
return self.view_template.render(context)
def url_content(self):
context = Context({'app': self.name, 'models': self.models})
return self.url_template.render(context)
def get_model_names(self):
return [m.__name__ for m in self.app_config.get_models()]
def get_serializer_names(self):
return [m + 'Serializer' for m in self.models]
def write_file(self, content, filename):
name = os.path.join(os.path.dirname(self.app.__file__), filename)
if os.path.exists(name) and not self.force:
msg = "Are you sure you want to overwrite %s? (y/n): " % filename
prompt = input # python3
response = prompt(msg)
if response != "y":
return False
new_file = open(name, 'w+')
new_file.write(content)
new_file.close()
return True
class APIViewGenerator(BaseGenerator):
def __init__(self, app_config, force):
super(APIViewGenerator, self).__init__(app_config, force)
self.view_template = Template(API_VIEW)
self.url_template = Template(API_URL)
class ViewSetGenerator(BaseGenerator):
def __init__(self, app_config, force):
super(ViewSetGenerator, self).__init__(app_config, force)
self.view_template = Template(VIEW_SET_VIEW)
self.url_template = Template(VIEW_SET_URL)
class FunctionViewGenerator(BaseGenerator):
def __init__(self, app_config, force):
super(FunctionViewGenerator, self).__init__(app_config, force)
self.view_template = Template(FUNCTION_VIEW)
self.url_template = Template(FUNCTION_URL)
class ModelViewSetGenerator(BaseGenerator):
def __init__(self, app_config, force):
super(ModelViewSetGenerator, self).__init__(app_config, force)
self.view_template = Template(MODEL_VIEW)
self.url_template = Template(MODEL_URL)
import sys
from django.core.management.base import AppCommand, CommandError
from drf_generators.generators import *
import django
class Command(AppCommand):
help = 'Generates DRF API Views and Serializers for a Django app'
args = "[appname ...]"
def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument('-f', '--format', dest='format',
default='viewset',
help='view format (default: viewset)'),
parser.add_argument('-d', '--depth', dest='depth', default=0,
help='serialization depth'),
parser.add_argument('--force', dest='force', action='store_true',
help='force overwrite files'),
parser.add_argument('--serializers', dest='serializers',
action='store_true',
help='generate serializers only'),
parser.add_argument('--views', dest='views', action='store_true',
help='generate views only'),
parser.add_argument('--urls', dest='urls', action='store_true',
help='generate urls only'),
parser.add_argument('--verbose', dest='verbose', action='store_true',
help='Print out logs of file generation'),
def handle_app_config(self, app_config, **options):
if app_config.models_module is None:
raise CommandError('You must provide an app to generate an API')
if sys.version_info[0] != 3 or sys.version_info[1] < 5:
raise CommandError('Python 3.5 or newer is required')
if django.VERSION[1] >= 11 or django.VERSION[0] in [2, 3]:
force = options['force']
fmt = options['format']
depth = options['depth']
serializers = options['serializers']
views = options['views']
urls = options['urls']
else:
raise CommandError('You must be using Django 1.11, 2.2, or 3.0')
if fmt == 'viewset':
generator = ViewSetGenerator(app_config, force)
elif fmt == 'apiview':
generator = APIViewGenerator(app_config, force)
elif fmt == 'function':
generator = FunctionViewGenerator(app_config, force)
elif fmt == 'modelviewset':
generator = ModelViewSetGenerator(app_config, force)
else:
message = '\'%s\' is not a valid format. ' % options['format']
message += '(viewset, modelviewset, apiview, function)'
raise CommandError(message)
if serializers:
result = generator.generate_serializers(depth)
elif views:
result = generator.generate_views()
elif urls:
result = generator.generate_urls()
else:
result = generator.generate_serializers(depth) + '\n'
result += generator.generate_views() + '\n'
result += generator.generate_urls()
if options['verbose']:
print(result)
__all__ = ['API_VIEW', 'API_URL']
API_URL = """from django.conf.urls import include, url
from {{ app }} import views
urlpatterns = [
{% for model in models %}
url(r'^{{ model|lower }}/(?P<id>[0-9]+)/$', views.{{ model }}APIView.as_view()),
url(r'^{{ model|lower }}/$', views.{{ model }}APIListView.as_view()),
{% endfor %}
]
"""
API_VIEW = """from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import generics
from {{ app }}.serializers import {{ serializers|join:', ' }}
from {{ app }}.models import {{ models|join:', ' }}
{% for model in models %}
class {{ model }}APIView(generics.RetrieveUpdateDestroyAPIView):
queryset = {{ model }}.objects.all()
serializer_class = {{ model }}Serializer
def get(self, request, id, format=None):
try:
item = {{ model }}.objects.get(pk=id)
serializer = {{ model }}Serializer(item)
return Response(serializer.data)
except {{ model }}.DoesNotExist:
return Response(status=404)
def put(self, request, id, format=None):
try:
item = {{ model }}.objects.get(pk=id)
except {{ model }}.DoesNotExist:
return Response(status=404)
serializer = {{ model }}Serializer(item, data=request.data)
if serializer.is_valid():
serializer.save()