├── .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 |
--------------------------------------------------------------------------------