├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── django_dyn_api ├── __init__.py ├── admin.py ├── apps.py ├── helpers.py ├── tests.py ├── urls.py └── views.py ├── docs └── blank.txt ├── publish.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.DS_Store 3 | *.egg* 4 | /dist/ 5 | /.idea 6 | /docs/_build/ 7 | /node_modules/ 8 | build/ 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.0.5] 2024-10-17 4 | ### Changes 5 | 6 | - Update RM 7 | - [Django Dynamic API](https://app-generator.dev/docs/developer-tools/dynamic-api.html) 8 | - Mention [Dynamic Django](https://app-generator.dev/docs/developer-tools/dynamic-django/index.html) Starter (commercial) 9 | 10 | ## [1.0.4] 2022-11-01 11 | ### Changes 12 | 13 | - DOCS Update 14 | 15 | ## [1.0.3] 2022-10-30 16 | ### Changes 17 | 18 | - `DOCS` Update 19 | - Remove `settings` module hard coding 20 | - Now use `getattr()` to bind the configuration 21 | 22 | ## [1.0.2] 2022-10-24 23 | ### Changes 24 | 25 | - DOCS Update 26 | - Added [Dynamic API](https://www.youtube.com/watch?v=nPQMUafTrNY) `video presentation` 27 | 28 | ## [1.0.1] 2022-10-24 29 | ### Changes 30 | 31 | - DOCS Update 32 | 33 | ## [1.0.0] 2022-10-24 34 | ### STABLE_RELEASE 35 | 36 | - Flag the stable version 37 | - DOCS Update 38 | 39 | ## [0.0.3] 2022-10-24 40 | ### Improvements 41 | 42 | - CRUD Works 43 | - JWT added 44 | 45 | ## [0.0.2] 2022-10-24 46 | ### Improvements 47 | 48 | - Draft Code Added 49 | 50 | ## [0.0.1] 2022-10-24 51 | ### Initial Version 52 | 53 | - Codebase design 54 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 [App Generator](https://appseed.us) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.md 2 | include README.md 3 | recursive-include django_dyn_api * 4 | recursive-include docs * 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Django Dynamic API](https://app-generator.dev/docs/developer-tools/dynamic-api.html) 2 | 3 | Simple tool that **Generates Secure APIs** on top of `DRF` with minimum effort - actively supported by **[App-Generator](https://app-generator.dev/)**. 4 | 5 | - [Dynamic API Features](https://www.youtube.com/watch?v=nPQMUafTrNY) - video presentation 6 | 7 |
8 | 9 | --- 10 | 11 | > For a **complete set of features** and long-term support, check out **[Dynamic Django](https://app-generator.dev/docs/developer-tools/dynamic-django/index.html)**, a powerful starter that incorporates: 12 | 13 | - [Dynamic DataTables](https://app-generator.dev/docs/developer-tools/dynamic-django/datatables.html): using a single line of configuration, the data saved in any table is automatically managed 14 | - [Dynamic API](https://app-generator.dev/docs/developer-tools/dynamic-django/api.html): any model can become a secure API Endpoint using DRF 15 | - [Dynamic Charts](https://app-generator.dev/docs/developer-tools/dynamic-django/charts.html): extract relevant charts without coding all major types are supported 16 | - [CSV Loader](https://app-generator.dev/docs/developer-tools/dynamic-django/csv-loader.html): translate CSV files into Django Models and (optional) load the information 17 | - Powerful [CLI Tools](https://app-generator.dev/docs/developer-tools/dynamic-django/cli.html) for the GIT interface, configuration editing, updating the configuration and database (create models, migrate DB) 18 | 19 |
20 | 21 | ## `Dynamic API Features` 22 | 23 | - `API engine` provided by `DRF` 24 | - `Minimal Configuration` (single line in config for each model) 25 | - `Handles any model` defined across the project 26 | 27 |
28 | 29 | ![Django Dynamic API - DRF Interface (open-source tool).](https://user-images.githubusercontent.com/51070104/197181145-f7458df7-23c3-4c14-bcb1-8e168882a104.jpg) 30 | 31 |
32 | 33 | ## How to use it 34 | 35 |
36 | 37 | > **Step #1** - `Install the package` 38 | 39 | ```bash 40 | $ pip install django-dynamic-api 41 | // OR 42 | $ pip install git+https://github.com/app-generator/django-dynamic-api.git 43 | ``` 44 | 45 |
46 | 47 | > **Step #2** - `Update Configuration`, include the new APPs 48 | 49 | ```python 50 | INSTALLED_APPS = [ 51 | 'django_dyn_api', # Django Dynamic API # <-- NEW 52 | 'rest_framework', # Include DRF # <-- NEW 53 | 'rest_framework.authtoken', # Include DRF Auth # <-- NEW 54 | ] 55 | ``` 56 | 57 |
58 | 59 | > **Step #3** - `Register the model` in `core/settings.py` (DYNAMIC_API section) 60 | 61 | This sample code assumes that `app1` exists and model `Book` is defined and migrated. 62 | 63 | ```python 64 | 65 | DYNAMIC_API = { 66 | # pattern: 67 | # API_SLUG -> Import_PATH 68 | 'books' : "app1.models.Book", 69 | } 70 | 71 | REST_FRAMEWORK = { 72 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 73 | 'rest_framework.authentication.SessionAuthentication', 74 | 'rest_framework.authentication.TokenAuthentication', 75 | ], 76 | } 77 | 78 | ``` 79 | 80 |
81 | 82 | > **Step #4** - `Migrate DB` and create the tables used by `DRF` 83 | 84 | ```bash 85 | $ python manage.py makemigrations 86 | $ python manage.py migrate 87 | ``` 88 | 89 |
90 | 91 | > **Step #5** - `Update routing`, include APIs 92 | 93 | ```python 94 | from django.contrib import admin 95 | from django.urls import path, include # <-- UPD: 'include` directive 96 | from rest_framework.authtoken.views import obtain_auth_token # <-- NEW 97 | 98 | urlpatterns = [ 99 | path("admin/", admin.site.urls), 100 | path('', include('django_dyn_api.urls')), # <-- NEW 101 | path('login/jwt/', view=obtain_auth_token), # <-- NEW 102 | ] 103 | ``` 104 | 105 |
106 | 107 | > **Step #6** - `Use API` 108 | 109 | If the managed model is `Books`, the API interface is `/api/books/` and all CRUD methods are available. 110 | 111 | > Note: for mutating requests, the `JWT Token` is provided by `http://localhost:8000/login/jwt/` route (the user should exist). 112 | 113 |
114 | 115 | ![Django API Generator - POSTMAN Interface (open-source tool).](https://user-images.githubusercontent.com/51070104/197181265-eb648e27-e5cf-4f3c-b330-d000aba53c6a.jpg) 116 | 117 |
118 | 119 | ### Links & resources 120 | 121 | - [DRF](https://www.django-rest-framework.org/) - HOMEpage 122 | - More [Developer Tools](https://appseed.us/developer-tools/) provided by `AppSeed` 123 | - Ask for [Support](https://appseed.us/support/) via `Email` & `Discord` 124 | 125 |
126 | 127 | --- 128 | [Django Dynamic API](https://app-generator.dev/docs/developer-tools/dynamic-api.html) - Open-source library provided by **[App-Generator](https://app-generator.dev/)** 129 | -------------------------------------------------------------------------------- /django_dyn_api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | -------------------------------------------------------------------------------- /django_dyn_api/admin.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.contrib import admin 7 | 8 | # Register your models here. 9 | -------------------------------------------------------------------------------- /django_dyn_api/apps.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.apps import AppConfig 7 | 8 | class DynApiConfig(AppConfig): 9 | default_auto_field = 'django.db.models.BigAutoField' 10 | name = 'django_dyn_api' 11 | -------------------------------------------------------------------------------- /django_dyn_api/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import datetime, sys, inspect, importlib 7 | 8 | from functools import wraps 9 | 10 | from django.db import models 11 | from django.http import HttpResponseRedirect, HttpResponse 12 | 13 | from rest_framework import serializers 14 | 15 | class Utils: 16 | @staticmethod 17 | def get_class(config, name: str) -> models.Model: 18 | return Utils.model_name_to_class(config[name]) 19 | 20 | @staticmethod 21 | def get_manager(config, name: str) -> models.Manager: 22 | return Utils.get_class(config, name).objects 23 | 24 | @staticmethod 25 | def get_serializer(config, name: str): 26 | class Serializer(serializers.ModelSerializer): 27 | class Meta: 28 | model = Utils.get_class(config, name) 29 | fields = '__all__' 30 | 31 | return Serializer 32 | 33 | @staticmethod 34 | def model_name_to_class(name: str): 35 | 36 | model_name = name.split('.')[-1] 37 | model_import = name.replace('.'+model_name, '') 38 | 39 | module = importlib.import_module(model_import) 40 | cls = getattr(module, model_name) 41 | 42 | return cls 43 | 44 | def check_permission(function): 45 | @wraps(function) 46 | def wrap(viewRequest, *args, **kwargs): 47 | 48 | try: 49 | 50 | # Check user 51 | if viewRequest.request.user.is_authenticated: 52 | return function(viewRequest, *args, **kwargs) 53 | 54 | # All good - allow the processing 55 | return HttpResponseRedirect('/login/') 56 | 57 | except Exception as e: 58 | 59 | # On error 60 | return HttpResponse( 'Error: ' + str( e ) ) 61 | 62 | return function(viewRequest, *args, **kwargs) 63 | 64 | return wrap 65 | -------------------------------------------------------------------------------- /django_dyn_api/tests.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.test import TestCase 7 | 8 | # Create your tests here. 9 | -------------------------------------------------------------------------------- /django_dyn_api/urls.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.contrib import admin 7 | from django.urls import path 8 | from .views import DynamicAPI 9 | 10 | urlpatterns = [ 11 | path('api//' , DynamicAPI.as_view()), 12 | path('api//' , DynamicAPI.as_view()), 13 | path('api///' , DynamicAPI.as_view()), 14 | ] 15 | -------------------------------------------------------------------------------- /django_dyn_api/views.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.http import Http404 7 | 8 | from django.contrib.auth.decorators import login_required 9 | from django.utils.decorators import method_decorator 10 | 11 | from rest_framework.generics import get_object_or_404 12 | from rest_framework.views import APIView 13 | from rest_framework.response import Response 14 | 15 | from django.conf import settings 16 | 17 | DYNAMIC_API = {} 18 | 19 | try: 20 | DYNAMIC_API = getattr(settings, 'DYNAMIC_API') 21 | except: 22 | pass 23 | 24 | from .helpers import Utils 25 | 26 | #from .helpers import check_permission 27 | 28 | class DynamicAPI(APIView): 29 | 30 | # READ : GET api/model/id or api/model 31 | def get(self, request, **kwargs): 32 | 33 | model_id = kwargs.get('id', None) 34 | try: 35 | if model_id is not None: 36 | 37 | # Validate for integer 38 | try: 39 | model_id = int(model_id) 40 | 41 | if model_id < 0: 42 | raise ValueError('Expect positive int') 43 | 44 | except ValueError as e: 45 | return Response(data={ 46 | 'message': 'Input Error = ' + str(e), 47 | 'success': False 48 | }, status=400) 49 | 50 | thing = get_object_or_404(Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')), id=model_id) 51 | model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(instance=thing) 52 | output = model_serializer.data 53 | else: 54 | all_things = Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')).all() 55 | thing_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name')) 56 | output = [] 57 | for thing in all_things: 58 | output.append(thing_serializer(instance=thing).data) 59 | except KeyError: 60 | return Response(data={ 61 | 'message': 'this model is not activated or not exist.', 62 | 'success': False 63 | }, status=400) 64 | except Http404: 65 | return Response(data={ 66 | 'message': 'object with given id not found.', 67 | 'success': False 68 | }, status=404) 69 | return Response(data={ 70 | 'data': output, 71 | 'success': True 72 | }, status=200) 73 | 74 | # CREATE : POST api/model/ 75 | #@check_permission 76 | def post(self, request, **kwargs): 77 | try: 78 | model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(data=request.data) 79 | if model_serializer.is_valid(): 80 | model_serializer.save() 81 | else: 82 | return Response(data={ 83 | **model_serializer.errors, 84 | 'success': False 85 | }, status=400) 86 | except KeyError: 87 | return Response(data={ 88 | 'message': 'this model is not activated or not exist.', 89 | 'success': False 90 | }, status=400) 91 | return Response(data={ 92 | 'message': 'Record Created.', 93 | 'success': True 94 | }, status=200) 95 | 96 | # UPDATE : PUT api/model/id/ 97 | #@check_permission 98 | def put(self, request, **kwargs): 99 | try: 100 | thing = get_object_or_404(Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')), id=kwargs.get('id')) 101 | model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(instance=thing, 102 | data=request.data, 103 | partial=True) 104 | if model_serializer.is_valid(): 105 | model_serializer.save() 106 | else: 107 | return Response(data={ 108 | **model_serializer.errors, 109 | 'success': False 110 | }, status=400) 111 | except KeyError: 112 | return Response(data={ 113 | 'message': 'this model is not activated or not exist.', 114 | 'success': False 115 | }, status=400) 116 | except Http404: 117 | return Response(data={ 118 | 'message': 'object with given id not found.', 119 | 'success': False 120 | }, status=404) 121 | return Response(data={ 122 | 'message': 'Record Updated.', 123 | 'success': True 124 | }, status=200) 125 | 126 | # DELETE : DELETE api/model/id/ 127 | #@check_permission 128 | def delete(self, request, **kwargs): 129 | try: 130 | model_manager = Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')) 131 | to_delete_id = kwargs.get('id') 132 | model_manager.get(id=to_delete_id).delete() 133 | except KeyError: 134 | return Response(data={ 135 | 'message': 'this model is not activated or not exist.', 136 | 'success': False 137 | }, status=400) 138 | except Utils.get_class(DYNAMIC_API, kwargs.get('model_name')).DoesNotExist as e: 139 | return Response(data={ 140 | 'message': 'object with given id not found.', 141 | 'success': False 142 | }, status=404) 143 | return Response(data={ 144 | 'message': 'Record Deleted.', 145 | 'success': True 146 | }, status=200) 147 | -------------------------------------------------------------------------------- /docs/blank.txt: -------------------------------------------------------------------------------- 1 | "coming soon" -------------------------------------------------------------------------------- /publish.txt: -------------------------------------------------------------------------------- 1 | python setup.py sdist 2 | 3 | twine check dist/* 4 | 5 | twine upload .\dist\THE_GENERATED_PACKAGE 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import find_packages, setup 3 | 4 | with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme: 5 | README = readme.read() 6 | 7 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 8 | 9 | setup( 10 | name='django-dynamic-api', 11 | version='1.0.5', 12 | zip_safe=False, 13 | packages=find_packages(), 14 | include_package_data=True, 15 | description='Django Dynamic API over DRF', 16 | long_description=README, 17 | long_description_content_type="text/markdown", 18 | url='https://app-generator.dev/docs/developer-tools/dynamic-api.html', 19 | author='AppSeed.us', 20 | author_email='support@appseed.us', 21 | license='MIT License', 22 | install_requires=[ 23 | 'djangorestframework', 24 | ], 25 | classifiers=[ 26 | 'Intended Audience :: Developers', 27 | 'Intended Audience :: System Administrators', 28 | 'License :: OSI Approved :: MIT License', 29 | 'Operating System :: OS Independent', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 2.6', 32 | 'Programming Language :: Python :: 2.7', 33 | 'Programming Language :: Python :: 3.2', 34 | 'Programming Language :: Python :: 3.3', 35 | 'Programming Language :: Python :: 3.4', 36 | 'Programming Language :: Python :: 3.5', 37 | 'Programming Language :: Python :: 3.6', 38 | 'Environment :: Web Environment', 39 | 'Topic :: Software Development', 40 | 'Topic :: Software Development :: User Interfaces', 41 | ], 42 | ) 43 | --------------------------------------------------------------------------------