├── .editorconfig ├── .github └── workflows │ └── pypi.yml ├── .gitignore ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── conf.py ├── example.rst ├── how_it_works.rst ├── html ├── index.rst ├── install.rst ├── management.rst ├── mixins.rst └── requirements.txt ├── kaio ├── __init__.py ├── debug_toolbar.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── generate_ini.py │ │ └── kaiosettings.py ├── mixins │ ├── __init__.py │ ├── cache.py │ ├── celeryconf.py │ ├── cms.py │ ├── compress.py │ ├── database.py │ ├── debug.py │ ├── email.py │ ├── filerconf.py │ ├── logs.py │ ├── paths.py │ ├── security.py │ ├── sentry.py │ ├── storage.py │ └── whitenoise.py ├── options.py └── properties.py ├── readthedocs.yaml ├── setup.cfg └── setup.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,js,json,css,less}] 16 | indent_size = 2 17 | 18 | [*.json] 19 | insert_final_newline = ignore 20 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yml: -------------------------------------------------------------------------------- 1 | name: PyPI Publish 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | deploy: 8 | name: Publish on PyPI 9 | runs-on: ubuntu-latest 10 | if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | - name: Set up Python 3.x 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.x' 18 | - name: Install dependencies 19 | run: python -m pip install build 20 | - name: Build package 21 | run: python -m build 22 | - name: Publish to PyPI 23 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 24 | with: 25 | user: __token__ 26 | password: ${{ secrets.PYPI_API_TOKEN }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Virtual envs 2 | .venv 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 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 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ 63 | 64 | #Ipython Notebook 65 | .ipynb_checkpoints 66 | 67 | # File managers 68 | .directory 69 | .DS_Store 70 | 71 | # IDEs and editors 72 | *.geany 73 | .*.nja 74 | .project 75 | .pydevproject 76 | .settings 77 | nbproject/ 78 | .idea 79 | .vscode 80 | 81 | 82 | # Created by .ignore support plugin (hsz.mobi) 83 | docs/_build 84 | docs/_static 85 | docs/_templates 86 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Change log 3 | ========== 4 | 5 | v1.6.0 (2025-03-19) 6 | -------------------- 7 | 8 | * New setting ``ALLOWED_HOSTS_DEBUG_TOOLBAR`` for allowing debug toolbar for some hosts in addition 9 | to ``INTERNAL_IPS``. 10 | * Update requirements for building docs and use them to create reproducible builds in ReadTheDocs. 11 | 12 | v1.5.0 (2023-06-29) 13 | -------------------- 14 | 15 | * Support Python 3.11 and Django 4.2. 16 | * Migrate ReadTheDocs configuration file to v2. 17 | * GitHub Actions to deploy to PyPI. 18 | 19 | v1.4.2 (2023-01-19) 20 | -------------------- 21 | 22 | * Add more settings for django_yubin: MAILER_STORAGE_DELETE. 23 | 24 | v1.4.1 (2023-01-11) 25 | -------------------- 26 | 27 | * Add more settings for django_yubin: MAILER_HC_QUEUED_LIMIT_OLD, MAILER_STORAGE_BACKEND and 28 | MAILER_FILE_STORAGE_DIR. 29 | 30 | v1.4.0 (2022-09-27) 31 | -------------------- 32 | 33 | * Add support for django_yubin >= 2.0.0. 34 | 35 | v1.3.0 (2022-05-30) 36 | -------------------- 37 | 38 | * Improve ``CeleryMixin``: better and updated default values and some new settings. 39 | 40 | v1.2.0 (2022-05-11) 41 | -------------------- 42 | 43 | * Add ``AWS_S3_ENDPOINT_URL`` and ``AWS_S3_CUSTOM_DOMAIN`` to ``StorageMixin`` to support CloudFront. 44 | 45 | v1.1.0 (2022-05-09) 46 | -------------------- 47 | 48 | * Add ``SENTRY_IGNORE_LOGGERS`` to allow to avoid sending noisy logs to Sentry. 49 | 50 | v1.0.1 (2022-05-05) 51 | -------------------- 52 | 53 | * Update requirements versions to fix build errors in ReadTheDocs. 54 | * No new features. 55 | 56 | v1.0.0 (2022-05-05) 57 | -------------------- 58 | 59 | * Add new mixin for Sentry SDK and remove old Raven and Sentry settings from LogsMixin. 60 | 61 | v0.15.0 (2022-05-02) 62 | -------------------- 63 | 64 | * Automatically configure ``INTERNAL_IPS`` to show debug_toolbar inside contaniers when ``ENABLE_DEBUG_TOOLBAR`` is 65 | ``True``. 66 | * Update security fixes in dependencies (thanks GitHub dependabot). 67 | 68 | v0.14.3 (2020-11-05) 69 | -------------------- 70 | 71 | * Fix encoding regression in 0.14.2. 72 | 73 | v0.14.2 (2020-03-12) 74 | -------------------- 75 | 76 | * Use UTF-8 encoding when logging to files. 77 | 78 | v0.14.1 (2019-04-05) 79 | -------------------- 80 | 81 | * Parse only the first .ini file starting from current directory (included) up to "/" (excluded). 82 | 83 | v0.14.0 (2018-12-18) 84 | -------------------- 85 | 86 | * Add SESSION_CACHE_XXX settings in CachesMixin to allow to configure sessions in cache. 87 | 88 | v0.13.0 (2018-05-31) 89 | -------------------- 90 | 91 | * Add optional LOG_FORMATTER_EXTRA_FIELDS setting. 92 | * Add mixin for django-storages (currently only AWS S3). 93 | 94 | v0.12.0 (2018-03-06) 95 | -------------------- 96 | 97 | * Property to configure INTERNAL_IPS via .ini or envvar. 98 | * Allow to override DEBUG_TOOLBAR_MIDDLEWARE in settings. 99 | 100 | v0.11.0 (2018-02-02) 101 | -------------------- 102 | 103 | * Support to set DATABASES OPTIONS options. 104 | * Support to customize logger formatter class and format. 105 | 106 | v0.10.0 (2017-11-08) 107 | -------------------- 108 | 109 | * Add support for sass (and scss) and remove support for coffescript. 110 | 111 | v0.9.1 (2017-11-08) 112 | ------------------- 113 | 114 | * Don't wait for lock in Yubin. 115 | 116 | v0.9.0 (2017-11-08) 117 | ------------------- 118 | 119 | * Better defaults for DEFAULT_FROM_EMAIL and EMAIL_BACKEND. 120 | 121 | v0.8.0 (2017-09-01) 122 | ------------------- 123 | 124 | * Add Sentry support for RQ. 125 | 126 | v0.7.2 (2017-06-15) 127 | ------------------- 128 | 129 | * Updated documentation and small bug fix in WhiteNoiseMixin. 130 | 131 | v0.7.1 (2017-06-15) 132 | ------------------- 133 | 134 | * Added documentation first version. 135 | 136 | v0.7.0 (2017-06-12) 137 | ------------------- 138 | 139 | * Add support for SECURE_PROXY_SSL_HEADER in SecurityMixin. 140 | 141 | v0.6.0 (2017-05-31) 142 | ------------------- 143 | 144 | * Breaking change: Remove DATABASE_OPTIONS, it doesn't work with environment variables. 145 | 146 | v0.5.0 (2017-05-08) 147 | ------------------- 148 | 149 | * Strip names and values from options. 150 | * Add support for redis password. 151 | 152 | v0.4.2 (2016-11-10) 153 | ------------------- 154 | 155 | * Fix missing return in database mixin. 156 | 157 | v0.4.1 (2016-11-04) 158 | ------------------- 159 | 160 | * COMPRESS_CSS_HASHING_METHOD = 'content' by default. 161 | * Accept DATABASE_OPTIONS. 162 | * Fix #2 ImportError: cannot import name 'NoArgsCommand' with Django 1.10. 163 | 164 | 165 | v0.4.0 (2016-08-29) 166 | ------------------- 167 | 168 | * Support Django 1.10. 169 | * Support django-configurations 2 170 | * Support Babel 6. 171 | * Add Whitenoise mixin. 172 | * Better handling and defaults for database tests. 173 | 174 | v0.3.0 (2016-05-31) 175 | ------------------- 176 | 177 | * First public version. 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, APSL 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of django-kaio nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGELOG.rst 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | django-kaio 3 | =========== 4 | 5 | .. image:: https://img.shields.io/pypi/v/django-kaio.svg 6 | :target: https://pypi.python.org/pypi/django-kaio/ 7 | 8 | Class based settings for Django projects that can be read from multiple sources. 9 | 10 | 11 | Documentation 12 | ------------- 13 | Visit the `documentation `_ for an in-depth look at **django-kaio**. 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = django-kaio 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # django-kaio documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jun 13 12:57:40 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.githubpages', 36 | 'sphinx.ext.todo' 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # 45 | # source_suffix = ['.rst', '.md'] 46 | source_suffix = '.rst' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = 'django-kaio' 53 | copyright = '2017, APSL' 54 | author = 'APSL' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = '0.14.0' 62 | # The full version, including alpha/beta/rc tags. 63 | release = '0.14.0' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | # language = en 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | # This patterns also effect to html_static_path and html_extra_path 75 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 76 | 77 | # The name of the Pygments (syntax highlighting) style to use. 78 | pygments_style = 'sphinx' 79 | 80 | # If true, `todo` and `todoList` produce output, else they produce nothing. 81 | todo_include_todos = True 82 | 83 | 84 | # -- Options for HTML output ---------------------------------------------- 85 | 86 | # The theme to use for HTML and HTML Help pages. See the documentation for 87 | # a list of builtin themes. 88 | # 89 | # html_theme = 'alabaster' 90 | html_theme = 'sphinx_rtd_theme' 91 | 92 | # Theme options are theme-specific and customize the look and feel of a theme 93 | # further. For a list of options available for each theme, see the 94 | # documentation. 95 | # 96 | # html_theme_options = {} 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | html_static_path = ['_static'] 102 | 103 | 104 | # -- Options for HTMLHelp output ------------------------------------------ 105 | 106 | # Output file base name for HTML help builder. 107 | htmlhelp_basename = 'django-kaiodoc' 108 | 109 | 110 | # -- Options for LaTeX output --------------------------------------------- 111 | 112 | latex_elements = { 113 | # The paper size ('letterpaper' or 'a4paper'). 114 | # 115 | # 'papersize': 'letterpaper', 116 | 117 | # The font size ('10pt', '11pt' or '12pt'). 118 | # 119 | # 'pointsize': '10pt', 120 | 121 | # Additional stuff for the LaTeX preamble. 122 | # 123 | # 'preamble': '', 124 | 125 | # Latex figure (float) alignment 126 | # 127 | # 'figure_align': 'htbp', 128 | } 129 | 130 | # Grouping the document tree into LaTeX files. List of tuples 131 | # (source start file, target name, title, 132 | # author, documentclass [howto, manual, or own class]). 133 | latex_documents = [ 134 | (master_doc, 'django-kaio.tex', 'django-kaio Documentation', 135 | 'APSL', 'manual'), 136 | ] 137 | 138 | 139 | # -- Options for manual page output --------------------------------------- 140 | 141 | # One entry per manual page. List of tuples 142 | # (source start file, name, description, authors, manual section). 143 | man_pages = [ 144 | (master_doc, 'django-kaio', 'django-kaio Documentation', 145 | [author], 1) 146 | ] 147 | 148 | 149 | # -- Options for Texinfo output ------------------------------------------- 150 | 151 | # Grouping the document tree into Texinfo files. List of tuples 152 | # (source start file, target name, title, author, 153 | # dir menu entry, description, category) 154 | texinfo_documents = [ 155 | (master_doc, 'django-kaio', 'django-kaio Documentation', 156 | author, 'django-kaio', 'One line description of project.', 157 | 'Miscellaneous'), 158 | ] 159 | -------------------------------------------------------------------------------- /docs/example.rst: -------------------------------------------------------------------------------- 1 | Application example 2 | =================== 3 | 4 | 5 | Example from scratch. The kiosk 6 | ------------------------------- 7 | 8 | 1. We execute 9 | 10 | .. code-block:: python 11 | 12 | django-admin.py startporject kiosk 13 | 14 | Since we do not want the project and the application to be called the same we will 15 | rename the main directory of `kiosk` to` prj_kiosk` and we move all within the ``src`` 16 | directory of the project. We will change the name of the srcf folder to ``main`` 17 | so that` kiosko` will be free if we want to create there the data model. 18 | 19 | 20 | 2. We create the requirements file in the project directory and create 21 | the requirements to proceed to create the virtual environment. 22 | 23 | .. code-block:: python 24 | 25 | # requirements.txt 26 | Django==1.10.7 27 | django-appconf==1.0.2 28 | django_compressor==2.1 29 | django-extensions==1.7.2 30 | django-kaio==0.7.1 31 | django-logentry-admin==1.0.2 32 | django-redis==4.4.4 33 | django-robots==2.0 34 | django-storages==1.5.2 35 | django-yubin==0.3.1 36 | psycopg2==2.6.2 37 | pytz==2016.6.1 38 | redis==2.10.5 39 | requests==2.17.3 40 | 41 | with the versions we need 42 | 43 | 3. Modify ``manage.py`` and ``wsgi.py`` as explained in the :ref:`config wsgi.py and manage.py` section. 44 | 45 | 4. Replace the settings.py by our custom version of it. E.g.: 46 | 47 | .. code-block:: python 48 | 49 | import os 50 | from os.path import join 51 | 52 | from configurations import Configuration 53 | from django.contrib.messages import constants as messages 54 | from kaio import Options 55 | from kaio.mixins import (CachesMixin, DatabasesMixin, CompressMixin, LogsMixin, 56 | PathsMixin, SecurityMixin, DebugMixin, WhiteNoiseMixin) 57 | 58 | opts = Options() 59 | 60 | 61 | class Base(CachesMixin, DatabasesMixin, CompressMixin, PathsMixin, LogsMixin, 62 | SecurityMixin, DebugMixin, WhiteNoiseMixin, Configuration): 63 | """ 64 | Project settings for development and production. 65 | """ 66 | 67 | DEBUG = opts.get('DEBUG', True) 68 | 69 | THUMBNAIL_FORCE_OVERWRITE = True 70 | 71 | BASE_DIR = opts.get('APP_ROOT', None) 72 | APP_SLUG = opts.get('APP_SLUG', 'kiosk') 73 | SITE_ID = 1 74 | SECRET_KEY = opts.get('SECRET_KEY', 'key') 75 | 76 | USE_I18N = True 77 | USE_L10N = True 78 | USE_TZ = True 79 | LANGUAGE_CODE = 'es' 80 | TIME_ZONE = 'Europe/Madrid' 81 | 82 | ROOT_URLCONF = 'main.urls' 83 | WSGI_APPLICATION = 'main.wsgi.application' 84 | 85 | INSTALLED_APPS = [ 86 | # django 87 | 'django.contrib.admin', 88 | 'django.contrib.auth', 89 | 'django.contrib.contenttypes', 90 | 'django.contrib.sessions', 91 | 'django.contrib.sites', 92 | 'django.contrib.messages', 93 | 'django.contrib.staticfiles', 94 | 95 | # apps 96 | 'kiosk', 97 | 'main', 98 | 99 | # 3rd parties 100 | 'compressor', 101 | 'constance', 102 | 'cookielaw', 103 | 'constance.backends.database', 104 | 'django_extensions', 105 | 'django_yubin', 106 | 'kaio', 107 | 'logentry_admin', 108 | 'robots', 109 | 'sorl.thumbnail', 110 | 'bootstrap3', 111 | 'storages', 112 | 'django_tables2', 113 | ] 114 | 115 | MIDDLEWARE = [ 116 | 'django.middleware.security.SecurityMiddleware', 117 | 'django.middleware.locale.LocaleMiddleware', 118 | 'django.contrib.sessions.middleware.SessionMiddleware', 119 | 'django.middleware.common.CommonMiddleware', 120 | 'django.middleware.csrf.CsrfViewMiddleware', 121 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 122 | 'django.contrib.messages.middleware.MessageMiddleware', 123 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 124 | ] 125 | 126 | # SecurityMiddleware options 127 | SECURE_BROWSER_XSS_FILTER = True 128 | 129 | TEMPLATES = [ 130 | { 131 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 132 | 'DIRS': [ 133 | os.path.join(BASE_DIR, 'sfc_test_portal/templates/'), 134 | ], 135 | 'OPTIONS': { 136 | 'context_processors': [ 137 | "django.contrib.auth.context_processors.auth", 138 | "django.template.context_processors.debug", 139 | "django.template.context_processors.i18n", 140 | "django.template.context_processors.media", 141 | "django.template.context_processors.static", 142 | "django.contrib.messages.context_processors.messages", 143 | "django.template.context_processors.tz", 144 | 'django.template.context_processors.request', 145 | 'constance.context_processors.config', 146 | ], 147 | 'loaders': [ 148 | 'django.template.loaders.filesystem.Loader', 149 | 'django.template.loaders.app_directories.Loader', 150 | ] 151 | }, 152 | }, 153 | ] 154 | if not DEBUG: 155 | TEMPLATES[0]['OPTIONS']['loaders'] = [ 156 | ('django.template.loaders.cached.Loader', TEMPLATES[0]['OPTIONS']['loaders']), 157 | ] 158 | 159 | # Email 160 | EMAIL_BACKEND = 'django_yubin.smtp_queue.EmailBackend' 161 | DEFAULT_FROM_EMAIL = opts.get('DEFAULT_FROM_EMAIL', 'Example ') 162 | MAILER_LOCK_PATH = join(BASE_DIR, 'send_mail') 163 | 164 | # Bootstrap 3 alerts integration with Django messages 165 | MESSAGE_TAGS = { 166 | messages.ERROR: 'danger', 167 | } 168 | 169 | # Constance 170 | CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' 171 | CONSTANCE_DATABASE_CACHE_BACKEND = 'default' 172 | CONSTANCE_CONFIG = { 173 | 'GOOGLE_ANALYTICS_TRACKING_CODE': ('UA-XXXXX-Y', 'Google Analytics tracking code.'), 174 | } 175 | 176 | 177 | 5. Generate the .ini file in the ``src`` directory executing: 178 | 179 | .. code-block:: python 180 | 181 | python manage.py generate_ini > app.ini 182 | 183 | and then modify the default parameters we have. In particular we will have to modify 184 | the database connection and put the application in debug mode. 185 | 186 | 6. Execute the migrations: 187 | 188 | .. code-block:: python 189 | 190 | python manage.py syndb --all 191 | 192 | And we proceed as always. 193 | 194 | 7. We need to modify ``main/urls.py`` to be able to serve the static content while we are in debug mode. 195 | 196 | .. code-block:: python 197 | 198 | from django.conf.urls import patterns, include, url 199 | from django.conf import settings 200 | 201 | from django.contrib import admin 202 | admin.autodiscover() 203 | 204 | urlpatterns = patterns('', 205 | # Examples: 206 | url(r'^$', 'kiosk.views.home', name='home'), 207 | url(r'^kiosk/', include('kiosk.foo.urls')), 208 | url(r'^admin/', include(admin.site.urls)), 209 | ) 210 | 211 | if settings.DEBUG: 212 | from django.conf.urls.static import static 213 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 214 | 215 | 216 | And finally we run 217 | 218 | .. code-block:: python 219 | 220 | python manage.py apsettings 221 | 222 | to check the **settings** of our application. 223 | 224 | If we need to add an application settings we have two options: 225 | 226 | 1. Generate a mixin for the particular module, if it has to be reusable. 227 | 2. Add such configuration in our settings.py base class. 228 | 229 | -------------------------------------------------------------------------------- /docs/how_it_works.rst: -------------------------------------------------------------------------------- 1 | How it works 2 | ============ 3 | 4 | The simplest way to get a param value is: 5 | 6 | .. code-block:: python 7 | 8 | from apconf import Options 9 | 10 | opts = Options() 11 | APP_SLUG = opts.get('APP_SLUG', 'apsl-app') 12 | 13 | We get the APP_SLUG, with the default value 'apsl-app'. Besides, *kaio* stores 14 | internally the request default value, in order to inform the management scripts. 15 | (See below). 16 | 17 | 18 | settings.py 19 | ----------- 20 | 21 | We configure the settings through classes, using *django-configurations*. 22 | We can use the mixins, so that the repetitive configurations rest into the mixin, 23 | centralizing the parametrization and saving code. 24 | 25 | **Important** Make sure that *Settings* is the last class in the class definition: 26 | 27 | Basic app settings sample: 28 | 29 | .. code-block:: python 30 | 31 | import os 32 | from os.path import join 33 | 34 | from configurations import Configuration 35 | from django.contrib.messages import constants as messages 36 | from kaio import Options 37 | from kaio.mixins import (CachesMixin, DatabasesMixin, CompressMixin, LogsMixin, 38 | PathsMixin, SecurityMixin, DebugMixin, WhiteNoiseMixin) 39 | 40 | 41 | opts = Options() 42 | 43 | 44 | class Base(CachesMixin, DatabasesMixin, CompressMixin, PathsMixin, LogsMixin, 45 | SecurityMixin, DebugMixin, WhiteNoiseMixin, Configuration): 46 | """ 47 | Project settings for development and production. 48 | """ 49 | 50 | DEBUG = opts.get('DEBUG', True) 51 | 52 | THUMBNAIL_FORCE_OVERWRITE = True 53 | 54 | BASE_DIR = opts.get('APP_ROOT', None) 55 | APP_SLUG = opts.get('APP_SLUG', 'test-project') 56 | SITE_ID = 1 57 | SECRET_KEY = opts.get('SECRET_KEY', 'key') 58 | 59 | USE_I18N = True 60 | USE_L10N = True 61 | USE_TZ = True 62 | LANGUAGE_CODE = 'es' 63 | TIME_ZONE = 'Europe/Madrid' 64 | 65 | ROOT_URLCONF = 'main.urls' 66 | WSGI_APPLICATION = 'main.wsgi.application' 67 | 68 | INSTALLED_APPS = [ 69 | 'django.contrib.admin', 70 | 'django.contrib.auth', 71 | 'django.contrib.contenttypes', 72 | 'django.contrib.sessions', 73 | 'django.contrib.sites', 74 | 'django.contrib.messages', 75 | 'django.contrib.staticfiles', 76 | 'kaio', 77 | '...', 78 | ] 79 | 80 | MIDDLEWARE = [ 81 | 'django.middleware.security.SecurityMiddleware', 82 | 'django.middleware.locale.LocaleMiddleware', 83 | 'django.contrib.sessions.middleware.SessionMiddleware', 84 | 'django.middleware.common.CommonMiddleware', 85 | 'django.middleware.csrf.CsrfViewMiddleware', 86 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 87 | 'django.contrib.messages.middleware.MessageMiddleware', 88 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 89 | ] 90 | 91 | Using mixins, almost we have only to configure the INSTALLED_APPS. 92 | For further configurations we'll adding more mixins. 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APSL/django-kaio/917ed4617aa9639c5ba7672f11c7f3f08ad130df/docs/html -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-kaio documentation master file, created by 2 | sphinx-quickstart on Tue Jun 13 12:57:40 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to django-kaio's documentation! 7 | ======================================= 8 | 9 | **Django-kaio** is a django-package that helps us to configure our django project. 10 | The values of the configuration can come from an .ini file or from environment settings. 11 | 12 | The values are casted automatically, first trying to cast to *int*, then to *bool* and finally to *string*. 13 | 14 | Also note that we can create class-based configurations settings, as django-configurations_ do. 15 | 16 | .. _django-configurations: https://django-configurations.readthedocs.io/en/stable/# 17 | 18 | 19 | Also includes: 20 | 21 | * if the .ini file does not exist set the default values 22 | * searches the .ini file in the current and parent directories 23 | * managanement script to let us see the current project configuration 24 | * management script to generate the .ini file with the default values 25 | * uses django-configurations in order to be able to create class based settings 26 | * mixins for standard configurations, such as Paths, Filer, Cache, Database... 27 | 28 | 29 | .. toctree:: 30 | install 31 | how_it_works 32 | management 33 | mixins 34 | example 35 | :maxdepth: 2 36 | 37 | 38 | Indices and tables 39 | ================== 40 | 41 | * :ref:`genindex` 42 | * :ref:`modindex` 43 | * :ref:`search` 44 | 45 | .. todolist:: 46 | 47 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | To install the package 5 | 6 | .. code-block:: python 7 | 8 | pip install django-kaio 9 | 10 | Then you've to append :code:`kaio` to :code:`INSTALLED_APPS` in your settings. 11 | 12 | .. code-block:: python 13 | 14 | INSTALLED_APPS = ( 15 | ... 16 | 'kaio', 17 | ) 18 | 19 | 20 | Configuration with django-configurations 21 | ---------------------------------------- 22 | 23 | 24 | To use class based settings, we need to configure django-configurations. 25 | It's all explained here_. 26 | 27 | .. _here: http://django-configurations.readthedocs.org/en/latest/ 28 | 29 | 30 | .. _config wsgi.py and manage.py: 31 | 32 | Modifiying wsgi.py and manage.py 33 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 | 35 | We need to configure two files of our project: ``manage.py`` and ``wsgi.py`` 36 | 37 | * manage.py 38 | 39 | .. code-block:: python 40 | 41 | #!/usr/bin/env python 42 | 43 | import os 44 | import sys 45 | 46 | if __name__ == "__main__": 47 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings') 48 | os.environ.setdefault('DJANGO_CONFIGURATION', 'Base') 49 | 50 | from configurations.management import execute_from_command_line 51 | 52 | execute_from_command_line(sys.argv) 53 | 54 | * wsgi.py 55 | 56 | .. code-block:: python 57 | 58 | import os 59 | 60 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings') 61 | os.environ.setdefault('DJANGO_CONFIGURATION', 'Base') 62 | 63 | from configurations.wsgi import get_wsgi_application 64 | 65 | application = get_wsgi_application() 66 | 67 | If you need or prefer to use asgi instead of wsgi: 68 | 69 | * asgi.py 70 | 71 | .. code-block:: python 72 | 73 | import os 74 | 75 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings") 76 | os.environ.setdefault("DJANGO_CONFIGURATION", "Base") 77 | 78 | from configurations.asgi import get_asgi_application 79 | 80 | application = get_asgi_application() 81 | -------------------------------------------------------------------------------- /docs/management.rst: -------------------------------------------------------------------------------- 1 | Management scripts 2 | ================== 3 | 4 | We have to management scripts available in order to see the current 5 | configurations values and to generate a file with default values into the standard output. 6 | 7 | apsettings 8 | ---------- 9 | 10 | We use it to see the current configurations values. 11 | 12 | .. code-block:: python 13 | 14 | python manage.py apsettings 15 | 16 | It shows the current configuration. In three columns: 17 | * final values into the settings 18 | * params into the .ini file 19 | * param default value 20 | 21 | 22 | generate_ini 23 | ------------ 24 | 25 | We use it to generate a file with default values into the standard output. 26 | 27 | .. code-block:: python 28 | 29 | python manage.py generate_ini 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/mixins.rst: -------------------------------------------------------------------------------- 1 | Mixins 2 | ====== 3 | 4 | The mixins are defined in kaio/mixins and inherit from **Object**. They are defined from a function that takes 5 | the name from the .ini (onwards app.ini) file section. 6 | 7 | The params into the app.ini file are set without quotation marks, either are numbers, texts, strings, etc. 8 | 9 | CachesMixin 10 | ----------- 11 | 12 | This mixin allows us to configure the cache of our application. It is intended for use with ``Redis`` in 13 | production. If a cache type is not defined, it means that we have ``dummy`` cache. 14 | 15 | .. code-block:: python 16 | 17 | from kaio.mixins import CachesMixin 18 | 19 | **Section**: Cache 20 | 21 | **Parameters** 22 | 23 | **CACHE_TYPE** 24 | cache type, by default ``locmem``, options: ``locmem``, ``redis``, ``dummy`` 25 | 26 | **CACHE_REDIS_DB** 27 | redis database number that we'll use as cache into redis. By default, ``2``. 28 | 29 | **CACHE_REDIS_PASSWORD** 30 | Password for redis. By default without password. 31 | 32 | **REDIS_HOST** 33 | redis host name. By default ``localhost`` 34 | 35 | **REDIS_PORT** 36 | port of the redis server. By default ``6379`` 37 | 38 | **CACHE_PREFIX** 39 | prefix to use in the cache keys for the projecte. By default is the project ``SLUG``. 40 | 41 | **CACHE_TIMEOUT** 42 | Cache expiration time. By default ``3600`` seconds, 1 hour. 43 | 44 | **CACHE_MAX_ENTRIES** 45 | Maximum number of cached entries. By default ``10000``. 46 | 47 | **CachesMixin** also allows to configure the cache for sessions. You must set 48 | ``SESSION_ENGINE = 'django.contrib.sessions.backends.cache'`` or ``'.cached_db'``. 49 | By default use almost same settings as default cache. 50 | 51 | **SESSION_CACHE_TYPE** 52 | cache type, by default ``CACHE_TYPE``, options: ``redis`` 53 | 54 | **SESSION_CACHE_REDIS_DB** 55 | redis database number that we'll use as cache into redis. By default, ``3``. 56 | 57 | **SESSION_CACHE_REDIS_PASSWORD** 58 | Password for redis. By default without password. 59 | 60 | **SESSION_REDIS_HOST** 61 | redis host name. By default ``REDIS_HOST`` 62 | 63 | **SESSION_REDIS_PORT** 64 | port of the redis server. By default ``REDIS_PORT`` 65 | 66 | **SESSION_CACHE_PREFIX** 67 | prefix to use in the cache keys for the projecte. By default ``CACHE_PREFIX_session``. 68 | 69 | **SESSION_CACHE_TIMEOUT** 70 | Cache expiration time. By default ``None`` (no timeout). 71 | 72 | **SESSION_CACHE_MAX_ENTRIES** 73 | Maximum number of cached entries. By default ``1000000``. 74 | 75 | **SESSION_CACHE_ALIAS** 76 | Selects the cache to use for sessions. By default ``sessions``. 77 | 78 | 79 | CeleryMixin 80 | ----------- 81 | 82 | This mixin allows us to configure Celery_ in case we use it in our application. 83 | 84 | .. _Celery: https://docs.celeryq.dev/en/stable/ 85 | 86 | .. code-block:: python 87 | 88 | from kaio.mixins import CeleryMixin 89 | 90 | **Section**: Celery 91 | 92 | **Parameters** 93 | 94 | **CELERY_DISABLE_RATE_LIMITS** 95 | ``True`` 96 | 97 | **CELERYBEAT_SCHEDULER** 98 | ``django_celery_beat.schedulers:DatabaseScheduler`` 99 | 100 | **CELERY_DEFAULT_QUEUE** 101 | Default: ``celery``. 102 | 103 | **CELERY_RESULT_BACKEND** 104 | Default ``redis://{REDIS_HOST}:{REDIS_PORT}/{CELERY_REDIS_RESULT_DB}`` if Redis is available, else ``None``. 105 | 106 | **CELERY_IGNORE_RESULT** 107 | Default ``False``. 108 | 109 | **CELERY_RESULT_EXPIRES** 110 | Default: ``86400`` (1 day in seconds). 111 | 112 | **CELERY_MAX_CACHED_RESULTS** 113 | Default ``5000``. 114 | 115 | **CELERY_CACHE_BACKEND** 116 | Default: ``default`` 117 | 118 | **CELERY_ALWAYS_EAGER** 119 | Default ``False``. 120 | 121 | **CELERY_EAGER_PROPAGATES_EXCEPTIONS** 122 | Default ``True``. 123 | 124 | **CELERY_REDIS_RESULT_DB** 125 | Default ``0``. 126 | 127 | **CELERY_REDIS_BROKER_DB** 128 | Default ``0``. 129 | 130 | **RABBITMQ_HOST** 131 | Default ``localhost``. 132 | 133 | **RABBITMQ_PORT** 134 | Default ``5672``. 135 | 136 | **RABBITMQ_USER** 137 | Default ``guest``. 138 | 139 | **RABBITMQ_PASSWD** 140 | Default ``guest``. 141 | 142 | **RABBITMQ_VHOST** 143 | Default ``/``. 144 | 145 | **BROKER_TYPE** 146 | Default ``redis``. 147 | 148 | **BROKER_URL** 149 | * Default for Redis: ``redis://{REDIS_HOST}:{REDIS_PORT}/{CELERY_REDIS_RESULT_DB}``. 150 | * Default for RabbitMQ: 151 | ``amqp://{RABBITMQ_USER}:{RABBITMQ_PASSWD}@{RABBITMQ_HOST}:{RABBITMQ_PORT}/{RABBITMQ_VHOST}`` 152 | * Default for others: ``django://``. 153 | 154 | 155 | CmsMixin 156 | -------- 157 | 158 | .. warning:: Deprecated mixin 159 | 160 | Mixin that helps us to get the languages configured on the project. 161 | 162 | .. code-block:: python 163 | 164 | from kaio.mixins import CMSMixin 165 | 166 | **Section**: Compress 167 | 168 | **Parameters** 169 | 170 | 171 | CompressMixin 172 | ------------- 173 | 174 | django-compressor_ configuration. 175 | 176 | .. _django-compressor: http://django-compressor.readthedocs.org/en/latest/settings/ 177 | 178 | .. code-block:: python 179 | 180 | from kaio.mixins import CompressMixin 181 | 182 | **Section**: Compress 183 | 184 | **Parameters** 185 | 186 | **COMPRESS_DEBUG_TOGGLE** 187 | by default ``nocompress`` in DEBUG mode. 188 | 189 | **COMPRESS_ENABLED** 190 | by default ``False``. 191 | 192 | **COMPRESS_CSS_HASHING_METHOD** 193 | by default ``content``. 194 | 195 | **COMPRESS_LESSC_ENABLED** 196 | by default ``True``. 197 | 198 | **COMPRESS_SASS_ENABLED** 199 | by default ``True``. 200 | 201 | **COMPRESS_BABEL_ENABLED** 202 | by default ``False``. 203 | 204 | **COMPRESS_LESSC_PATH** 205 | by default ``lessc``. 206 | 207 | **COMPRESS_SASS_PATH** 208 | by default ``node-sass``. 209 | 210 | **COMPRESS_BABEL_PATH** 211 | by default ``babel``. 212 | 213 | **COMPRESS_PRECOMPILERS** 214 | by default includes automatically less, babel and coffeescript if they are active. 215 | 216 | **COMPRESS_OUTPUT_DIR** 217 | by default ``CACHE/``. 218 | 219 | **COMPRESS_OFFLINE** 220 | by default ``False``. 221 | 222 | **COMPRESS_OFFLINE_TIMEOUT** 223 | by default ``31536000`` (1 year in seconds). 224 | 225 | **COMPRESS_OFFLINE_MANIFEST** 226 | by default ``manifest.json``. 227 | 228 | 229 | **Static offline compression** 230 | 231 | In order to be able to use it you have to follow two steps: 232 | 233 | * add COMPRESS_OFFLINE = True to app.ini file 234 | * the ``{% compress js/css %}`` can not have any django logic, no vars, no templatetags, no subblocks... 235 | 236 | This last step is advisable to follow it as a good practice just in case 237 | in any future moment we want the **COMPRESS_OFFLINE** feature. 238 | 239 | Example of the [Compress] section with compress activated and compress offline 240 | activated. **LESS**, **SASS** and **BABEL** suport are active by default: 241 | 242 | .. code-block:: python 243 | 244 | ... 245 | [Compress] 246 | COMPRESS_ENABLED = True 247 | COMPRESS_OFFLINE = True 248 | ... 249 | 250 | The idea is to have COMPRESS_OFFLINE = False in development environment and to 251 | have COMPRESS_OFFLINE = True once we deploy the project to production environment. 252 | 253 | 254 | In order to test it in development environment you have to execute 255 | 256 | .. code-block:: python 257 | 258 | python manage.py collectstatic 259 | 260 | and then 261 | 262 | .. code-block:: python 263 | 264 | python manage.py compress 265 | 266 | 267 | DatabaseMixin 268 | ------------- 269 | 270 | Database access configuration. 271 | 272 | .. code-block:: python 273 | 274 | from kaio.mixins import DatabasesMixin 275 | 276 | **Section**: Database 277 | 278 | **Parameters** 279 | 280 | **DATABASE_ENGINE** 281 | by default ``sqlite3``, allow ``sqlite3``, ``postgresql_psycopg2``, ``mysql``, ``oracle`` 282 | 283 | **DATABASE_NAME** 284 | default name, if we use ``sqlite3`` it will be ``db.sqlite`` 285 | 286 | **DATABASE_USER** 287 | user to use 288 | 289 | **DATABASE_PASSWORD** 290 | password 291 | 292 | **DATABASE_HOST** 293 | host name 294 | 295 | **DATABASE_PORT** 296 | port number 297 | 298 | **DATABASE_CONN_MAX_AGE** 299 | by default ``0``. 300 | 301 | **DATABASE_OPTIONS_OPTIONS** 302 | string to add to database options setting. Empty by default. Example to change the postgresql schema: ``DATABASE_OPTIONS_OPTIONS = -c search_path=some_schema`` 303 | 304 | 305 | DebugMixin 306 | ---------- 307 | This mixin allows us to define and work with the debug parameters and configure ``django-debug-toolbar`` 308 | to be used in our application. Therefore its use depends on whether this module is configured 309 | in the ``requirements.txt`` of the project, otherwise we will not have activated the option of the ``debug toolbar``. 310 | 311 | .. code-block:: python 312 | 313 | from kaio.mixins import DebugMixin 314 | 315 | **Section**: Debug 316 | 317 | **Parameters** 318 | 319 | **DEBUG** 320 | by default ``False``. 321 | 322 | **TEMPLATE_DEBUG** 323 | by default same as **DEBUG**. 324 | 325 | **ENABLE_DEBUG_TOOLBAR** 326 | by default same as **DEBUG**. ``False`` if the module is not installed. 327 | 328 | **INTERNAL_IPS** 329 | Debug Toolbar is shown only if your IP is listed in the INTERNAL_IPS setting. 330 | CSV of IPs , by default `127.0.0.1`. 331 | If ``ENABLE_DEBUG_TOOLBAR`` is ``True`` it automatically appends IPs for showing the toolbar inside contaniers. 332 | https://django-debug-toolbar.readthedocs.io/en/stable/installation.html#configure-internal-ips 333 | 334 | **ALLOWED_HOSTS_DEBUG_TOOLBAR** 335 | If you want to set debug toolbar on an environment deployed with docker for testing, INTERNAL_IPS are not enough because the IP from your domain will not be 336 | an INTERNAL_IP of the docker image. If ``ENABLE_DEBUG_TOOLBAR`` is ``True`` it will set ALLOWED_HOSTS_DEBUG_TOOLBAR from envvar, expecting a comma separated list. 337 | Then, you can override ``SHOW_TOOLBAR_CALLBACK`` debug toolbar config with `kaio.debug_toolbar.show_toolbar` to take this allowed hosts into consideration. 338 | https://django-debug-toolbar.readthedocs.io/en/stable/configuration.html#show-toolbar-callback 339 | 340 | 341 | EmailMixin 342 | ---------- 343 | 344 | Set the basic parameters by default to configure the mail. In its configuration by default allows us to 345 | operate with ``django-yubin``, leaving its final configuration for the production environment. 346 | 347 | .. code-block:: python 348 | 349 | from kaio.mixins import EmailMixin 350 | 351 | **Section**: Email 352 | 353 | **Parameters** 354 | 355 | **DEFAULT_FROM_EMAIL** 356 | by default ``Example ``. 357 | 358 | **EMAIL_BACKEND** 359 | by default ``django.core.mail.backends.smtp.EmailBackend``, ``django_yubin.smtp_queue.EmailBackend`` 360 | or ``django_yubin.backends.QueuedEmailBackend`` if django_yubin is installed and its version. 361 | 362 | **EMAIL_FILE_PATH** 363 | by default ``None``. 364 | 365 | **EMAIL_HOST** 366 | by default ``localhost``. 367 | 368 | **EMAIL_HOST_PASSWORD** 369 | by default ``''``. 370 | 371 | **EMAIL_HOST_USER** 372 | by default ``''``. 373 | 374 | **EMAIL_PORT** 375 | by default ``25``. 376 | 377 | **EMAIL_SUBJECT_PREFIX** 378 | Prefix to add to Django's subject. By default `[Django]` 379 | 380 | **EMAIL_USE_TLS** 381 | by default ``False``. 382 | 383 | **MAILER_PAUSE_SEND** 384 | by default ``False``. 385 | 386 | **MAILER_USE_BACKEND** 387 | by default ``django.core.mail.backends.smtp.EmailBackend``. 388 | 389 | **MAILER_HC_QUEUED_LIMIT_OLD** 390 | If there are emails created, enqueued or in progress for more than x minutes, Yubin HealthCheck 391 | view will show an error. By default ``30``. 392 | 393 | **MAILER_STORAGE_BACKEND** 394 | by default ``django_yubin.storage_backends.DatabaseStorageBackend``. 395 | 396 | **MAILER_STORAGE_DELETE** 397 | by default ``True``. 398 | 399 | **MAILER_FILE_STORAGE_DIR** 400 | by default ``yubin``. 401 | 402 | Following settings are deprecated, they exist for backwards compatibility. 403 | 404 | **MAILER_MAIL_ADMINS_PRIORITY** 405 | by default ``None``. 406 | 407 | **MAILER_MAIL_MANAGERS_PRIORITY** 408 | by default ``None``. 409 | 410 | **MAILER_EMPTY_QUEUE_SLEEP** 411 | by default ``30``. 412 | 413 | **MAILER_LOCK_WAIT_TIMEOUT** 414 | by default ``0``. 415 | 416 | **MAILER_LOCK_PATH** 417 | by default ``os.path.join(self.APP_ROOT, "send_mail")``. 418 | 419 | Recall that in order to use django_yubin_ we must configure the **cron**. 420 | 421 | .. _django_yubin: http://django-yubin.readthedocs.org/en/latest/settings.html 422 | 423 | 424 | FilerMixin 425 | ---------- 426 | 427 | .. todo:: FilerMixin - Complete description 428 | 429 | .. code-block:: python 430 | 431 | from kaio.mixins import FilerMixin 432 | 433 | **Section**: Filer 434 | 435 | **Parameters** 436 | 437 | **FILER_IS_PUBLIC_DEFAULT** 438 | Default ``True``. 439 | 440 | **FILER_ENABLE_PERMISSIONS** 441 | Default ``False``. 442 | 443 | **FILER_DEBUG** 444 | Default ``False``. 445 | 446 | **FILER_ENABLE_LOGGING** 447 | Default ``False``. 448 | 449 | **FILER_0_8_COMPATIBILITY_MODE** 450 | Default ``False``. 451 | 452 | **THUBMNAIL_DEBUG** 453 | Default ``False``. 454 | 455 | **THUMBNAIL_QUALITY** 456 | Default ``85``. 457 | 458 | **FILER_CUSTOM_NGINX_SERVER** 459 | Default ``False``. 460 | 461 | **DEFAULT_FILE_STORAGE** 462 | Default ``django.core.files.storage.FileSystemStorage``. 463 | 464 | **FILER_CUSTOM_SECURE_MEDIA_ROOT** 465 | Default ``filer_private``. 466 | 467 | 468 | LogsMixin 469 | --------- 470 | 471 | Mixin that handles the configuration the Django logs. Established some default configurations that we use 472 | in our development and production environments for the project configuration. 473 | 474 | .. code-block:: python 475 | 476 | from kaio.mixins import LogsMixin 477 | 478 | **Section**: Logs 479 | 480 | **Parameters** 481 | 482 | **LOG_LEVEL** 483 | sets the project logging level. By default: ``DEBUG`` 484 | 485 | **DJANGO_LOG_LEVEL** 486 | sets the django logging level. By default: ``ERROR`` 487 | 488 | **LOG_FILE** 489 | name of the log file. No established by default, usually specified in production. 490 | 491 | **EXTRA_LOGGING** 492 | parameter that sets the log level at module level in a easy way. It does not have a default value. 493 | As a parameter we have to set a module list with the differents levels to log each separated by comma 494 | in the followinf format: ``:log_value`` 495 | E.g.: 496 | 497 | .. code-block:: python 498 | 499 | [Logs] 500 | EXTRA_LOGGING = oscar.paypal:DEBUG, django.db:INFO 501 | 502 | **LOG_FORMATTER_FORMAT** 503 | by default `[%(asctime)s] %(levelname)s %(name)s-%(lineno)s %(message)s`. 504 | This option is not interpolated, see https://docs.python.org/3/library/configparser.html#interpolation-of-values 505 | 506 | **LOG_FORMATTER_CLASS** 507 | custom formatter class. By default no formatter class is used. 508 | 509 | **LOG_FORMATTER_EXTRA_FIELDS** 510 | optional extra fields passed to the logger formatter class. 511 | 512 | 513 | SentryMixin 514 | ----------- 515 | 516 | Only adds the Django integration. You can change this overwriting the ``integrations()`` method. In case 517 | you need more low-level control, you can overwrite the ``sentry_init()`` method. 518 | 519 | .. code-block:: python 520 | 521 | from kaio.mixins import SentryMixin 522 | 523 | **SENTRY_DSN** 524 | The DSN to configure Sentry. If blank, Sentry integration is not initialized. By default ``''``. 525 | 526 | **SENTRY_IGNORE_LOGGERS** 527 | CSV of loggers to don't send to Sentry. By default ``'django.security.DisallowedHost'``. 528 | 529 | 530 | PathsMixin 531 | ---------- 532 | 533 | Paths base settings. 534 | 535 | .. code-block:: python 536 | 537 | from kaio.mixins import PathsMixin 538 | 539 | **Section**: Paths 540 | 541 | **Parameters** 542 | 543 | **APP_ROOT** 544 | By default the current directory, ``abspath('.')``. 545 | 546 | **MEDIA_ROOT** 547 | By default the current ``APP_ROOT`` + ``/media``. 548 | 549 | **STATIC_URL** 550 | By default ``/static/``. 551 | 552 | **MEDIA_URL** 553 | By default ``/media/``. 554 | 555 | **STATIC_ROOT** 556 | By default ``abspath(join("/tmp", "{}-static".format(self.APP_SLUG))``. 557 | 558 | 559 | 560 | SecurityMixin 561 | ------------- 562 | 563 | Security base settings. 564 | 565 | .. code-block:: python 566 | 567 | from kaio.mixins import SecurityMixin 568 | 569 | **Section**: Security 570 | 571 | **Parameters** 572 | 573 | **SECRET_KEY** 574 | A secret key for a particular Django installation. 575 | This is used to provide cryptographic signing, and should be set to a unique, unpredictable value. 576 | By default ``''``. 577 | 578 | **ALLOWED_HOSTS** 579 | A list of strings representing the host/domain names that this Django site can serve. 580 | By default ``[]``. 581 | 582 | **SECURE_PROXY_SSL_HEADER_NAME** 583 | user to use 584 | The name of the header to configure the proxy ssl. By default ``HTTP_X_FORWARDED_PROTO`` 585 | 586 | **SECURE_PROXY_SSL_HEADER_VALUE** 587 | The value of the header to configure the proxy ssl. By default ``https`` 588 | 589 | **SECURE_PROXY_SSL_HEADER** 590 | A tuple representing a HTTP header/value combination that signifies a request is secure. 591 | This controls the behavior of the request object’s is_secure() method. 592 | By default returns the tuple of the combination of the ``SECURE_PROXY_SSL_HEADER_NAME`` and ``SECURE_PROXY_SSL_HEADER_VALUE``. 593 | https://docs.djangoproject.com/en/1.10/ref/settings/#secure-proxy-ssl-header 594 | 595 | 596 | StorageMixin 597 | ------------ 598 | 599 | Mixin that provides settings for django-storages. Currently only supports AWS S3. 600 | Look at http://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html for details. 601 | 602 | .. code-block:: python 603 | 604 | from kaio.mixins import StorageMixin 605 | 606 | **Section**: Storage 607 | 608 | **Parameters** 609 | 610 | **DEFAULT_FILE_STORAGE** 611 | By default: ``storages.backends.s3boto3.S3Boto3Storage``. For tests it might be convenient to change it to ``django.core.files.storage.FileSystemStorage``. Only in Django versions < 4.2. 612 | 613 | **DEFAULT_BACKEND_STORAGE** 614 | By default: ``storages.backends.s3boto3.S3Boto3Storage``. For tests it might be convenient to change it to ``django.core.files.storage.FileSystemStorage``. Only in Django versions >= 4.2. 615 | 616 | **STATICFILES_BACKEND_STORAGE** 617 | By default: ``django.contrib.staticfiles.storage.StaticFilesStorage``Only in Django versions >= 4.2. 618 | 619 | **AWS_S3_SIGNATURE_VERSION** 620 | By default ``s3v4``. 621 | 622 | **AWS_S3_REGION_NAME** 623 | By default ``None``. Example: ``eu-west-1``. 624 | 625 | **AWS_S3_ENDPOINT_URL** 626 | By default ``None``. 627 | 628 | **AWS_S3_CUSTOM_DOMAIN** 629 | By default ``None``. 630 | 631 | **AWS_STORAGE_BUCKET_NAME** 632 | By default ``''``. 633 | 634 | **AWS_LOCATION** 635 | By default ``''``. 636 | 637 | **AWS_ACCESS_KEY_ID** 638 | By default ``''``. 639 | 640 | **AWS_SECRET_ACCESS_KEY** 641 | By default ``''``. 642 | 643 | **AWS_QUERYSTRING_AUTH** 644 | By default ``True``. 645 | 646 | **AWS_DEFAULT_ACL** 647 | By default ``private``. 648 | 649 | 650 | WhiteNoiseMixin 651 | --------------- 652 | 653 | Automatic configuration for static serving using whitenoise_. You must have version 3 installed. 654 | 655 | .. _whitenoise: http://whitenoise.evans.io/ 656 | 657 | .. code-block:: python 658 | 659 | from kaio.mixins import WhiteNoiseMixin 660 | 661 | **Parameters** 662 | 663 | **ENABLE_WHITENOISE** 664 | by default ``False``. ``False`` if the module is not installed. 665 | 666 | **WHITENOISE_AUTOREFRESH** 667 | by default ``True``. 668 | 669 | **WHITENOISE_USE_FINDERS** 670 | by default ``True``. 671 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.13 2 | Babel==2.12.1 3 | certifi==2024.7.4 4 | charset-normalizer==3.2.0 5 | colorama==0.4.6 6 | docutils==0.18.1 7 | idna==3.7 8 | imagesize==1.4.1 9 | Jinja2==3.1.6 10 | livereload==2.6.3 11 | MarkupSafe==2.1.3 12 | packaging==23.1 13 | Pygments==2.16.1 14 | requests==2.32.2 15 | six==1.16.0 16 | snowballstemmer==2.2.0 17 | Sphinx==7.2.3 18 | sphinx-autobuild==2021.3.14 19 | sphinx-rtd-theme==1.3.0 20 | sphinxcontrib-applehelp==1.0.7 21 | sphinxcontrib-devhelp==1.0.5 22 | sphinxcontrib-htmlhelp==2.0.4 23 | sphinxcontrib-jquery==4.1 24 | sphinxcontrib-jsmath==1.0.1 25 | sphinxcontrib-qthelp==1.0.6 26 | sphinxcontrib-serializinghtml==1.1.9 27 | tornado==6.4.2 28 | urllib3==2.2.2 29 | -------------------------------------------------------------------------------- /kaio/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .options import Options # noqa 4 | 5 | 6 | __VERSION__ = '1.6.0' 7 | -------------------------------------------------------------------------------- /kaio/debug_toolbar.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | def show_toolbar(request): 4 | """ 5 | Function to determine whether to show the toolbar on a given page. 6 | """ 7 | return settings.DEBUG and \ 8 | settings.ENABLE_DEBUG_TOOLBAR and \ 9 | ((request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS) or is_host_debug_toolbar_allowed( 10 | request.get_host())) 11 | 12 | 13 | def is_host_debug_toolbar_allowed(host): 14 | for allowed_host in settings.ALLOWED_HOSTS_DEBUG_TOOLBAR: 15 | if host.endswith(allowed_host): 16 | return True 17 | return False -------------------------------------------------------------------------------- /kaio/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APSL/django-kaio/917ed4617aa9639c5ba7672f11c7f3f08ad130df/kaio/management/__init__.py -------------------------------------------------------------------------------- /kaio/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APSL/django-kaio/917ed4617aa9639c5ba7672f11c7f3f08ad130df/kaio/management/commands/__init__.py -------------------------------------------------------------------------------- /kaio/management/commands/generate_ini.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @author: bcabezas@apsl.net 3 | 4 | import sys 5 | 6 | from clint.textui import puts, colored 7 | try: 8 | from django.core.management.base import NoArgsCommand 9 | except ImportError: 10 | from django.core.management import BaseCommand as NoArgsCommand 11 | 12 | from kaio import Options 13 | 14 | 15 | def module_to_dict(module, omittable=lambda k: k.startswith('_')): 16 | """ 17 | Converts a module namespace to a Python dictionary. Used by get_settings_diff. 18 | """ 19 | return dict([(k, repr(v)) for k, v in module.__dict__.items() if not omittable(k)]) 20 | 21 | 22 | class Command(NoArgsCommand): 23 | help = """Print a .ini with default values in stdout.""" 24 | 25 | requires_model_validation = False 26 | 27 | def handle_noargs(self, **options): 28 | # Inspired by Postfix's "postconf -n". 29 | from django.conf import settings 30 | 31 | # Because settings are imported lazily, we need to explicitly load them. 32 | settings._setup() 33 | 34 | user_settings = module_to_dict(settings._wrapped) 35 | 36 | opts = Options() 37 | pformat = "%-25s = %s" 38 | puts('') 39 | for section in opts.sections: 40 | puts(colored.green("[%s]" % section)) 41 | for key, kaio_value in opts.items(section): 42 | keycolor = colored.magenta(key) 43 | if key in user_settings: 44 | keycolor = colored.blue(key) 45 | 46 | default_value = opts.options[key].default_value 47 | value = kaio_value or default_value 48 | 49 | if sys.version_info[0] < 3: 50 | value = unicode(value).encode('utf8') # noqa: F821 51 | else: 52 | value = str(value) 53 | 54 | try: 55 | puts(pformat % (keycolor, value)) 56 | except Exception as e: 57 | raise e 58 | puts('') 59 | 60 | def handle(self, **options): 61 | return self.handle_noargs(**options) 62 | -------------------------------------------------------------------------------- /kaio/management/commands/kaiosettings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # bcabezas@apsl.net 3 | 4 | try: 5 | from django.core.management.base import NoArgsCommand 6 | except ImportError: 7 | from django.core.management import BaseCommand as NoArgsCommand 8 | 9 | from kaio import Options 10 | 11 | 12 | def module_to_dict(module, omittable=lambda k: k.startswith('_')): 13 | "Converts a module namespace to a Python dictionary. Used by get_settings_diff." 14 | return dict([(k, repr(v)) for k, v in module.__dict__.items() if not omittable(k)]) 15 | 16 | 17 | def clint_encode(value): 18 | """clint puts crashess when value is unicode. so we always encode""" 19 | return value 20 | 21 | 22 | class Command(NoArgsCommand): 23 | help = """Displays differences between the current settings.py and Django's 24 | default settings. Settings that don't appear in the defaults are 25 | followed by "###".""" 26 | 27 | requires_model_validation = False 28 | 29 | def handle_noargs(self, **options): 30 | # Inspired by Postfix's "postconf -n". 31 | from django.conf import settings, global_settings 32 | 33 | # Because settings are imported lazily, we need to explicitly load them. 34 | settings._setup() 35 | 36 | user_settings = module_to_dict(settings._wrapped) 37 | default_settings = module_to_dict(global_settings) 38 | 39 | opts = Options() 40 | from clint.textui import puts, colored 41 | pformat = "%30s: %-30s %-30s %-30s" 42 | puts('') 43 | puts(pformat % ( 44 | colored.white('Option'), 45 | colored.cyan('APP Value'), 46 | colored.cyan('INI Value'), 47 | colored.green('APP Default'))) 48 | puts('') 49 | for section in opts.sections: 50 | puts(pformat % (colored.green("[%s]" % section), '', '', '')) 51 | for key, kaio_value in opts.items(section): 52 | keycolor = colored.magenta(key) 53 | if key in user_settings: 54 | value = colored.green(user_settings[key]) 55 | keycolor = colored.blue(key) 56 | else: 57 | value = colored.green(opts.options[key].get_value_or_default()) 58 | 59 | default_value = opts.options[key].default_value 60 | kaio_value = kaio_value if kaio_value else repr(kaio_value) 61 | 62 | puts(pformat % ( 63 | keycolor, 64 | clint_encode(value), 65 | colored.white(clint_encode(kaio_value)), 66 | clint_encode(default_value))) 67 | 68 | puts('') 69 | 70 | puts(colored.white("No configurables directamente en INI (estáticos o compuestos por otros):")) 71 | puts() 72 | 73 | not_configured = set(user_settings.keys()) - set(opts.keys()) 74 | # not_configured = not_configured - set([ 75 | # 'INSTALLED_APPS', 76 | # 'MIDDLEWARE_CLASSES', 77 | # 'CONTEXT_PROCESSORS', 78 | # ]) 79 | pformat = "%30s: %-50s" 80 | puts(pformat % ( 81 | colored.white('Option'), 82 | colored.cyan('Value'))) 83 | for key in sorted(not_configured): 84 | if key not in default_settings: 85 | puts(pformat % (colored.blue(key), 86 | user_settings[key])) 87 | elif user_settings[key] != default_settings[key]: 88 | puts(pformat % ( 89 | colored.blue(key), 90 | colored.green(user_settings[key]) 91 | # colored.white(default_settings[key]) 92 | )) 93 | 94 | def handle(self, **options): 95 | return self.handle_noargs(**options) 96 | -------------------------------------------------------------------------------- /kaio/mixins/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .database import DatabasesMixin # noqa: F401 4 | from .cache import CachesMixin # noqa: F401 5 | from .compress import CompressMixin # noqa: F401 6 | from .paths import PathsMixin # noqa: F401 7 | from .logs import LogsMixin # noqa: F401 8 | from .filerconf import FilerMixin # noqa: F401 9 | from .cms import CMSMixin # noqa: F401 10 | from .security import SecurityMixin # noqa: F401 11 | from .debug import DebugMixin # noqa: F401 12 | from .celeryconf import CeleryMixin # noqa: F401 13 | from .email import EmailMixin # noqa: F401 14 | from .sentry import SentryMixin # noqa: F401 15 | from .storage import StorageMixin # noqa: F401 16 | from .whitenoise import WhiteNoiseMixin # noqa: F401 17 | -------------------------------------------------------------------------------- /kaio/mixins/cache.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kaio import Options 4 | from functools import partial 5 | 6 | opts = Options() 7 | get = partial(opts.get, section='Cache') 8 | 9 | 10 | class CachesMixin(object): 11 | 12 | # Settings for default cache. 13 | 14 | @property 15 | def CACHE_TYPE(self): 16 | return get('CACHE_TYPE', 'locmem') 17 | 18 | @property 19 | def REDIS_HOST(self): 20 | return get('REDIS_HOST', 'localhost') 21 | 22 | @property 23 | def REDIS_PORT(self): 24 | return get('REDIS_PORT', 6379) 25 | 26 | @property 27 | def CACHE_REDIS_DB(self): 28 | return get('CACHE_REDIS_DB', 2) 29 | 30 | @property 31 | def CACHE_REDIS_PASSWORD(self): 32 | return get('CACHE_REDIS_PASSWORD', None) 33 | 34 | @property 35 | def CACHE_PREFIX(self): 36 | return get('CACHE_PREFIX', self.APP_SLUG) 37 | 38 | @property 39 | def CACHE_TIMEOUT(self): 40 | return get('CACHE_TIMEOUT', 3600) 41 | 42 | @property 43 | def CACHE_MAX_ENTRIES(self): 44 | return get('CACHE_MAX_ENTRIES', 10000) 45 | 46 | @property 47 | def DEFAULT_CACHE(self): 48 | if self.CACHE_TYPE == 'redis': 49 | CACHE = { 50 | 'BACKEND': 'django_redis.cache.RedisCache', 51 | 'LOCATION': 'redis://%s:%s/%s' % (self.REDIS_HOST, 52 | self.REDIS_PORT, 53 | self.CACHE_REDIS_DB), 54 | 'KEY_PREFIX': self.CACHE_PREFIX, 55 | 'TIMEOUT': self.CACHE_TIMEOUT, 56 | 'OPTIONS': { 57 | "CLIENT_CLASS": "django_redis.client.DefaultClient", 58 | 'MAX_ENTRIES': self.CACHE_MAX_ENTRIES, 59 | }, 60 | } 61 | if self.CACHE_REDIS_PASSWORD is not None: 62 | CACHE['OPTIONS']['PASSWORD'] = self.CACHE_REDIS_PASSWORD 63 | elif self.CACHE_TYPE == 'locmem': 64 | CACHE = { 65 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 66 | 'CACHE_PREFIX': self.CACHE_PREFIX, 67 | 'LOCATION': 'unique-key-apsl' 68 | } 69 | else: 70 | CACHE = { 71 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 72 | } 73 | 74 | return CACHE 75 | 76 | # Settings for session cache. 77 | # You must set SESSION_ENGINE = 'django.contrib.sessions.backends.cache' 78 | # (or cached_db). By default use almost same settings as default cache. 79 | 80 | @property 81 | def SESSION_CACHE_TYPE(self): 82 | return get('SESSION_CACHE_TYPE', self.CACHE_TYPE) 83 | 84 | @property 85 | def SESSION_REDIS_HOST(self): 86 | return get('SESSION_REDIS_HOST', self.REDIS_HOST) 87 | 88 | @property 89 | def SESSION_REDIS_PORT(self): 90 | return get('SESSION_REDIS_PORT', self.REDIS_PORT) 91 | 92 | @property 93 | def SESSION_CACHE_REDIS_DB(self): 94 | return get('SESSION_CACHE_REDIS_DB', 3) 95 | 96 | @property 97 | def SESSION_CACHE_REDIS_PASSWORD(self): 98 | return get('SESSION_CACHE_REDIS_PASSWORD', self.CACHE_REDIS_PASSWORD) 99 | 100 | @property 101 | def SESSION_CACHE_PREFIX(self): 102 | return get('SESSION_CACHE_PREFIX', '%s_session' % self.CACHE_PREFIX) 103 | 104 | @property 105 | def SESSION_CACHE_TIMEOUT(self): 106 | return get('SESSION_CACHE_TIMEOUT', None) 107 | 108 | @property 109 | def SESSION_CACHE_MAX_ENTRIES(self): 110 | return get('SESSION_CACHE_MAX_ENTRIES', 1000000) 111 | 112 | @property 113 | def SESSION_CACHE(self): 114 | # Support for Redis only 115 | if self.SESSION_CACHE_TYPE != 'redis' or self.SESSION_ENGINE not in ( 116 | 'django.contrib.sessions.backends.cache', 117 | 'django.contrib.sessions.backends.cached_db'): 118 | return 119 | 120 | CACHE = { 121 | 'BACKEND': 'django_redis.cache.RedisCache', 122 | 'LOCATION': 'redis://%s:%s/%s' % (self.SESSION_REDIS_HOST, 123 | self.SESSION_REDIS_PORT, 124 | self.SESSION_CACHE_REDIS_DB), 125 | 'KEY_PREFIX': self.SESSION_CACHE_PREFIX, 126 | 'TIMEOUT': self.SESSION_CACHE_TIMEOUT, 127 | 'OPTIONS': { 128 | "CLIENT_CLASS": "django_redis.client.DefaultClient", 129 | 'MAX_ENTRIES': self.SESSION_CACHE_MAX_ENTRIES, 130 | }, 131 | } 132 | 133 | if self.SESSION_CACHE_REDIS_PASSWORD is not None: 134 | CACHE['OPTIONS']['PASSWORD'] = self.SESSION_CACHE_REDIS_PASSWORD 135 | 136 | return CACHE 137 | 138 | @property 139 | def SESSION_CACHE_ALIAS(self): 140 | return get('SESSION_CACHE_ALIAS', 'session') 141 | 142 | # Main cache settings. 143 | 144 | @property 145 | def CACHES(self): 146 | caches = {'default': self.DEFAULT_CACHE} 147 | session_cache = self.SESSION_CACHE 148 | if session_cache: 149 | caches[self.SESSION_CACHE_ALIAS] = session_cache 150 | return caches 151 | -------------------------------------------------------------------------------- /kaio/mixins/celeryconf.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | import logging 3 | from kaio import Options 4 | 5 | 6 | SUPPORTED_BROKER_TYPES = ['redis', 'rabbitmq'] 7 | DEFAULT_BROKER_TYPE = 'redis' 8 | DEFAULT_BROKER_URL = 'django://' # used if cannot setup redis or rabbitmq 9 | 10 | 11 | opts = Options() 12 | get = partial(opts.get, section='Celery') 13 | 14 | log = logging.getLogger(__name__) 15 | 16 | 17 | class CeleryMixin(object): 18 | """Celery APSL Custom mixin""" 19 | 20 | CELERY_DISABLE_RATE_LIMITS = True 21 | CELERYBEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' 22 | 23 | def _redis_available(self): 24 | try: 25 | import redis # noqa: F401 26 | except ImportError: 27 | return False 28 | 29 | if not self.REDIS_PORT or not self.REDIS_HOST: 30 | return False 31 | 32 | return True 33 | 34 | @property 35 | def CELERY_DEFAULT_QUEUE(self): 36 | return get('CELERY_DEFAULT_QUEUE', 'celery') 37 | 38 | @property 39 | def CELERY_RESULT_BACKEND(self): 40 | """Redis result backend config""" 41 | 42 | # allow specify directly 43 | configured = get('CELERY_RESULT_BACKEND', None) 44 | if configured: 45 | return configured 46 | 47 | if not self._redis_available(): 48 | return None 49 | 50 | host, port = self.REDIS_HOST, self.REDIS_PORT 51 | if host and port: 52 | return "redis://{host}:{port}/{db}".format( 53 | host=host, 54 | port=port, 55 | db=self.CELERY_REDIS_RESULT_DB, 56 | ) 57 | 58 | @property 59 | def CELERY_IGNORE_RESULT(self): 60 | """Whether to store the task return values or not (tombstones)""" 61 | return get('CELERY_IGNORE_RESULT', False) 62 | 63 | @property 64 | def CELERY_RESULT_EXPIRES(self): 65 | return get('CELERY_RESULT_EXPIRES', 86400) # 1 day in seconds 66 | 67 | @property 68 | def CELERY_MAX_CACHED_RESULTS(self): 69 | """This is the total number of results to cache before older results 70 | are evicted. The default is 5000.""" 71 | 72 | return get('CELERY_MAX_CACHED_RESULTS', 5000) 73 | 74 | @property 75 | def CELERY_CACHE_BACKEND(self): 76 | return get('CELERY_CACHE_BACKEND', 'default') 77 | 78 | @property 79 | def CELERY_ALWAYS_EAGER(self): 80 | return get('CELERY_ALWAYS_EAGER', False) 81 | 82 | @property 83 | def CELERY_EAGER_PROPAGATES_EXCEPTIONS(self): 84 | return get('CELERY_EAGER_PROPAGATES_EXCEPTIONS', True) 85 | 86 | @property 87 | def CELERY_REDIS_RESULT_DB(self): 88 | try: 89 | return int(get('CELERY_REDIS_RESULT_DB', 0)) 90 | except Exception: 91 | return 0 92 | 93 | @property 94 | def CELERY_REDIS_BROKER_DB(self): 95 | try: 96 | return int(get('CELERY_REDIS_BROKER_DB', 0)) 97 | except ValueError: 98 | return 0 99 | 100 | @property 101 | def RABBITMQ_HOST(self): 102 | return get('RABBITMQ_HOST', 'localhost') 103 | 104 | @property 105 | def RABBITMQ_PORT(self): 106 | return get('RABBITMQ_PORT', 5672) 107 | 108 | @property 109 | def RABBITMQ_USER(self): 110 | return get('RABBITMQ_USER', 'guest') 111 | 112 | @property 113 | def RABBITMQ_PASSWD(self): 114 | return get('RABBITMQ_PASSWD', 'guest') 115 | 116 | @property 117 | def RABBITMQ_VHOST(self): 118 | return get('RABBITMQ_VHOST', '/') 119 | 120 | @property 121 | def BROKER_TYPE(self): 122 | """Custom setting allowing switch between rabbitmq, redis""" 123 | 124 | broker_type = get('BROKER_TYPE', DEFAULT_BROKER_TYPE) 125 | if broker_type not in SUPPORTED_BROKER_TYPES: 126 | log.warn("Specified BROKER_TYPE {} not supported. Backing to default {}".format( 127 | broker_type, DEFAULT_BROKER_TYPE)) 128 | return DEFAULT_BROKER_TYPE 129 | else: 130 | return broker_type 131 | 132 | @property 133 | def BROKER_URL(self): 134 | """Sets BROKER_URL depending on redis or rabbitmq settings""" 135 | 136 | # also allow specify broker_url 137 | broker_url = get('BROKER_URL', None) 138 | if broker_url: 139 | log.info("Using BROKER_URL setting: {}".format(broker_url)) 140 | return broker_url 141 | 142 | redis_available = self._redis_available() 143 | broker_type = self.BROKER_TYPE 144 | if broker_type == 'redis' and not redis_available: 145 | log.warn("Choosed broker type is redis, but redis not available. \ 146 | Check redis package, and REDIS_HOST, REDIS_PORT settings") 147 | 148 | if broker_type == 'redis' and redis_available: 149 | return 'redis://{host}:{port}/{db}'.format( 150 | host=self.REDIS_HOST, 151 | port=self.REDIS_PORT, 152 | db=self.CELERY_REDIS_BROKER_DB) 153 | elif broker_type == 'rabbitmq': 154 | return 'amqp://{user}:{passwd}@{host}:{port}/{vhost}'.format( 155 | user=self.RABBITMQ_USER, 156 | passwd=self.RABBITMQ_PASSWD, 157 | host=self.RABBITMQ_HOST, 158 | port=self.RABBITMQ_PORT, 159 | vhost=self.RABBITMQ_VHOST) 160 | else: 161 | return DEFAULT_BROKER_URL 162 | -------------------------------------------------------------------------------- /kaio/mixins/cms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kaio import Options 4 | from functools import partial 5 | 6 | opts = Options() 7 | get = partial(opts.get, section='CMS') 8 | 9 | 10 | class CMSMixin(object): 11 | 12 | CMS_SEO_FIELDS = True 13 | CMS_REDIRECTS = True 14 | CMS_SOFTROOT = False 15 | CMS_TEMPLATE_INHERITANCE = True 16 | CMS_MENU_TITLE_OVERWRITE = True 17 | CMS_USE_TINYMCE = False 18 | CMS_PERMISSION = True 19 | 20 | @property 21 | def CMS_LANGUAGES(self): 22 | langs_list = [{ 23 | 'code': code, 24 | 'name': name, 25 | 'hide_untranslated': code == self.LANGUAGE_CODE, 26 | 'redirect_on_fallback': not (code == self.LANGUAGE_CODE), 27 | } for code, name in self.LANGUAGES] 28 | 29 | return { 30 | self.SITE_ID: langs_list, 31 | 'default': { 32 | 'fallbacks': [self.LANGUAGE_CODE, ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kaio/mixins/compress.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from configurations import Configuration 4 | from kaio import Options 5 | from functools import partial 6 | 7 | opts = Options() 8 | get = partial(opts.get, section='Compress') 9 | 10 | 11 | class CompressMixin(object): 12 | 13 | STATICFILES_FINDERS = list(Configuration.STATICFILES_FINDERS) + [ 14 | "compressor.finders.CompressorFinder", 15 | ] 16 | 17 | @property 18 | def COMPRESS_ENABLED(self): 19 | return get('COMPRESS_ENABLED', False) 20 | 21 | @property 22 | def COMPRESS_CSS_HASHING_METHOD(self): 23 | return get('COMPRESS_CSS_HASHING_METHOD', 'content') 24 | 25 | @property 26 | def COMPRESS_DEBUG_TOGGLE(self): 27 | if self.DEBUG: 28 | return 'nocompress' 29 | return None 30 | 31 | @property 32 | def COMPRESS_LESSC_ENABLED(self): 33 | return get('COMPRESS_LESSC_ENABLED', True) 34 | 35 | @property 36 | def COMPRESS_BABEL_ENABLED(self): 37 | return get('COMPRESS_BABEL_ENABLED', True) 38 | 39 | @property 40 | def COMPRESS_SASS_ENABLED(self): 41 | return get('COMPRESS_SASS_ENABLED', True) 42 | 43 | @property 44 | def COMPRESS_LESSC_PATH(self): 45 | return get('COMPRESS_LESSC_PATH', 'lessc') 46 | 47 | @property 48 | def COMPRESS_BABEL_PATH(self): 49 | return get('COMPRESS_BABEL_PATH', 'babel') 50 | 51 | @property 52 | def COMPRESS_SASS_PATH(self): 53 | return get('COMPRESS_SASS_PATH', 'node-sass') 54 | 55 | @property 56 | def COMPRESS_PRECOMPILERS(self): 57 | precompilers = [] 58 | if self.COMPRESS_LESSC_ENABLED: 59 | precompilers.append(('text/less', self.COMPRESS_LESSC_PATH + ' {infile} {outfile}')) 60 | if self.COMPRESS_BABEL_ENABLED: 61 | precompilers.append(('text/babel', self.COMPRESS_BABEL_PATH + ' {infile} -o {outfile}')) 62 | if self.COMPRESS_SASS_ENABLED: 63 | precompilers.append(('text/sass', self.COMPRESS_SASS_PATH + ' {infile} {outfile}')) 64 | precompilers.append(('text/scss', self.COMPRESS_SASS_PATH + ' {infile} {outfile}')) 65 | return precompilers 66 | 67 | # offline settings 68 | # http://django-compressor.readthedocs.org/en/latest/settings/#offline-settings 69 | 70 | @property 71 | def COMPRESS_OFFLINE(self): 72 | return get('COMPRESS_OFFLINE', False) 73 | 74 | @property 75 | def COMPRESS_OFFLINE_TIMEOUT(self): 76 | return get('COMPRESS_OFFLINE_TIMEOUT', 31536000) # 1 year in seconds 77 | 78 | @property 79 | def COMPRESS_OFFLINE_MANIFEST(self): 80 | return get('COMPRESS_OFFLINE_MANIFEST', 'manifest.json') 81 | 82 | def COMPRESS_OUTPUT_DIR(self): 83 | if not self.COMPRESS_ENABLED and self.COMPRESS_LESSC_ENABLED: 84 | return '' 85 | else: 86 | return get('COMPRESS_OUTPUT_DIR', 'CACHE/') 87 | -------------------------------------------------------------------------------- /kaio/mixins/database.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from functools import partial 4 | from kaio import Options 5 | 6 | 7 | opts = Options() 8 | get = partial(opts.get, section='Database') 9 | 10 | 11 | class DatabasesMixin(object): 12 | 13 | @staticmethod 14 | def get_engine(prefix): 15 | """ 16 | Retrieve the database engine. 17 | Only change the full engine string if there is no «backends» in it. 18 | """ 19 | engine = get('{}DATABASE_ENGINE'.format(prefix), 'sqlite3') 20 | if 'backends' in engine: 21 | return engine 22 | return 'django.db.backends.' + engine 23 | 24 | def get_databases(self, prefix=''): 25 | databases = { 26 | 'default': { 27 | 'ENGINE': self.get_engine(prefix), 28 | 'NAME': get('{}DATABASE_NAME'.format(prefix), '{}db.sqlite'.format(prefix.lower())), 29 | 'USER': get('{}DATABASE_USER'.format(prefix), None), 30 | 'PASSWORD': get('{}DATABASE_PASSWORD'.format(prefix), ''), 31 | 'HOST': get('{}DATABASE_HOST'.format(prefix), ''), 32 | 'PORT': get('{}DATABASE_PORT'.format(prefix), ''), 33 | 'CONN_MAX_AGE': get('{}DATABASE_CONN_MAX_AGE'.format(prefix), 0), 34 | 'TEST': { 35 | 'NAME': get('{}DATABASE_NAME'.format(prefix), None), 36 | } 37 | } 38 | } 39 | 40 | options = get('DATABASE_OPTIONS_OPTIONS') 41 | if options: 42 | databases['default']['OPTIONS'] = {'options': options} 43 | 44 | return databases 45 | 46 | def DATABASES(self): 47 | return self.get_databases() 48 | -------------------------------------------------------------------------------- /kaio/mixins/debug.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from functools import partial 4 | 5 | from kaio import Options 6 | 7 | 8 | opts = Options() 9 | get = partial(opts.get, section='Debug') 10 | 11 | 12 | class DebugMixin(object): 13 | """Debug base settings""" 14 | 15 | @property 16 | def DEBUG(self): 17 | return get('DEBUG', False) 18 | 19 | @property 20 | def TEMPLATE_DEBUG(self): 21 | debug = get('TEMPLATE_DEBUG', self.DEBUG) 22 | for template in self.TEMPLATES: 23 | if template['BACKEND'] == 'django.template.backends.django.DjangoTemplates': 24 | template['OPTIONS']['debug'] = debug 25 | 26 | # https://django-debug-toolbar.readthedocs.io/en/stable/installation.html#explicit-setup 27 | DEBUG_TOOLBAR_PATCH_SETTINGS = False 28 | DEBUG_TOOLBAR_MIDDLEWARE = 'debug_toolbar.middleware.DebugToolbarMiddleware' 29 | 30 | @property 31 | def ENABLE_DEBUG_TOOLBAR(self): 32 | enabled = get('ENABLE_DEBUG_TOOLBAR', self.DEBUG) 33 | if enabled: 34 | try: 35 | import debug_toolbar # noqa: F401 36 | except ImportError: 37 | return False 38 | else: 39 | self._add_debug_toolbar_to_installed_apps() 40 | self._add_debug_toolbar_to_middleware() 41 | 42 | return enabled 43 | 44 | @property 45 | def INTERNAL_IPS(self): 46 | ips = [ip.strip() for ip in get('INTERNAL_IPS', '127.0.0.1').split(',') if ip] 47 | # For Docker: https://django-debug-toolbar.readthedocs.io/en/stable/installation.html#configure-internal-ips 48 | if self.ENABLE_DEBUG_TOOLBAR: 49 | import socket 50 | _hostname, _aliases, docker_ips = socket.gethostbyname_ex(socket.gethostname()) 51 | ips += [ip[:-1] + '1' for ip in docker_ips] 52 | return ips 53 | 54 | @property 55 | def ALLOWED_HOSTS_DEBUG_TOOLBAR(self): 56 | if self.ENABLE_DEBUG_TOOLBAR: 57 | return get("ALLOWED_HOSTS_DEBUG_TOOLBAR", "").split(",") 58 | return [] 59 | 60 | def _add_debug_toolbar_to_installed_apps(self): 61 | if 'debug_toolbar' not in self.INSTALLED_APPS: 62 | self.INSTALLED_APPS.append('debug_toolbar') 63 | 64 | def _add_debug_toolbar_to_middleware(self): 65 | middlewares_settings = ( 66 | 'MIDDLEWARE', # django >= 1.10 67 | 'MIDDLEWARE_CLASSES', # django < 1.10 68 | ) 69 | for middleware_setting in middlewares_settings: 70 | middlewares = getattr(self, middleware_setting, None) 71 | if middlewares is not None: 72 | if self.DEBUG_TOOLBAR_MIDDLEWARE not in middlewares: 73 | middlewares.insert(0, self.DEBUG_TOOLBAR_MIDDLEWARE) 74 | -------------------------------------------------------------------------------- /kaio/mixins/email.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | import os 5 | from kaio import Options 6 | from functools import partial 7 | 8 | 9 | logger = logging.getLogger(__name__) 10 | opts = Options() 11 | get = partial(opts.get, section='Email') 12 | 13 | 14 | class EmailMixin(object): 15 | """Settings para enviar emails""" 16 | 17 | # Django settings: https://docs.djangoproject.com/en/1.11/ref/settings/#email-backend 18 | 19 | @property 20 | def DEFAULT_FROM_EMAIL(self): 21 | return get('DEFAULT_FROM_EMAIL', 'Example ') 22 | 23 | @property 24 | def EMAIL_BACKEND(self): 25 | backend = get('EMAIL_BACKEND') 26 | if backend: 27 | return backend 28 | 29 | if 'django_yubin' not in self.INSTALLED_APPS: 30 | return 'django.core.mail.backends.smtp.EmailBackend' 31 | 32 | try: 33 | import django_yubin # type: ignore # noqa 34 | except ImportError: 35 | logger.warn('WARNING: django_yubin in INSTALLED_APPS but not pip installed.') 36 | return 'django.core.mail.backends.smtp.EmailBackend' 37 | 38 | try: 39 | from django_yubin.version import VERSION # type: ignore # noqa 40 | if VERSION[0] > 1: 41 | return 'django_yubin.backends.QueuedEmailBackend' 42 | else: 43 | return 'django_yubin.smtp_queue.EmailBackend' 44 | except Exception: 45 | return 'django_yubin.smtp_queue.EmailBackend' 46 | 47 | 48 | @property 49 | def EMAIL_FILE_PATH(self): 50 | return get('EMAIL_FILE_PATH', None) 51 | 52 | @property 53 | def EMAIL_HOST(self): 54 | return get('EMAIL_HOST', 'localhost') 55 | 56 | @property 57 | def EMAIL_HOST_PASSWORD(self): 58 | return get('EMAIL_HOST_PASSWORD', '') 59 | 60 | @property 61 | def EMAIL_HOST_USER(self): 62 | return get('EMAIL_HOST_USER', '') 63 | 64 | @property 65 | def EMAIL_PORT(self): 66 | return get('EMAIL_PORT', 25) 67 | 68 | @property 69 | def EMAIL_SUBJECT_PREFIX(self): 70 | return get('EMAIL_SUBJECT_PREFIX', '[Django] ') 71 | 72 | @property 73 | def EMAIL_USE_TLS(self): 74 | return get('EMAIL_USE_TLS', False) 75 | 76 | # django-yubin settings: http://django-yubin.readthedocs.org/en/latest/settings.html 77 | 78 | @property 79 | def MAILER_PAUSE_SEND(self): 80 | return get('MAILER_PAUSE_SEND', False) 81 | 82 | @property 83 | def MAILER_USE_BACKEND(self): 84 | return get('MAILER_USE_BACKEND', 'django.core.mail.backends.smtp.EmailBackend') 85 | 86 | @property 87 | def MAILER_HC_QUEUED_LIMIT_OLD(self): 88 | return get('MAILER_HC_QUEUED_LIMIT_OLD', 30) 89 | 90 | @property 91 | def MAILER_STORAGE_BACKEND(self): 92 | return get('MAILER_STORAGE_BACKEND', "django_yubin.storage_backends.DatabaseStorageBackend") 93 | 94 | @property 95 | def MAILER_STORAGE_DELETE(self): 96 | return get('MAILER_STORAGE_DELETE', True) 97 | 98 | @property 99 | def MAILER_FILE_STORAGE_DIR(self): 100 | return get('MAILER_FILE_STORAGE_DIR', "yubin") 101 | 102 | 103 | # deprecated, for backwards compatibility 104 | 105 | @property 106 | def MAILER_MAIL_ADMINS_PRIORITY(self): 107 | try: 108 | from django_yubin import constants 109 | priority = constants.PRIORITY_HIGH 110 | except Exception: 111 | priority = 1 112 | return get('MAILER_MAIL_ADMINS_PRIORITY', priority) 113 | 114 | @property 115 | def MAILER_MAIL_MANAGERS_PRIORITY(self): 116 | return get('MAILER_MAIL_MANAGERS_PRIORITY', None) 117 | 118 | @property 119 | def MAILER_EMPTY_QUEUE_SLEEP(self): 120 | return get('MAILER_EMPTY_QUEUE_SLEEP', 30) 121 | 122 | @property 123 | def MAILER_LOCK_WAIT_TIMEOUT(self): 124 | return get('MAILER_LOCK_WAIT_TIMEOUT', 0) 125 | 126 | @property 127 | def MAILER_LOCK_PATH(self): 128 | return get("MAILER_LOCK_PATH", os.path.join(self.APP_ROOT, "send_mail")) 129 | -------------------------------------------------------------------------------- /kaio/mixins/filerconf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from os.path import join, abspath 4 | from kaio import Options 5 | from configurations import Configuration 6 | from functools import partial 7 | 8 | opts = Options() 9 | get = partial(opts.get, section='Filer') 10 | 11 | 12 | class FilerMixin(object): 13 | """Settings para django-filer y easy_thumbnails""" 14 | 15 | THUMBNAIL_PROCESSORS = ( 16 | 'easy_thumbnails.processors.colorspace', 17 | 'easy_thumbnails.processors.autocrop', 18 | # 'easy_thumbnails.processors.scale_and_crop', 19 | 'filer.thumbnail_processors.scale_and_crop_with_subject_location', 20 | 'easy_thumbnails.processors.filters', 21 | ) 22 | 23 | @property 24 | def FILER_IS_PUBLIC_DEFAULT(self): 25 | return get('FILER_IS_PUBLIC_DEFAULT', True) 26 | 27 | @property 28 | def FILER_ENABLE_PERMISSIONS(self): 29 | return get('FILER_ENABLE_PERMISSIONS', False) 30 | 31 | @property 32 | def FILER_DEBUG(self): 33 | return get('FILER_DEBUG', False) 34 | 35 | @property 36 | def FILER_ENABLE_LOGGING(self): 37 | return get('FILER_ENABLE_LOGGING', False) 38 | 39 | @property 40 | def FILER_0_8_COMPATIBILITY_MODE(self): 41 | get('FILER_0_8_COMPATIBILITY_MODE', False) 42 | 43 | @property 44 | def THUMBNAIL_DEBUG(self): 45 | return get('THUBMNAIL_DEBUG', False) 46 | 47 | @property 48 | def THUMBNAIL_QUALITY(self): 49 | return get('THUMBNAIL_QUALITY', 85) 50 | 51 | @property 52 | def FILER_CUSTOM_NGINX_SERVER(self): 53 | """If true will serve secure file trough XNginxXAccelRedirectServer""" 54 | return get('FILER_CUSTOM_NGINX_SERVER', False) 55 | 56 | @property 57 | def default_file_storage(self): 58 | """Common storage for filer configs""" 59 | return getattr( 60 | Configuration, 'DEFAULT_FILE_STORAGE', 61 | 'django.core.files.storage.FileSystemStorage') 62 | 63 | @property 64 | def FILER_CUSTOM_SECURE_MEDIA_ROOT(self): 65 | """Secure media root 66 | As in filer settings, defaults to MEDIA_ROOT/../smedia""" 67 | return opts.get( 68 | 'FILER_CUSTOM_SECURE_MEDIA_ROOT', 69 | abspath(join(self.MEDIA_ROOT, '..', 'smedia'))) 70 | 71 | @property 72 | def filer_private_files_path(self): 73 | return abspath( 74 | join( 75 | self.FILER_CUSTOM_SECURE_MEDIA_ROOT, 76 | 'filer_private' 77 | )) 78 | 79 | @property 80 | def filer_private_thumbnails_path(self): 81 | return abspath( 82 | join( 83 | self.FILER_CUSTOM_SECURE_MEDIA_ROOT, 84 | 'filer_private_thumbnails')) 85 | 86 | @property 87 | def FILER_SERVERS(self): 88 | """Filer config to be served from XNginxXAccelRedirectServer 89 | see http://django-filer.readthedocs.org/en/0.9.4/secure_downloads.html#secure-downloads 90 | """ 91 | if not self.FILER_CUSTOM_NGINX_SERVER: 92 | return {} 93 | else: 94 | return { 95 | 'private': { 96 | 'main': { 97 | 'ENGINE': 'filer.server.backends.nginx.NginxXAccelRedirectServer', 98 | 'OPTIONS': { 99 | 'location': self.filer_private_files_path, 100 | 'nginx_location': '/nginx_filer_private', 101 | }, 102 | }, 103 | 'thumbnails': { 104 | 'ENGINE': 'filer.server.backends.nginx.NginxXAccelRedirectServer', 105 | 'OPTIONS': { 106 | 'location': self.filer_private_thumbnails_path, 107 | 'nginx_location': '/nginx_filer_private_thumbnails', 108 | }, 109 | }, 110 | }, 111 | } 112 | 113 | @property 114 | def FILER_STORAGES(self): 115 | """Filer config to set custom private media path 116 | http://django-filer.readthedocs.org/en/0.9.4/settings.html#filer-storages 117 | """ 118 | if not self.FILER_CUSTOM_NGINX_SERVER: 119 | return {} 120 | 121 | return { 122 | 'public': { 123 | 'main': { 124 | 'ENGINE': self.default_file_storage, 125 | 'OPTIONS': {}, 126 | 'UPLOAD_TO': 'filer.utils.generate_filename.by_date', 127 | 'UPLOAD_TO_PREFIX': 'filer_public', 128 | }, 129 | 'thumbnails': { 130 | 'ENGINE': self.default_file_storage, 131 | 'OPTIONS': {}, 132 | 'THUMBNAIL_OPTIONS': { 133 | 'base_dir': 'filer_public_thumbnails', 134 | }, 135 | }, 136 | }, 137 | 'private': { 138 | 'main': { 139 | 'ENGINE': 'filer.storage.PrivateFileSystemStorage', 140 | 'OPTIONS': { 141 | 'location': self.filer_private_files_path, 142 | 'base_url': '/smedia/filer_private/', 143 | }, 144 | 'UPLOAD_TO': 'filer.utils.generate_filename.by_date', 145 | 'UPLOAD_TO_PREFIX': '', 146 | }, 147 | 'thumbnails': { 148 | 'ENGINE': 'filer.storage.PrivateFileSystemStorage', 149 | 'OPTIONS': { 150 | 'location': self.filer_private_thumbnails_path, 151 | 'base_url': '/smedia/filer_private_thumbnails/', 152 | }, 153 | 'THUMBNAIL_OPTIONS': {}, 154 | }, 155 | }, 156 | } 157 | -------------------------------------------------------------------------------- /kaio/mixins/logs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kaio import Options 4 | from functools import partial 5 | 6 | opts = Options() 7 | get = partial(opts.get, section='Logs') 8 | 9 | 10 | class LogsMixin(object): 11 | """Django Logging configuration""" 12 | 13 | @property 14 | def LOG_LEVEL(self): 15 | return get('LOG_LEVEL', 'DEBUG').upper() 16 | 17 | @property 18 | def DJANGO_LOG_LEVEL(self): 19 | return get('DJANGO_LOG_LEVEL', 'ERROR').upper() 20 | 21 | @property 22 | def LOG_FILE(self): 23 | return get('LOG_FILE', '') 24 | 25 | @property 26 | def EXTRA_LOGGING(self): 27 | """ 28 | lista modulos con los distintos niveles a logear y su 29 | nivel de debug 30 | 31 | Por ejemplo: 32 | 33 | [Logs] 34 | EXTRA_LOGGING = oscar.paypal:DEBUG, django.db:INFO 35 | 36 | """ 37 | 38 | input_text = get('EXTRA_LOGGING', '') 39 | modules = input_text.split(',') 40 | if input_text: 41 | modules = input_text.split(',') 42 | modules = [x.split(':') for x in modules] 43 | else: 44 | modules = [] 45 | return modules 46 | 47 | # The best way to propagate logs up to the root logger is to prevent 48 | # Django logging configuration and handle it ourselves. 49 | # 50 | # http://stackoverflow.com/questions/20282521/django-request-logger-not-propagated-to-root/22336174#22336174 51 | # https://docs.djangoproject.com/en/1.10/topics/logging/#disabling-logging-configuration 52 | LOGGING_CONFIG = None 53 | 54 | @property 55 | def LOGGING(self): 56 | config = { 57 | 'version': 1, 58 | 'disable_existing_loggers': True, 59 | 'formatters': self.formatters, 60 | 'filters': self.filters, 61 | 'handlers': self.handlers, 62 | 'loggers': self.loggers, 63 | } 64 | import logging.config 65 | logging.config.dictConfig(config) 66 | return config 67 | 68 | @property 69 | def handlers(self): 70 | handlers = {} 71 | 72 | handlers['default'] = { 73 | 'level': self.LOG_LEVEL, 74 | 'class': 'logging.StreamHandler', 75 | 'formatter': 'default' 76 | } 77 | 78 | if self.LOG_FILE: 79 | handlers['default']['class'] = 'logging.FileHandler' 80 | handlers['default']['filename'] = self.LOG_FILE 81 | handlers['default']['encoding'] = 'utf-8' 82 | 83 | handlers['mail_admins'] = { 84 | 'level': 'ERROR', 85 | 'filters': ['require_debug_false'], 86 | 'class': 'django.utils.log.AdminEmailHandler' 87 | } 88 | 89 | return handlers 90 | 91 | @property 92 | def loggers(self): 93 | loggers = {} 94 | 95 | loggers[''] = { 96 | 'handlers': ['default'], 97 | 'level': self.LOG_LEVEL, 98 | 'propagate': True, 99 | } 100 | 101 | loggers['rq.worker'] = { 102 | 'handlers': ['default'], 103 | 'level': self.LOG_LEVEL, 104 | 'propagate': False, 105 | } 106 | 107 | loggers['requests.packages.urllib3'] = { 108 | 'handlers': ['default'], 109 | 'level': self.LOG_LEVEL, 110 | 'propagate': False, 111 | } 112 | 113 | loggers['django'] = { 114 | 'handlers': ['default'], 115 | 'level': self.DJANGO_LOG_LEVEL, 116 | 'propagate': False, 117 | } 118 | 119 | if self.EXTRA_LOGGING: 120 | try: 121 | for module, level in self.EXTRA_LOGGING: 122 | loggers[module] = { 123 | 'handlers': ['default'], 124 | 'level': level, 125 | 'propagate': False, 126 | } 127 | except Exception as exc: 128 | import sys 129 | sys.stderr.write(exc) 130 | 131 | return loggers 132 | 133 | @property 134 | def formatters(self): 135 | formatters_config = { 136 | 'default': { 137 | 'format': get('LOG_FORMATTER_FORMAT', '[%(asctime)s] %(levelname)s %(name)s-%(lineno)s %(message)s') 138 | } 139 | } 140 | 141 | formatter_class = get('LOG_FORMATTER_CLASS') 142 | if formatter_class: 143 | formatters_config['default']['()'] = formatter_class 144 | 145 | extra_fields = get('LOG_FORMATTER_EXTRA_FIELDS') 146 | if extra_fields: 147 | formatters_config['default']['extra_fields'] = extra_fields 148 | 149 | return formatters_config 150 | 151 | @property 152 | def filters(self): 153 | return { 154 | 'require_debug_false': { 155 | '()': 'django.utils.log.RequireDebugFalse', 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /kaio/mixins/paths.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kaio import Options 4 | from os.path import abspath, join 5 | from functools import partial 6 | 7 | opts = Options() 8 | get = partial(opts.get, section='Paths') 9 | 10 | 11 | class PathsMixin(object): 12 | 13 | @property 14 | def APP_ROOT(self): 15 | return get('APP_ROOT', abspath('.')) 16 | 17 | @property 18 | def MEDIA_ROOT(self): 19 | return get('MEDIA_ROOT', abspath(join(self.APP_ROOT, 'media'))) 20 | 21 | @property 22 | def STATIC_URL(self): 23 | return get('STATIC_URL', '/static/') 24 | 25 | @property 26 | def MEDIA_URL(self): 27 | return get('MEDIA_URL', '/media/') 28 | 29 | @property 30 | def STATIC_ROOT(self): 31 | return get( 32 | 'STATIC_ROOT', 33 | abspath(join("/tmp", "%s-static" % self.APP_SLUG))) 34 | -------------------------------------------------------------------------------- /kaio/mixins/security.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from kaio import Options 4 | 5 | opts = Options() 6 | 7 | 8 | def get(value, default): 9 | return opts.get(value, default, section='Security') 10 | 11 | 12 | class SecurityMixin(object): 13 | """ 14 | Security base settings 15 | """ 16 | 17 | @property 18 | def SECRET_KEY(self): 19 | return get('SECRET_KEY', u'sysadmin, change the secret key!!!!') 20 | 21 | @property 22 | def ALLOWED_HOSTS(self): 23 | return [h.strip() for h in get('ALLOWED_HOSTS', '*').split(',') if h] 24 | 25 | @property 26 | def SECURE_PROXY_SSL_HEADER_NAME(self): 27 | return get('SECURE_PROXY_SSL_HEADER_NAME', 'HTTP_X_FORWARDED_PROTO') 28 | 29 | @property 30 | def SECURE_PROXY_SSL_HEADER_VALUE(self): 31 | return get('SECURE_PROXY_SSL_HEADER_VALUE', 'https') 32 | 33 | @property 34 | def SECURE_PROXY_SSL_HEADER(self): 35 | # required in order to have the request.is_secure() method to work properly in https environments 36 | # https://docs.djangoproject.com/en/1.10/ref/settings/#secure-proxy-ssl-header 37 | # SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 38 | return self.SECURE_PROXY_SSL_HEADER_NAME, self.SECURE_PROXY_SSL_HEADER_VALUE 39 | -------------------------------------------------------------------------------- /kaio/mixins/sentry.py: -------------------------------------------------------------------------------- 1 | from kaio import Options 2 | from functools import partial 3 | 4 | 5 | opts = Options() 6 | get = partial(opts.get, section='Sentry') 7 | 8 | 9 | class SentryMixin(object): 10 | """Sentry configuration""" 11 | 12 | @property 13 | def SENTRY_DSN(self): 14 | dsn = get('SENTRY_DSN') 15 | if dsn: 16 | self.sentry_init(dsn) 17 | self.ignore_loggers() 18 | return dsn 19 | 20 | @property 21 | def SENTRY_IGNORE_LOGGERS(self): 22 | loggers = get('SENTRY_IGNORE_LOGGERS', 'django.security.DisallowedHost') 23 | return [l.strip() for l in loggers.split(',') if l] 24 | 25 | def sentry_init(self, dsn): 26 | import sentry_sdk 27 | sentry_sdk.init( 28 | dsn=dsn, 29 | integrations=self.integrations(), 30 | send_default_pii=True, # Associate Django user.id or user's IP to errors 31 | ) 32 | 33 | def integrations(self): 34 | from sentry_sdk.integrations.django import DjangoIntegration 35 | return [DjangoIntegration()] 36 | 37 | def ignore_loggers(self): 38 | from sentry_sdk.integrations.logging import ignore_logger 39 | for logger in self.SENTRY_IGNORE_LOGGERS: 40 | ignore_logger(logger) 41 | -------------------------------------------------------------------------------- /kaio/mixins/storage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from functools import partial 4 | 5 | import django 6 | from kaio import Options 7 | 8 | 9 | opts = Options() 10 | get = partial(opts.get, section='Storage') 11 | 12 | 13 | class _BaseStorage(object): 14 | """Settings for django-storages 15 | 16 | Currently only supports AWS S3 with and without CloudFront. 17 | """ 18 | 19 | # AWS S3 settings: http://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html 20 | 21 | @property 22 | def AWS_S3_SIGNATURE_VERSION(self): 23 | return get('AWS_S3_SIGNATURE_VERSION', 's3v4') 24 | 25 | @property 26 | def AWS_S3_REGION_NAME(self): 27 | return get('AWS_S3_REGION_NAME', None) 28 | 29 | @property 30 | def AWS_S3_ENDPOINT_URL(self): 31 | return get('AWS_S3_ENDPOINT_URL', None) 32 | 33 | @property 34 | def AWS_S3_CUSTOM_DOMAIN(self): 35 | return get("AWS_S3_CUSTOM_DOMAIN", None) 36 | 37 | @property 38 | def AWS_STORAGE_BUCKET_NAME(self): 39 | return get('AWS_STORAGE_BUCKET_NAME', '') 40 | 41 | @property 42 | def AWS_LOCATION(self): 43 | return get('AWS_LOCATION', '') 44 | 45 | @property 46 | def AWS_ACCESS_KEY_ID(self): 47 | return get('AWS_ACCESS_KEY_ID', '') 48 | 49 | @property 50 | def AWS_SECRET_ACCESS_KEY(self): 51 | return get('AWS_SECRET_ACCESS_KEY', '') 52 | 53 | @property 54 | def AWS_QUERYSTRING_AUTH(self): 55 | return get('AWS_QUERYSTRING_AUTH', True) 56 | 57 | @property 58 | def AWS_DEFAULT_ACL(self): 59 | return get('AWS_DEFAULT_ACL', 'private') 60 | 61 | 62 | if django.VERSION[:2] < (4, 2): 63 | class StorageMixin(_BaseStorage): 64 | @property 65 | def DEFAULT_FILE_STORAGE(self): 66 | return get('DEFAULT_FILE_STORAGE', 'storages.backends.s3boto3.S3Boto3Storage') 67 | else: 68 | class StorageMixin(_BaseStorage): 69 | @property 70 | def STORAGES(self): 71 | return { 72 | "default": { 73 | "BACKEND": get('DEFAULT_BACKEND_STORAGE', 74 | 'storages.backends.s3boto3.S3Boto3Storage') 75 | }, 76 | "staticfiles": { 77 | "BACKEND": get('STATICFILES_BACKEND_STORAGE', 78 | 'django.contrib.staticfiles.storage.StaticFilesStorage') 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /kaio/mixins/whitenoise.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from functools import partial 4 | 5 | from kaio import Options 6 | 7 | 8 | opts = Options() 9 | get = partial(opts.get, section='WhiteNoise') 10 | 11 | 12 | class WhiteNoiseMixin(object): 13 | """Settings for http://whitenoise.evans.io version 3""" 14 | 15 | @property 16 | def ENABLE_WHITENOISE(self): 17 | enabled = get('ENABLE_WHITENOISE', False) 18 | if enabled: 19 | try: 20 | import whitenoise # noqa: F401 21 | self._add_whitenoise_to_installed_apps() 22 | self._add_whitenoise_to_middleware() 23 | except ImportError: 24 | return False 25 | return enabled 26 | 27 | @property 28 | def WHITENOISE_AUTOREFRESH(self): 29 | return get('WHITENOISE_AUTOREFRESH', True) 30 | 31 | @property 32 | def WHITENOISE_USE_FINDERS(self): 33 | return get('WHITENOISE_USE_FINDERS', True) 34 | 35 | def _add_whitenoise_to_installed_apps(self): 36 | if 'whitenoise.runserver_nostatic' not in self.INSTALLED_APPS: 37 | index = self.INSTALLED_APPS.index('django.contrib.staticfiles') 38 | self.INSTALLED_APPS.insert(index, 'whitenoise.runserver_nostatic') 39 | 40 | def _add_whitenoise_to_middleware(self): 41 | middlewares_settings = ( 42 | 'MIDDLEWARE', # django >= 1.10 43 | 'MIDDLEWARE_CLASSES', # django < 1.10 44 | ) 45 | for middleware_setting in middlewares_settings: 46 | middlewares = getattr(self, middleware_setting, None) 47 | if middlewares is not None: 48 | if 'whitenoise.middleware.WhiteNoiseMiddleware' not in middlewares: 49 | try: 50 | index = middlewares.index('django.middleware.security.SecurityMiddleware') + 1 51 | except ValueError: 52 | index = 0 53 | middlewares.insert(index, 'whitenoise.middleware.WhiteNoiseMiddleware') 54 | -------------------------------------------------------------------------------- /kaio/options.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | from configparser import ConfigParser, NoSectionError 5 | except ImportError: 6 | pass 7 | import os 8 | from os.path import abspath, curdir, isfile, join, pardir 9 | import sys 10 | 11 | 12 | DEFAULT_CONF_NAME = "app.ini" 13 | DEFAULT_SECTION = "Base" 14 | 15 | 16 | def singleton(cls): 17 | instances = {} 18 | 19 | def getinstance(): 20 | if cls not in instances: 21 | instances[cls] = cls() 22 | return instances[cls] 23 | return getinstance 24 | 25 | 26 | class Option(object): 27 | """Option Object""" 28 | 29 | def __init__(self, value=None, section=None, default_value=None): 30 | self.value = value 31 | self.section = section 32 | self.default_value = default_value 33 | 34 | def __repr__(self): 35 | msg = u'Option(value=%r, section=%r, default=%r)' 36 | return msg % (self.value, self.section, self.default_value) 37 | 38 | def get_value_or_default(self): 39 | if self.value is not None: 40 | return self.value 41 | return self.default_value 42 | 43 | 44 | @singleton 45 | class Options(object): 46 | """Option Parser. By now based on ini files""" 47 | 48 | # Options that will not be interpolated. 49 | RAW_OPTIONS = {'LOG_FORMATTER_FORMAT'} 50 | 51 | def __init__(self): 52 | """Parse initial options""" 53 | self.config = ConfigParser() 54 | self.config_file = None 55 | self._read_config() 56 | self.defaults = {} 57 | self.options = {} 58 | self._parse_options() 59 | 60 | @classmethod 61 | def _is_raw_option(cls, option): 62 | return option.upper() in cls.RAW_OPTIONS 63 | 64 | def _conf_path(self): 65 | """Search .ini file from current directory (included) up to "/" (excluded)""" 66 | current = abspath(curdir) 67 | while current != "/": 68 | filename = join(current, DEFAULT_CONF_NAME) 69 | if isfile(filename): 70 | return filename 71 | current = abspath(join(current, pardir)) 72 | # If the .ini file doesn't exist returns an empty filename so default values are used 73 | return '' 74 | 75 | def _read_config(self): 76 | try: 77 | self.config_file = self.config.read(self._conf_path())[0] 78 | except IndexError: 79 | self.config_file = abspath(join(curdir, DEFAULT_CONF_NAME)) 80 | 81 | def _cast_value(self, value): 82 | """Support: int, bool, str""" 83 | try: 84 | value = int(value) 85 | except ValueError: 86 | if value.lower().strip() in ["true", "t", "1", "yes"]: 87 | value = True 88 | elif value.lower().strip() in ["false", "f", "no", "0"]: 89 | value = False 90 | return value 91 | 92 | def _parse_options(self): 93 | """Parse .ini file and set options in self.options""" 94 | for section in self.config.sections(): 95 | for option in self.config.options(section): 96 | raw = self._is_raw_option(option) 97 | value = self.config.get(section=section, option=option, raw=raw) 98 | value = self._cast_value(value) 99 | self.options[option.upper()] = Option(value, section) 100 | 101 | def __iter__(self): 102 | """Return an iterator of options""" 103 | return (o for o in self.options.items()) 104 | 105 | @property 106 | def sections(self): 107 | """Get defined sections""" 108 | return set(o.section for k, o in self.options.items()) 109 | 110 | def items(self, section=None): 111 | """Iterate items of a section in format (name, value)""" 112 | if section: 113 | return ((k, o.value) for k, o in self.options.items() if o.section == section) 114 | else: 115 | return ((k, o.value) for k, o in self.options.items()) 116 | 117 | def keys(self, section=None): 118 | """Returns all configured option names (keys)""" 119 | return [k for k, v in self.items(section)] 120 | 121 | def write(self): 122 | """Save all defined options""" 123 | for name, option in self.options.items(): 124 | if sys.version_info[0] < 3: 125 | try: 126 | value = unicode(option.value) # noqa 127 | except UnicodeDecodeError: 128 | value = unicode(option.value, 'utf-8') # noqa 129 | else: 130 | value = str(value) 131 | 132 | try: 133 | self.config.set(section=option.section, option=name.upper(), value=value) 134 | except NoSectionError: 135 | self.config.add_section(option.section) 136 | self.config.set(section=option.section, option=name, value=value) 137 | self._write_file() 138 | 139 | def _write_file(self): 140 | import codecs 141 | with codecs.open(self.config_file, 'w', "utf-8") as config_file: 142 | self.config.write(config_file) 143 | 144 | def set(self, name, value, section=DEFAULT_SECTION): 145 | name = name.strip() 146 | if type(value) == str: 147 | if sys.version_info[0] < 3: 148 | value = value.decode('utf-8') 149 | value = value.strip() 150 | if name in self.options: 151 | self.options[name].value = value 152 | else: 153 | self.options[name] = Option(value, section) 154 | 155 | def get(self, name, default=None, section=DEFAULT_SECTION): 156 | """Returns value, and also saves the requested default 157 | If value exists in environ, return environ value""" 158 | name = name.strip() 159 | 160 | try: 161 | self.options[name].default_value = default 162 | if not self.options[name].section: 163 | self.options[name].section = section 164 | except KeyError: 165 | self.options[name] = Option(value=None, section=section, default_value=default) 166 | 167 | try: 168 | value = self._cast_value(os.environ[name].strip()) 169 | return value 170 | except KeyError: 171 | if self.options[name].value is not None: 172 | return self.options[name].value 173 | else: 174 | return default 175 | -------------------------------------------------------------------------------- /kaio/properties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # 4 | # bcabezas@apsl.net 5 | 6 | """ 7 | Importa todas las opciones para esta aplicacion como variables del modulo. 8 | Pensado como parche para compatiblidad con properties actuales. 9 | Todas las properties de app.ini quedaran como variables de properties 10 | 11 | Ejemplo de uso desde properties.py: 12 | 13 | from kaio.properties import * 14 | 15 | """ 16 | 17 | import sys 18 | from kaio.options import Options 19 | 20 | opts = Options() 21 | 22 | thismodule = sys.modules[__name__] 23 | 24 | for name, value in opts: 25 | setattr(thismodule, name, value) 26 | -------------------------------------------------------------------------------- /readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | 6 | build: 7 | os: ubuntu-22.04 8 | tools: 9 | python: "3.11" 10 | 11 | sphinx: 12 | builder: html 13 | configuration: docs/conf.py 14 | 15 | formats: 16 | - pdf 17 | - epub 18 | 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt 22 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [devpi:upload] 5 | formats = sdist.tgz,bdist_wheel 6 | 7 | [metadata] 8 | license_file = LICENSE 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding utf-8 3 | 4 | import os 5 | import re 6 | from setuptools import setup, find_packages 7 | import sys 8 | 9 | 10 | main_py = open(os.path.join('kaio', '__init__.py')).read() 11 | metadata = dict(re.findall("__([A-Z]+)__ = '([^']+)'", main_py)) 12 | __VERSION__ = metadata['VERSION'] 13 | 14 | 15 | install_requires = [ 16 | 'clint', 17 | 'django-configurations>=2,<3', 18 | ] 19 | if sys.version_info[0] < 3: 20 | install_requires.append('configparser') 21 | 22 | 23 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: 24 | README = readme.read() 25 | 26 | 27 | setup( 28 | name='django-kaio', 29 | version=__VERSION__, 30 | author='APSL', 31 | author_email='engineering@apsl.net', 32 | url='https://github.com/APSL/django-kaio', 33 | packages=find_packages(), 34 | license='BSD', 35 | description="Class based settings for Django projects that can be read from multiple sources", 36 | long_description=README, 37 | install_requires=install_requires, 38 | classifiers=[ 39 | 'Development Status :: 5 - Production/Stable', 40 | 'Topic :: Software Development :: Libraries :: Python Modules', 41 | 'Environment :: Web Environment', 42 | 'Intended Audience :: Developers', 43 | 'License :: OSI Approved :: BSD License', 44 | 'Operating System :: OS Independent', 45 | 'Programming Language :: Python', 46 | 'Programming Language :: Python :: 2', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Programming Language :: Python :: 3', 49 | 'Programming Language :: Python :: 3.4', 50 | 'Programming Language :: Python :: 3.5', 51 | 'Programming Language :: Python :: 3.6', 52 | 'Programming Language :: Python :: 3.7', 53 | 'Programming Language :: Python :: 3.8', 54 | 'Programming Language :: Python :: 3.9', 55 | 'Programming Language :: Python :: 3.10', 56 | 'Programming Language :: Python :: 3.11', 57 | 'Framework :: Django', 58 | 'Framework :: Django :: 1.8', 59 | 'Framework :: Django :: 1.9', 60 | 'Framework :: Django :: 1.10', 61 | 'Framework :: Django :: 1.11', 62 | 'Framework :: Django :: 2.0', 63 | 'Framework :: Django :: 2.1', 64 | 'Framework :: Django :: 2.2', 65 | 'Framework :: Django :: 3.0', 66 | 'Framework :: Django :: 3.1', 67 | 'Framework :: Django :: 3.2', 68 | 'Framework :: Django :: 4.0', 69 | 'Framework :: Django :: 4.1', 70 | 'Framework :: Django :: 4.2', 71 | ], 72 | include_package_data=True, 73 | zip_safe=False, 74 | ) 75 | --------------------------------------------------------------------------------