├── example ├── contents │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── views.py │ ├── models.py │ └── admin.py ├── example │ ├── __init__.py │ ├── urls.py │ ├── wsgi.py │ └── settings.py ├── static │ └── css │ │ └── style.css └── manage.py ├── admino ├── fields.py ├── forms.py ├── templatetags │ ├── __init__.py │ └── admino_tags.py ├── __init__.py ├── constants.py ├── templates │ └── admin │ │ ├── base_site.html │ │ └── base.html ├── utils.py ├── views.py ├── serializers.py └── sites.py ├── MANIFEST.in ├── setup.cfg ├── .gitignore ├── LICENSE ├── setup.py └── README.md /example/contents/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/static/css/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admino/fields.py: -------------------------------------------------------------------------------- 1 | from django import forms -------------------------------------------------------------------------------- /example/contents/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admino/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | -------------------------------------------------------------------------------- /admino/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'erdemozkol' 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include README.rst 3 | include LICENSE -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license-file = LICENSE 3 | description-file = README.md 4 | -------------------------------------------------------------------------------- /example/contents/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /example/contents/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /admino/__init__.py: -------------------------------------------------------------------------------- 1 | from sites import site 2 | 3 | VERSION = (0, 0, 1) 4 | __version__ = '.'.join(map(str, VERSION)) 5 | 6 | -------------------------------------------------------------------------------- /admino/constants.py: -------------------------------------------------------------------------------- 1 | HTTP_METHOD_VIEWS = { 2 | "post": "create", 3 | "put": "update", 4 | "delete": "delete", 5 | } 6 | -------------------------------------------------------------------------------- /admino/templatetags/admino_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.filter(takes_context=True) 8 | def media_clean(media, **kwargs): 9 | return media -------------------------------------------------------------------------------- /admino/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base_site.html" %} 2 | 3 | {% block title %}Admino{% endblock %} 4 | 5 | {% block branding %} 6 |

Admino

