├── tests ├── __init__.py ├── requirements.txt ├── settings.py └── test_toolbar.py ├── setup.cfg ├── pytest.ini ├── AUTHORS ├── doc └── elastic_queries.png ├── MANIFEST.in ├── .flake8 ├── CHANGES ├── elastic_panel ├── __init__.py ├── static │ └── elastic_panel │ │ └── js │ │ └── elastic_panel.js ├── templates │ └── elastic_panel │ │ └── elastic_panel.html └── panel.py ├── pyproject.toml ├── .gitignore ├── LICENSE ├── setup.py ├── .pre-commit-config.yaml ├── README.md └── .github └── workflows └── publish-to-test-pypi.yml /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | env = 3 | DJANGO_SETTINGS_MODULE=tests.settings 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | http://github.com/Benoss/django-elasticsearch-debug-toolbar/contributors 2 | -------------------------------------------------------------------------------- /doc/elastic_queries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benoss/django-elasticsearch-debug-toolbar/HEAD/doc/elastic_queries.png -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | recursive-include elastic_panel/templates * 4 | recursive-include elastic_panel/static * 5 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | select = ANN,B,B9,BLK,C,D,DAR,E,F,I,S,W 3 | ignore = E203,E501,W503,D100,D415,D104,B008,B902 4 | max-line-length = 200 5 | max-complexity = 40 6 | application-import-names = tests 7 | import-order-style = google 8 | docstring-convention = google 9 | per-file-ignores = tests/*:S101,E402 10 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | django-debug-toolbar>=3.0 2 | elasticsearch<8.0.0 3 | 4 | pytest 5 | pytest-env 6 | 7 | isort 8 | flake8 9 | flake8-print # Forbid print statement in code use logging. instead 10 | flake8-bugbear # Catch common errors 11 | flake8-printf-formatting # 12 | black>=22.1.0 13 | pre-commit 14 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 3.1.0 2 | * Add a stacktrace to the records to show where the searches were originated #13 3 | 4 | 3.0.2 5 | * Just CI auto deploy fixes 6 | 7 | 3.0.0 8 | * Compatible with Debug toolbar 3.x 9 | * Compatible with Django 4.0 (Requires python 3.8+) 10 | * Remove python 3.6 and 3.7 11 | * Add Python 3.8 3.9 3.10 12 | * Remove travis switch to github actions 13 | 14 | 2.0.0 15 | * Remove python 2.x 16 | * Compatible with Debug toolbar 2.x 17 | 18 | 1.0.0 19 | Added duplicate detection and query hash 20 | 21 | 0.1.0 22 | * Initial release 23 | -------------------------------------------------------------------------------- /elastic_panel/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | elastic_panel 5 | ~~~~~~~~~~~~~~ 6 | 7 | :copyright: (c) 2014 by Benoit Chabord 8 | :license: See LICENSE for more details. 9 | 10 | """ 11 | 12 | import pkg_resources 13 | 14 | try: 15 | __version__ = pkg_resources.get_distribution("django-elasticsearch-debug-toolbar").version 16 | except Exception: 17 | __version__ = "unknown" 18 | 19 | 20 | __title__ = "elastic_panel" 21 | __author__ = "Benoit Chabord" 22 | __copyright__ = "Copyright 2014 Benoit Chabord" 23 | 24 | VERSION = __version__ 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 120 3 | target-version=["py39", ] 4 | include = '\.pyi?$' 5 | # see help text for default exclude 6 | exclude = '''/( 7 | # black defaults from --help (see also https://github.com/ambv/black#configuration-format ) 8 | \.git 9 | | \.hg 10 | | \.mypy_cache 11 | | \.nox 12 | | \.tox 13 | | \.venv 14 | | _build 15 | | buck-out 16 | | build 17 | | dist 18 | 19 | # common exclusions 20 | | node_modules 21 | 22 | # django specific exclusions 23 | 24 | | migrations 25 | 26 | # project specific exclusions 27 | )/ 28 | ''' 29 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 4 | 5 | INSTALLED_APPS = ["debug_toolbar", "elastic_panel"] 6 | 7 | DEBUG_TOOLBAR_PANELS = [ 8 | "elastic_panel.panel.ElasticDebugPanel", 9 | ] 10 | SECRET_KEY = "test" 11 | DEBUG = True 12 | 13 | DATABASES = { 14 | "default": { 15 | "ENGINE": "django.db.backends.sqlite3", 16 | "NAME": ":memory:", 17 | }, 18 | } 19 | 20 | TEMPLATES = [ 21 | { 22 | "BACKEND": "django.template.backends.django.DjangoTemplates", 23 | "DIRS": [BASE_DIR + "/tests/templates"], 24 | "APP_DIRS": True, 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | .pytest_cache 45 | 46 | # Rope 47 | .ropeproject 48 | 49 | # Django stuff: 50 | *.log 51 | *.pot 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | .idea/ 57 | .pypirc 58 | 59 | .eggs/ 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Benoit Chabord 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | from setuptools import find_packages 4 | 5 | setup( 6 | name="django-elasticsearch-debug-toolbar", 7 | packages=find_packages(), 8 | version="3.0.2", 9 | description="A Django Debug Toolbar panel for Elasticsearch", 10 | long_description=open("README.md").read(), 11 | author="Benoit Chabord", 12 | author_email="benauf@gmail.com", 13 | url="http://github.com/Benoss/django-elasticsearch-debug-toolbar", 14 | license="MIT", 15 | keywords=["django", "es", "elastic", "elasticsearch"], 16 | include_package_data=True, 17 | classifiers=[ 18 | "Framework :: Django", 19 | "Intended Audience :: Developers", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | "Programming Language :: Python", 23 | "Programming Language :: Python :: 3.8", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | ], 27 | tests_require=["pytest", "django-debug-toolbar", "elasticsearch"], 28 | test_suite="pytest.collector", 29 | ) 30 | -------------------------------------------------------------------------------- /elastic_panel/static/elastic_panel/js/elastic_panel.js: -------------------------------------------------------------------------------- 1 | var toggle = function(elem) { 2 | if (window.getComputedStyle(elem).display === 'block') { 3 | elem.style.display = 'none'; 4 | return; 5 | } 6 | 7 | elem.style.display = 'block'; 8 | }; 9 | 10 | var uarr = String.fromCharCode(0x25b6), 11 | darr = String.fromCharCode(0x25bc); 12 | 13 | var showHandler = function(e) { 14 | 15 | if(this.nextElementSibling) { 16 | toggle(this.nextElementSibling) 17 | } 18 | 19 | var arrow = this.children[0]; 20 | arrow.textContent = arrow.textContent == uarr ? darr : uarr 21 | 22 | toggle(this.parentNode.nextElementSibling) 23 | 24 | return false 25 | }; 26 | 27 | for (var e of document.querySelectorAll('a.elasticShowTemplate')) { 28 | e.addEventListener('click', showHandler) 29 | } 30 | 31 | var textHandler = function(e) { 32 | var selection = window.getSelection(); 33 | var range = document.createRange(); 34 | range.selectNodeContents(this.parentNode.nextElementSibling.querySelector('code')); 35 | selection.removeAllRanges(); 36 | selection.addRange(range); 37 | }; 38 | 39 | for (var e of document.querySelectorAll('.selectText')) { 40 | e.addEventListener('click', textHandler) 41 | } 42 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # github will run `pre-commit run --hook-stage manual -a` and then manually black and flake8 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.1.0 5 | hooks: 6 | - id: check-yaml 7 | stages: [commit, push, manual] 8 | - id: check-json 9 | stages: [commit, push, manual] 10 | - id: end-of-file-fixer 11 | stages: [commit, push, manual] 12 | - id: trailing-whitespace 13 | stages: [commit, push, manual] 14 | - repo: local 15 | hooks: 16 | - id: isort 17 | name: isort 18 | entry: isort --profile black 19 | language: system 20 | types: [python] 21 | stages: [commit, push, manual] 22 | - id: black 23 | name: black 24 | entry: black 25 | language: system 26 | types: [python] 27 | stages: [commit, push, manual] 28 | - id: flake8 29 | name: flake8 30 | entry: flake8 31 | language: system 32 | types: [python] 33 | stages: [commit, push, manual] 34 | - id: tests 35 | name: pytest 36 | entry: pytest 37 | pass_filenames: false 38 | language: system 39 | types: [python] 40 | stages: [commit, push, manual] 41 | -------------------------------------------------------------------------------- /tests/test_toolbar.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import django 4 | 5 | django.setup() 6 | 7 | 8 | from debug_toolbar.toolbar import DebugToolbar # noqa: E402 9 | from django.http import HttpResponse # noqa: E402 10 | from django.test import RequestFactory # noqa: E402 11 | from django.test import TestCase 12 | from elasticsearch.connection import Connection # noqa: E402 13 | 14 | from elastic_panel import panel # noqa: E402 15 | 16 | 17 | class ImportTest(TestCase): 18 | def test_input(self): 19 | panel.ElasticQueryInfo("GET", "asdasd", "asdasd", "{}", 200, "adssad", 1) 20 | panel.ElasticQueryInfo("GET", "asdasd", "asdasd", "", 200, "adssad", 1) 21 | panel.ElasticQueryInfo("GET", "asdasd", "asdasd", None, 200, "adssad", 1) 22 | panel.ElasticQueryInfo("GET", "asdasd", "asdasd", "{'asddsa': 'é'}", 200, "adssad", 1) 23 | panel.ElasticQueryInfo("GET", "asdasd", "asdasd", b"{'asddsa': 'asddasds'}", 200, "adssad", 1) 24 | 25 | 26 | class PanelTests(TestCase): 27 | def setUp(self): 28 | self.get_response = lambda request: HttpResponse() 29 | self.request = RequestFactory().get("/") 30 | self.toolbar = DebugToolbar(self.request, self.get_response) 31 | self.panel = panel.ElasticDebugPanel(self.toolbar, self.get_response) 32 | 33 | def test_recording(self, *args): 34 | response = self.panel.process_request(self.request) 35 | Connection().log_request_success("GET", "asdasd", "asdasd", "{}", 200, "adssad", 1) 36 | self.assertIsNotNone(response) 37 | 38 | self.panel.generate_stats(self.request, response) 39 | stats = self.panel.get_stats() 40 | self.assertIn("records", stats) 41 | self.assertEqual(len(stats["records"]), 1) 42 | 43 | 44 | if __name__ == "__main__": 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Django Elasticsearch Toolbar 2 | ============================ 3 | 4 | A Django Debug Toolbar panel for Elasticsearch 5 | [](https://badge.fury.io/py/django-elasticsearch-debug-toolbar) 6 | 7 | About 8 | ------------ 9 | 10 | Breaking changes: 11 | * django-elasticsearch-debug-toolbar 3.x is compatible with Django Debug Toolbar 3.x (elasticsearch <8.0.0) 12 | * django-elasticsearch-debug-toolbar 2.x is compatible with Django Debug Toolbar 2.x 13 | * django-elasticsearch-debug-toolbar 1.x is compatible with Django Debug Toolbar 1.x 14 | 15 | ElasticSearch queries using [elasticsearch python](https://github.com/elasticsearch/elasticsearch-py) official client. 16 | 17 | You are more than welcome to participate 18 | * Any idea and no time to code send your idea here: https://github.com/Benoss/django-elasticsearch-debug-toolbar/issues 19 | * An idea and the code just send a pull request here: https://github.com/Benoss/django-elasticsearch-debug-toolbar/pulls 20 | 21 | 22 | 23 | Installation 24 | ------------ 25 | 26 | Install using ``pip``:: 27 | 28 | pip install django-elasticsearch-debug-toolbar 29 | 30 | or install the development version from source:: 31 | 32 | pip install git+git@github.com:Benoss/django-elasticsearch-debug-toolbar.git 33 | 34 | * Then add ``elastic_panel`` to your ``INSTALLED_APPS`` so that we can find the templates in the panel. 35 | * Also, add ``'elastic_panel.panel.ElasticDebugPanel'`` to your ``DEBUG_TOOLBAR_PANELS``. 36 | 37 | Usage 38 | ------------ 39 | 40 | Just click the link in the Django Debug toolbar: 41 | 42 |  43 | 44 | License 45 | ------------ 46 | 47 | Uses the `MIT` license. 48 | 49 | * Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar 50 | * MIT: http://opensource.org/licenses/MIT 51 | -------------------------------------------------------------------------------- /elastic_panel/templates/elastic_panel/elastic_panel.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load static %} 3 | 4 | {#
No Elastic queries were recorded during this request.
52 | {% else %} 53 |
54 | DEBUG is set to False. This means
55 | that Elastic query logging is disabled.
56 |