7 | {% endblock %} 8 | 9 | -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /admino/templates/admin/base.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block extrastyle %} 4 | 9 | {% endblock %} 10 | 11 | {% block extrahead %} 12 | 13 | {% endblock %} -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.conf.urls.static import static 4 | 5 | from django.contrib import admin as django_admin 6 | import admino 7 | 8 | admino.site.activated() 9 | 10 | urlpatterns = [ 11 | url(r'^admin/', admino.site.urls), 12 | url(r'^django-admin/', django_admin.site.urls), 13 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 14 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /admino/utils.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | from admino.serializers import FormSerializer 4 | from django.forms import BaseForm 5 | from django.utils.functional import Promise 6 | from django.utils.encoding import force_unicode 7 | 8 | 9 | def import_from_string(module_path): 10 | """ 11 | Attempt to import a class from a string representation. 12 | """ 13 | try: 14 | parts = module_path.split('.') 15 | module_path, class_name = '.'.join(parts[:-1]), parts[-1] 16 | module = importlib.import_module(module_path) 17 | return getattr(module, class_name) 18 | except (ImportError, AttributeError) as e: 19 | msg = 'Could not import "%s" for Admino setting' % module_path 20 | raise ImportError(msg) 21 | -------------------------------------------------------------------------------- /example/contents/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.encoding import smart_unicode 3 | 4 | 5 | class BookType(models.Model): 6 | name = models.CharField(max_length=255) 7 | creation_date = models.DateTimeField(auto_now_add=True) 8 | 9 | def __unicode__(self): 10 | return smart_unicode(self.name) 11 | 12 | 13 | class Author(models.Model): 14 | name = models.CharField(max_length=255) 15 | image = models.ImageField(blank=True, null=True, upload_to="author/") 16 | creation_date = models.DateTimeField(auto_now_add=True) 17 | 18 | def __unicode__(self): 19 | return smart_unicode(self.name) 20 | 21 | 22 | class Book(models.Model): 23 | name = models.CharField(max_length=255) 24 | author = models.ForeignKey(Author) 25 | book_type = models.ManyToManyField(BookType) 26 | 27 | def __unicode__(self): 28 | return smart_unicode(self.name) -------------------------------------------------------------------------------- /example/contents/admin.py: -------------------------------------------------------------------------------- 1 | from admino.sites import ModelAdmino 2 | from django.contrib import admin 3 | from models import Author, BookType, Book 4 | 5 | 6 | class AuthorAdmin(admin.ModelAdmin): 7 | list_display = ("name", "creation_date") 8 | 9 | 10 | class BookTypeAdmin(admin.ModelAdmin): 11 | list_display = ("name", "creation_date") 12 | 13 | 14 | class BookAdmin(admin.ModelAdmin): 15 | admin_type = "admino" 16 | list_display = ("name", "author", "title") 17 | list_display_links = ("name", "author") 18 | list_filter = ("author", "name") 19 | 20 | def title(self, obj): 21 | return "mr %s" % obj.name 22 | 23 | 24 | class TestAdminoClass(ModelAdmino): 25 | def api_get(self, request, *args, **kwargs): 26 | print "hello" 27 | return super(TestAdminoClass, self).api_get(request, *args, **kwargs) 28 | 29 | 30 | admin.site.register(Author, AuthorAdmin) 31 | admin.site.register(BookType, BookTypeAdmin) 32 | admin.site.register(Book, BookAdmin) 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | screendumps/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | *.DS_Store 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | .idea/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | db.sqlite3 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | tmp.py 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # PyBuilder 63 | target/ 64 | notes/ 65 | #Ipython Notebook 66 | .ipynb_checkpoints 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Erdem Ozkol 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 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | VERSION = (0, 0, 1) 5 | __version__ = '.'.join(map(str, VERSION)) 6 | 7 | readme_rst = os.path.join(os.path.dirname(__file__), 'README.rst') 8 | 9 | if os.path.exists(readme_rst): 10 | long_description = open(readme_rst).read() 11 | else: 12 | long_description = "Admino is a django package that provides a REST API for admin endpoints. It allows you to customize django admin panel." 13 | 14 | # allow setup.py to be run from any path 15 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 16 | 17 | setup( 18 | name='django-admino', 19 | version=__version__, 20 | description="Admino is a django package that provides a REST API for admin endpoints. It allows you to customize django admin panel.", 21 | long_description=long_description, 22 | author="Erdem Ozkol", 23 | author_email="info@admino.io", 24 | url="https://github.com/erdem/django-admino", 25 | license="MIT", 26 | platforms=["any"], 27 | packages=find_packages(exclude=("example", "env", "notes")), 28 | include_package_data=True, 29 | classifiers=[ 30 | "Environment :: Web Environment", 31 | "Intended Audience :: Developers", 32 | "License :: OSI Approved :: MIT License", 33 | "Operating System :: OS Independent", 34 | "Framework :: Django", 35 | "Programming Language :: Python", 36 | ], 37 | ) 38 | 39 | -------------------------------------------------------------------------------- /example/contents/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-03-25 14:56 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Author', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=255)), 22 | ('image', models.ImageField(blank=True, null=True, upload_to=b'author/')), 23 | ('creation_date', models.DateTimeField(auto_now_add=True)), 24 | ], 25 | ), 26 | migrations.CreateModel( 27 | name='Book', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('name', models.CharField(max_length=255)), 31 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contents.Author')), 32 | ], 33 | ), 34 | migrations.CreateModel( 35 | name='BookType', 36 | fields=[ 37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('name', models.CharField(max_length=255)), 39 | ('creation_date', models.DateTimeField(auto_now_add=True)), 40 | ], 41 | ), 42 | migrations.AddField( 43 | model_name='book', 44 | name='book_type', 45 | field=models.ManyToManyField(to='contents.BookType'), 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Django Admino (Alpha) 2 | 3 | Admino is a django package that provides a REST API for admin endpoints. It allows you to customize django admin panel. 4 | 5 | http://admino.io 6 | 7 | 8 | ### Problem? 9 | 10 | Django admin is good solution for development tests and i/o, but django admin needs to be more customizable and extendable. 11 | 12 | ### Solution: 13 | 14 | if you want to implement a custom widget or ui for django admin, you may need a REST API to handle records. 15 | 16 | Admino added to new api views to django admin genericly. You don't need to change default admin files. 17 | Every API endpoint will generate your "ModelAdmin" configurations. 18 | 19 | ##### EXAMPLE: 20 | 21 | **Visible Books list page url:** /admin/books/book/?is_visible__exact=1 22 | 23 | ![](http://oi67.tinypic.com/2dqkfbs.jpg) 24 | 25 | **Visible Books api url:** /admin/**api**/books/book/?is_visible__exact=1 26 | 27 | ![](http://oi65.tinypic.com/2nu3779.jpg) 28 | 29 | 30 | **Book detail page url:** /admin/books/book/1/ 31 | 32 | ![](http://oi66.tinypic.com/4jx4d0.jpg) 33 | 34 | 35 | **Book detail api url:** /admin/**api**/books/book/1/ 36 | 37 | ![](http://oi65.tinypic.com/9jisus.jpg) 38 | 39 | #### Install 40 | 41 | pip install django-admino 42 | 43 | settings.py 44 | 45 | INSTALLED_APPS = [ 46 | 'admino', 47 | 48 | 'django.contrib.admin', 49 | 'django.contrib.auth', 50 | 'django.contrib.contenttypes', 51 | 'django.contrib.sessions', 52 | 'django.contrib.messages', 53 | 'django.contrib.staticfiles', 54 | 55 | 'books', 56 | ] 57 | 58 | urls.py 59 | 60 | from django.contrib import admin 61 | import admino 62 | 63 | admin.site = admino.site.activated(admin.site) 64 | 65 | urlpatterns = [ 66 | url(r'^admin/', admin.site.urls), 67 | ] 68 | 69 | 70 | Add custom admin Mixin class: 71 | 72 | settings.py 73 | 74 | ADMINO_MIXIN_CLASS = "app.module.AdminMixinClass" 75 | 76 | -------------------------------------------------------------------------------- /admino/views.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from urllib import urlencode 3 | 4 | from admino.serializers import ModelAdminSerializer 5 | from django.core.urlresolvers import reverse_lazy 6 | from django.http import JsonResponse 7 | from django.views.generic import View 8 | 9 | 10 | class APIView(View): 11 | 12 | def json_response(self, data, *args, **kwargs): 13 | return JsonResponse(data, safe=False, *args, **kwargs) 14 | 15 | 16 | class ChangeListRetrieveAPIView(APIView): 17 | 18 | def get_api_next_url(self, request, cl): 19 | page_num = cl.page_num 20 | if page_num and page_num is not int or not cl.multi_page: 21 | return None 22 | info = self.model._meta.app_label, self.model._meta.model_name 23 | url = reverse_lazy("admin:%s_%s_api_list" % info) 24 | host = request.get_host() 25 | params = cl.params 26 | params["p"] = page_num + 1 27 | return "%s://%s%s?%s" % (request.scheme, host, url, urlencode(params)) 28 | 29 | def get_api_previous_url(self, request, cl): 30 | page_num = cl.page_num 31 | if page_num == 0 or not cl.multi_page: 32 | return None 33 | 34 | info = self.model._meta.app_label, self.model._meta.model_name 35 | url = reverse_lazy("admin:%s_%s_api_list" % info) 36 | host = request.get_host() 37 | params = cl.params 38 | params["p"] = page_num - 1 39 | return "%s://%s%s?%s" % (request.scheme, host, url, urlencode(params)) 40 | 41 | def get(self, request, model_admin, admin_cl, *args, **kwargs): 42 | self.model = admin_cl.model 43 | results = [] 44 | for obj in admin_cl.result_list: 45 | results.append(model_admin.obj_as_dict(request, obj)) 46 | 47 | data = OrderedDict() 48 | data["count"] = admin_cl.result_count 49 | data["next"] = self.get_api_next_url(request, admin_cl) 50 | data["previous"] = self.get_api_previous_url(request, admin_cl) 51 | data["results"] = results 52 | return self.json_response(data) 53 | 54 | 55 | class APIMetaView(APIView): 56 | 57 | def get(self, request, model_admin, *args, **kwargs): 58 | form = model_admin.get_form(request) 59 | data = ModelAdminSerializer(model_admin=model_admin, admin_form=form).data 60 | return self.json_response(data) 61 | 62 | 63 | class AdminDetailRetrieveAPIView(APIView): 64 | 65 | def get(self, request, model_admin, admin_cl, *args, **kwargs): 66 | return self.json_response("ok") 67 | -------------------------------------------------------------------------------- /admino/serializers.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from django.forms.forms import DeclarativeFieldsMetaclass 3 | 4 | from django import forms 5 | from django.utils.encoding import force_unicode 6 | from django.utils.functional import Promise 7 | 8 | 9 | def obj_as_dict(o): 10 | 11 | if isinstance(o, DeclarativeFieldsMetaclass): 12 | o = FormSerializer(form=o).data 13 | 14 | if isinstance(o, forms.Field): 15 | o = FormFieldSerializer(field=o).data 16 | 17 | if isinstance(o, forms.Widget): 18 | o = FormWidgetSerializer(widget=o).data 19 | 20 | if isinstance(o, (list, tuple)): 21 | o = [obj_as_dict(x) for x in o] 22 | 23 | if isinstance(o, Promise): 24 | try: 25 | o = force_unicode(o) 26 | except: 27 | # Item could be a lazy tuple or list 28 | try: 29 | o = [obj_as_dict(x) for x in o] 30 | except: 31 | raise Exception('Unable to resolve lazy object %s' % o) 32 | if callable(o): 33 | o = o() 34 | 35 | if isinstance(o, dict): 36 | for k, v in o.items(): 37 | o[k] = obj_as_dict(v) 38 | 39 | return o 40 | 41 | 42 | class BaseSerializer(object): 43 | @property 44 | def data(self): 45 | raise NotImplementedError 46 | 47 | 48 | class FormWidgetSerializer(BaseSerializer): 49 | def __init__(self, widget, *args, **kwargs): 50 | self.widget = widget 51 | 52 | @property 53 | def data(self): 54 | widget_data = OrderedDict() 55 | widget_data["type"] = self.widget.__class__.__name__ 56 | return widget_data 57 | 58 | 59 | class FormFieldSerializer(BaseSerializer): 60 | def __init__(self, field, *args, **kwargs): 61 | self.field = field 62 | 63 | @property 64 | def data(self): 65 | field_data = OrderedDict() 66 | field_data["type"] = self.field.__class__.__name__ 67 | field_data["widget"] = self.field.widget 68 | field_data["is_required"] = self.field.required 69 | return field_data 70 | 71 | 72 | class FormSerializer(BaseSerializer): 73 | def __init__(self, form, *args, **kwargs): 74 | self.form = form 75 | 76 | @property 77 | def data(self): 78 | form_fields = [] 79 | for name, field in self.form.base_fields.items(): 80 | d = dict() 81 | d[name] = field 82 | form_fields.append(d) 83 | return form_fields 84 | 85 | 86 | #todo check "formfield_overrides" 87 | MODEL_ADMIN_CLASS_ATTRIBUTES = ( 88 | "raw_id_fields", "fields", "exclude", "fieldsets", "filter_vertical", "filter_horizontal", "radio_fields", 89 | "prepopulated_fields", "readonly_fields", "ordering", "view_on_site", 90 | "show_full_result_count", "list_display", "list_display_links", "list_filter", "list_per_page", "list_max_show_all", 91 | "list_editable", "search_fields", "date_hierarchy","save_as", "save_on_top") 92 | 93 | 94 | class ModelAdminSerializer(BaseSerializer): 95 | def __init__(self, model_admin, admin_form, *args, **kwargs): 96 | self.model_admin = model_admin 97 | self.admin_form = admin_form 98 | 99 | @property 100 | def data(self): 101 | data = OrderedDict() 102 | for attr in MODEL_ADMIN_CLASS_ATTRIBUTES: 103 | data[attr] = getattr(self.model_admin, attr, None) 104 | data["form"] = self.admin_form 105 | return obj_as_dict(data) 106 | 107 | def serialize_form(self): 108 | return FormSerializer(self.admin_form).data 109 | -------------------------------------------------------------------------------- /example/example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | import sys 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | SITE_PATH = os.path.abspath(os.path.dirname(__file__)) 20 | 21 | PROJECT_PATH = os.path.normpath(os.path.join(SITE_PATH, '..', '..')) 22 | if PROJECT_PATH not in sys.path: 23 | sys.path.insert(0, PROJECT_PATH) 24 | 25 | 26 | # Quick-start development settings - unsuitable for production 27 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 28 | 29 | # SECURITY WARNING: keep the secret key used in production secret! 30 | SECRET_KEY = '&#^btn*&n^@6l87p)oe@s*rs57%@!amu845_90453r)2dsrq)g' 31 | 32 | # SECURITY WARNING: don't run with debug turned on in production! 33 | DEBUG = True 34 | 35 | ALLOWED_HOSTS = [] 36 | 37 | 38 | # Application definition 39 | 40 | INSTALLED_APPS = [ 41 | 'admino', 42 | 43 | 'django.contrib.admin', 44 | 'django.contrib.auth', 45 | 'django.contrib.contenttypes', 46 | 'django.contrib.sessions', 47 | 'django.contrib.messages', 48 | 'django.contrib.staticfiles', 49 | 50 | 'contents', 51 | ] 52 | 53 | MIDDLEWARE_CLASSES = [ 54 | 'django.middleware.security.SecurityMiddleware', 55 | 'django.contrib.sessions.middleware.SessionMiddleware', 56 | 'django.middleware.common.CommonMiddleware', 57 | 'django.middleware.csrf.CsrfViewMiddleware', 58 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 59 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 60 | 'django.contrib.messages.middleware.MessageMiddleware', 61 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 62 | ] 63 | 64 | ROOT_URLCONF = 'example.urls' 65 | 66 | TEMPLATES = [ 67 | { 68 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 69 | 'DIRS': [], 70 | 'APP_DIRS': True, 71 | 'OPTIONS': { 72 | 'context_processors': [ 73 | 'django.template.context_processors.debug', 74 | 'django.template.context_processors.request', 75 | 'django.contrib.auth.context_processors.auth', 76 | 'django.contrib.messages.context_processors.messages', 77 | ], 78 | }, 79 | }, 80 | ] 81 | 82 | WSGI_APPLICATION = 'example.wsgi.application' 83 | 84 | 85 | # Database 86 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 87 | 88 | DATABASES = { 89 | 'default': { 90 | 'ENGINE': 'django.db.backends.sqlite3', 91 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 92 | } 93 | } 94 | 95 | 96 | # Password validation 97 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 98 | 99 | AUTH_PASSWORD_VALIDATORS = [ 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 108 | }, 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 111 | }, 112 | ] 113 | 114 | 115 | # Internationalization 116 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 117 | 118 | LANGUAGE_CODE = 'en-us' 119 | 120 | TIME_ZONE = 'UTC' 121 | 122 | USE_I18N = True 123 | 124 | USE_L10N = True 125 | 126 | USE_TZ = True 127 | 128 | # ADMINO_MIXIN_CLASS = "contents.admin.TestAdminoClass" 129 | 130 | 131 | # Static files (CSS, JavaScript, Images) 132 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 133 | 134 | STATIC_ROOT = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'media/') 135 | STATIC_URL = '/static/' 136 | MEDIA_ROOT = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'media/uploads/') 137 | MEDIA_URL = '/uploads/' 138 | -------------------------------------------------------------------------------- /admino/sites.py: -------------------------------------------------------------------------------- 1 | import json 2 | from functools import update_wrapper 3 | 4 | from admino.utils import import_from_string 5 | from admino.views import ChangeListRetrieveAPIView, APIMetaView 6 | 7 | from django import http 8 | from django.conf import settings 9 | from django.conf.urls import url, include 10 | from django.core.serializers.json import DjangoJSONEncoder 11 | from django.core import serializers 12 | from django.core.urlresolvers import reverse_lazy 13 | from django.http import HttpResponse 14 | from django.views.decorators.csrf import csrf_exempt 15 | 16 | from django.contrib.admin import actions 17 | from django.contrib.admin import site as django_site 18 | from django.contrib.admin import AdminSite as DjangoAdminSite, ModelAdmin as DjangoModelAdmin, autodiscover as django_admin_autodiscover 19 | from django.contrib.admin.options import IncorrectLookupParameters 20 | 21 | from .constants import HTTP_METHOD_VIEWS 22 | 23 | 24 | class AdminoMixin(DjangoModelAdmin): 25 | http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace'] 26 | 27 | def get_api_urls(self): 28 | 29 | def wrap(view): 30 | def wrapper(*args, **kwargs): 31 | return self.admin_site.admin_view(view)(*args, **kwargs) 32 | 33 | wrapper.model_admin = self 34 | return update_wrapper(wrapper, view) 35 | 36 | info = self.model._meta.app_label, self.model._meta.model_name 37 | 38 | urlpatterns = [ 39 | url(r'^$', 40 | wrap(self.admin_site.admin_view(self.dispatch)), 41 | name='%s_%s_api_list' % info), 42 | url(r'^(?P[-\d]+)/$', 43 | wrap(self.admin_site.admin_view(self.dispatch)), 44 | name='%s_%s_api_detail' % info), 45 | url(r'^meta/$', 46 | wrap(self.admin_site.admin_view(self.api_meta_view)), 47 | name='%s_%s_api_meta' % info), 48 | ] 49 | return urlpatterns 50 | 51 | def api_urls(self): 52 | return self.get_api_urls() 53 | 54 | api_urls = property(api_urls) 55 | 56 | def _allowed_methods(self): 57 | return [m.upper() for m in self.http_method_names if hasattr(self, "api_" + m)] 58 | 59 | def http_method_not_allowed(self, request, *args, **kwargs): 60 | if settings.DEBUG and self._allowed_methods(): 61 | raise Exception("Only" + str(self._allowed_methods())) 62 | return http.HttpResponseNotAllowed(self._allowed_methods()) 63 | 64 | @csrf_exempt 65 | def dispatch(self, request, *args, **kwargs): 66 | if not request.method.lower() in self.http_method_names: 67 | return self.http_method_not_allowed(request, *args, **kwargs) 68 | 69 | handler = self.http_method_not_allowed 70 | if request.method.lower() == "get": 71 | handler = getattr(self, "api_get", self.http_method_not_allowed) 72 | 73 | api_view_name = HTTP_METHOD_VIEWS.get(request.method.lower()) 74 | if api_view_name: 75 | handler = getattr(self, "api_" + api_view_name, self.http_method_not_allowed) 76 | 77 | return handler(request, *args, **kwargs) 78 | 79 | def api_get(self, request, *args, **kwargs): 80 | if kwargs.get("pk"): 81 | return self.api_detail(request, *args, **kwargs) 82 | else: 83 | return self.api_list(request, *args, **kwargs) 84 | 85 | def get_admin_cl(self, request): 86 | ChangeList = self.get_changelist(request) 87 | try: 88 | cl = ChangeList(request, self.model, self.list_display, 89 | self.list_display_links, self.list_filter, self.date_hierarchy, 90 | self.search_fields, self.list_select_related, self.list_per_page, 91 | self.list_max_show_all, self.list_editable, self) 92 | return cl 93 | 94 | except IncorrectLookupParameters: 95 | raise Exception("IncorrectLookupParameters") 96 | 97 | def get_model_admin_field_names(self, request, obj): 98 | """ 99 | This method return admin class readonly custom fields. 100 | Getting ModelAdmin list_display + readonly_fields 101 | """ 102 | return set(list(self.get_readonly_fields(request, obj)) + list(self.get_list_display(request))) 103 | 104 | def serialize_objs(self, objs): 105 | data_objs = json.loads(serializers.serialize('json', objs)) 106 | for data in data_objs: 107 | data.update(data["fields"]) 108 | del data["fields"] 109 | return data_objs 110 | 111 | def serialize_obj(self, obj): 112 | return self.serialize_objs([obj])[0] 113 | 114 | def obj_as_dict(self, request, obj): 115 | data = self.serialize_obj(obj) 116 | 117 | # serialize model instance fields datas 118 | for field in obj._meta.get_fields(): 119 | if field.is_relation and field.concrete: 120 | field_value = getattr(obj, field.name) 121 | if field_value: 122 | if field.many_to_many: 123 | data[field.name] = self.serialize_objs(field_value.all()) 124 | elif field.many_to_one or field.one_to_one or field.one_to_many: 125 | data[field.name] = self.serialize_obj(field_value) 126 | 127 | # add custom admin class field to serialized bundle 128 | model_admin_fields = self.get_model_admin_field_names(request, obj) 129 | for field in model_admin_fields: 130 | if field in data: 131 | continue 132 | 133 | if hasattr(obj, field): 134 | f = getattr(obj, field) 135 | data[field] = unicode(f) 136 | 137 | if hasattr(self, field): 138 | field_method = getattr(self, field) 139 | if callable(field_method): 140 | data[field] = field_method(obj) 141 | else: 142 | data[field] = field_method 143 | 144 | info = self.model._meta.app_label, self.model._meta.model_name 145 | admin_detail_url = str(reverse_lazy("admin:%s_%s_change" % info, args=(obj.id,))) 146 | data["admin_detail_url"] = admin_detail_url 147 | return data 148 | 149 | def get_api_list_view_class(self): 150 | return ChangeListRetrieveAPIView 151 | 152 | def api_meta_view(self, request, *args, **kwargs): 153 | return APIMetaView().get(request, model_admin=self) 154 | 155 | def api_list(self, request, *args, **kwargs): 156 | cl = self.get_admin_cl(request) 157 | view_class = self.get_api_list_view_class() 158 | return view_class().get(request, model_admin=self, admin_cl=cl) 159 | 160 | def api_detail(self, request, *args, **kwargs): 161 | obj = self.get_object(request, object_id=kwargs.get("pk")) 162 | ModelForm = self.get_form(request, obj=obj) 163 | form = ModelForm(instance=obj) 164 | data = self.obj_as_dict(request, form.instance) 165 | return HttpResponse(json.dumps(data, cls=DjangoJSONEncoder), content_type='application/json') 166 | 167 | def api_create(self, request, *args, **kwargs): 168 | data = json.loads(request.body) 169 | 170 | ModelForm = self.get_form(request, obj=None) 171 | form = ModelForm(data=data, files=request.FILES) 172 | if form.is_valid(): 173 | obj = form.save() 174 | data = self.obj_as_dict(request, obj) 175 | return HttpResponse(json.dumps(data, cls=DjangoJSONEncoder), content_type="application/json") 176 | else: 177 | errors = { 178 | "errors": json.loads(form.errors.as_json()) 179 | } 180 | return HttpResponse(json.dumps(errors), status=400, content_type="application/json") 181 | 182 | def api_update(self, request, *args, **kwargs): 183 | return HttpResponse("put") 184 | 185 | def api_delete(self, request, *args, **kwargs): 186 | return HttpResponse("delete") 187 | 188 | 189 | class ModelAdmino(AdminoMixin, DjangoModelAdmin): 190 | admin_type = "admino" 191 | 192 | 193 | class AdminoSite(DjangoAdminSite): 194 | 195 | def __init__(self, django_site, name='admino'): 196 | self.django_site = django_site 197 | self._registry = {} 198 | self.name = name 199 | self._actions = {'delete_selected': actions.delete_selected} 200 | self._global_actions = self._actions.copy() 201 | 202 | def activated(self): 203 | django_admin_registered_apps = self.django_site._registry 204 | for model, admin_obj in django_admin_registered_apps.items(): 205 | mixin_class = AdminoMixin 206 | if hasattr(settings, "ADMINO_MIXIN_CLASS"): 207 | module_path = getattr(settings, "ADMINO_MIXIN_CLASS") 208 | mixin_class = import_from_string(module_path) 209 | django_admin_class = admin_obj.__class__ 210 | admino_class = type("ModelAdmino", (mixin_class, django_admin_class), {"admin_type": "admino"}) 211 | admino_obj = admino_class(model, self) 212 | self._registry[model] = admino_obj 213 | 214 | django_admin_autodiscover() 215 | return self 216 | 217 | def get_urls(self): 218 | urlpatterns = super(AdminoSite, self).get_urls() 219 | valid_app_labels = [] 220 | for model, model_admin in self._registry.items(): 221 | api_urlpatterns = [ 222 | url(r'^api/%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.api_urls)), 223 | ] 224 | urlpatterns = urlpatterns + api_urlpatterns 225 | if model._meta.app_label not in valid_app_labels: 226 | valid_app_labels.append(model._meta.app_label) 227 | return urlpatterns 228 | 229 | site = AdminoSite(django_site=django_site) 230 | 231 | 232 | --------------------------------------------------------------------------------