diff --git a/.jslintrc b/.jslintrc new file mode 100644 index 0000000..dc14e3a --- /dev/null +++ b/.jslintrc @@ -0,0 +1,34 @@ +{ + "predef": [ + "document", + "navigator", + "console", + "window", + "django", + "$", + "gettext", + "alert", + "closePreview", + "objectIsEqual", + "editor", + "advancedJSONEditor", + "JSONEditor" + ], + + "browser": true, + "eqeq": true, + "white": true, + "unparam": true, + "undef": true, + "sub": true, + "asi": true, + "sloppy": true, + "vars": true, + "forin": true, + "nomen": true, + "continue": true, + "plusplus": true, + "newcap": true, + "indent": 4, + "maxerr": 10000 +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c739dc9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,46 @@ +language: python +sudo: false +cache: pip + +python: + - "3.6" + - "2.7" + +env: + - DJANGO="django>=2.1,<2.2" + - DJANGO="django>=2.0,<2.1" + - DJANGO="django>=1.11,<2.0" + +matrix: + exclude: + - python: "2.7" + env: DJANGO="django>=2.0,<2.1" + env: DJANGO="django>=2.1,<2.2" + +branches: + only: + - master + +before_install: + - pip install -U pip wheel setuptools + - pip install --no-cache-dir -U -r requirements-test.txt + - npm install -g jslint + - jslint django_jsonschema_widget/static/js/*.js + +install: + - pip install $DJANGO + - python setup.py -q develop + +script: + - coverage run --source= runtests.py + + - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + # gets commit message of last commit before pull request merge + COMMIT_MESSAGE=$(git log $TRAVIS_PULL_REQUEST_SHA --format=%B -n 1) + printf "Checking commit message:\n\n" + printf "$COMMIT_MESSAGE\n\n" + checkcommit --message "$COMMIT_MESSAGE" + fi + +after_success: + coveralls diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..85a2026 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,13 @@ +======= +Credits +======= + +Development Lead +---------------- + +* `Federico Capoano `_ + +Contributors +------------ + +* `Asif Saif Uddin `_ \ No newline at end of file diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..3836fad --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,112 @@ +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/openwisp/django-jsonschema-widget/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "feature" +is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +django-jsonschema-widget could always use more documentation, whether as part of the +official django-jsonschema-widget docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/openwisp/django-jsonschema-widget/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `django-jsonschema-widget` for local development. + +1. Fork the `django-jsonschema-widget` repo on GitHub. +2. Clone your fork locally:: + + $ git clone https://github.com/your_name_here/django-jsonschema-widget.git + +3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: + + $ mkvirtualenv django-jsonschema-widget + $ cd django-jsonschema-widget/ + $ python setup.py develop + +4. Create a branch for local development:: + + $ git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +5. When you're done making changes, check that your changes pass flake8 and the + tests, including testing other Python versions with tox:: + + $ flake8 jsonschema_widget tests + $ python setup.py test + $ tox + + To get flake8 and tox, just pip install them into your virtualenv. + +6. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +7. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 2.7, and 3.5+, and for PyPy. Check + https://travis-ci.org/openwisp/django-jsonschema-widget/pull_requests + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + + $ python -m unittest tests.test_jsonschema_widget diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..e4d4eb1 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,9 @@ +.. :changelog: + +History +------- + +0.1.0 (2018-04-16) +++++++++++++++++++ + +* First release on PyPI. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..3b826f2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include AUTHORS.rst +include CONTRIBUTING.rst +include HISTORY.rst +include LICENSE +include README.rst +recursive-include jsonschema_widget *.html *.png *.gif *js *.css *jpg *jpeg *svg *py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cc82341 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +.PHONY: clean-pyc clean-build docs help +.DEFAULT_GOAL := help +define BROWSER_PYSCRIPT +import os, webbrowser, sys +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +help: + @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' + +clean: clean-build clean-pyc + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr *.egg-info + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + +lint: ## check style with flake8 + flake8 jsonschema_widget tests + +test: ## run tests quickly with the default Python + python runtests.py tests + +test-all: ## run tests on every Python version with tox + tox + +coverage: ## check code coverage quickly with the default Python + coverage run --source jsonschema_widget runtests.py tests + coverage report -m + coverage html + open htmlcov/index.html + +docs: ## generate Sphinx HTML documentation, including API docs + rm -f docs/django-jsonschema-widget.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ jsonschema_widget + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(BROWSER) docs/_build/html/index.html + +release: clean ## package and upload a release + python setup.py sdist upload + python setup.py bdist_wheel upload + +sdist: clean ## package + python setup.py sdist + ls -l dist diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..f2bc90e --- /dev/null +++ b/README.rst @@ -0,0 +1,76 @@ +============================= +django-jsonschema-widget +============================= + +.. image:: https://badge.fury.io/py/django-jsonschema-widget.svg + :target: https://badge.fury.io/py/django-jsonschema-widget + +.. image:: https://travis-ci.org/openwisp/django-jsonschema-widget.svg?branch=master + :target: https://travis-ci.org/openwisp/django-jsonschema-widget + +.. image:: https://codecov.io/gh/openwisp/django-jsonschema-widget/branch/master/graph/badge.svg + :target: https://codecov.io/gh/openwisp/django-jsonschema-widget + +Configuration widget for embedded device. + +Documentation +------------- + +The full documentation is at https://django-jsonschema-widget.readthedocs.io. + +Quickstart +---------- + +Install django-jsonschema-widget:: + + pip install django-jsonschema-widget + +Add it to your `INSTALLED_APPS`: + +.. code-block:: python + + INSTALLED_APPS = ( + ... + 'django_jsonschema_widget.apps.JsonschemaWidgetConfig', + ... + ) + +Add django-jsonschema-widget's URL patterns: + +.. code-block:: python + + from django_jsonschema_widget import urls as jsonschema_widget_urls + + + urlpatterns = [ + ... + url(r'^', include(jsonschema_widget_urls)), + ... + ] + +Features +-------- + +* TODO + +Running Tests +------------- + +Does the code actually work? + +:: + + source /bin/activate + (myenv) $ pip install tox + (myenv) $ tox + +Credits +------- + +Tools used in rendering this package: + +* Cookiecutter_ +* `cookiecutter-djangopackage`_ + +.. _Cookiecutter: https://github.com/audreyr/cookiecutter +.. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage diff --git a/django_jsonschema_widget/__init__.py b/django_jsonschema_widget/__init__.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/django_jsonschema_widget/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/django_jsonschema_widget/apps.py b/django_jsonschema_widget/apps.py new file mode 100644 index 0000000..1555d84 --- /dev/null +++ b/django_jsonschema_widget/apps.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 +from django.apps import AppConfig + + +class JsonschemaWidgetConfig(AppConfig): + name = 'django_jsonschema_widget' diff --git a/django_jsonschema_widget/static/css/admin.css b/django_jsonschema_widget/static/css/admin.css new file mode 100644 index 0000000..86df464 --- /dev/null +++ b/django_jsonschema_widget/static/css/admin.css @@ -0,0 +1,117 @@ +a.button:focus, +a.jsoneditor-exit:focus{ text-decoration: none } +.djnjc-preformatted, .field-config .vLargeTextField, +.jsoneditor .vLargeTextField{ + font-size:1em; + font-family: "Bitstream Vera Sans Mono", + "Monaco", + "Droid Sans Mono", + "DejaVu Sans Mono", + "Ubuntu Mono", + "Courier New", Courier, monospace; + color: #f9f9f9; + overflow: auto; + background-color: #222; + white-space: pre-wrap; + word-wrap: break-word; + padding: 20px; + line-height: 22px; +} +.jsoneditor .vLargeTextField{ + color: #333; + background-color: #fff; + padding: 15px; + font-weight: bold +} + +.vLargeTextField.jsoneditor-raw{ + min-width: 100%; + min-height: 500px; + box-sizing: border-box; +} + +input.readonly{ + border: 1px solid rgba(0, 0, 0, 0.05) !important; + background-color: rgba(0, 0, 0, 0.070); +} + +.djnjc-overlay { + display: none; + position: fixed; + left: 0; + top: 0; + z-index: 11; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.91); +} + +.djnjc-overlay .inner{ + width: 100%; + height: 100%; + overflow: auto; + padding: 0; +} + +.djnjc-overlay .djnjc-preformatted{ + margin: 0; + padding: 40px 60px 20px; + background-color: transparent; + color: #adffa6; + line-height: 1.5em; +} + +.djnjc-preformatted.error{ + color: #ff7277 +} + +.djnjc-overlay .close, +.djnjc-overlay .close:focus, +.djnjc-overlay .close:active{ + position: absolute; + right: 3%; + top: 1.5%; + font-weight: bold; + font-size: 15px; + background-color: #772299; + color: #fff; + padding: 5px 8px; + border-radius: 5px; + text-decoration: none; +} +.djnjc-overlay .close:hover{ + background-color: #9e47c1; +} + +.errors.field-templates li, +.errorlist.nonfield li{ + white-space: pre-line; + word-break: break-all; +} +.form-row select{ background-color: #fff } +.form-row input[disabled], .form-row select[disabled]{ background-color: #f4f4f4 } +/* hide vpn specific fields by default */ +.field-vpn, .field-auto_cert{ + display: none; +} +#container .help{ margin-top: 3px; font-size: 13px } +#container .help a, +#netjsonconfig-hint a, #netjsonconfig-hint-advancedmode a{ + text-decoration: underline; + vertical-align: initial; +} +#netjsonconfig-hint, #netjsonconfig-hint-advancedmode { width: auto } + +#container ul.sortedm2m-items{ height: auto; min-height: auto } +#container .sortedm2m-container .help{ + margin-bottom: 20px; + color: #333; + font-weight: bold; +} + +#template_form #id_tags{ width: 261px } + +#device_form #id_model, +#device_form #id_os, +#device_form #id_system{ width: 610px } +#device_form #id_notes{ height: 42px } diff --git a/django_jsonschema_widget/static/css/jsonschema_widget.css b/django_jsonschema_widget/static/css/jsonschema_widget.css new file mode 100644 index 0000000..e69de29 diff --git a/django_jsonschema_widget/static/img/.gitignore b/django_jsonschema_widget/static/img/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/django_jsonschema_widget/static/js/jsonschema_widget.js b/django_jsonschema_widget/static/js/jsonschema_widget.js new file mode 100644 index 0000000..e69de29 diff --git a/django_jsonschema_widget/static/js/preview.js b/django_jsonschema_widget/static/js/preview.js new file mode 100644 index 0000000..79ad35c --- /dev/null +++ b/django_jsonschema_widget/static/js/preview.js @@ -0,0 +1,89 @@ +django.jQuery(function($) { + var overlay = $('.djnjc-overlay'), + body = $('body'), + inner = overlay.find('.inner'), + preview_url = $('.previewlink').attr('data-url'); + var openPreview = function() { + var selectors = 'input[type=text], input[type=hidden], select, textarea', + fields = $(selectors, '#content-main form').not('#id_config_jsoneditor *'), + $id = $('#id_id'), + data = {}; + // add id to POST data + // note: may be overridden by fields of OneToOne relation + if ($id.length) { data['id'] = $id.val(); } + // gather data to send in POST + fields.each(function(i, field){ + var $field = $(field), + name = $field.attr('name'); + // skip management fields + if(!name || + name.indexOf('initial-') === 0 || + name.indexOf('config-__') === 0 || + name.indexOf('_FORMS') != -1){ return; } + // rename fields of OneToOne relation + if(name.indexOf('config-0-') === 0){ + name = name.replace('config-0-', ''); + } + data[name] = $field.val(); + }); + // show preview + $.post(preview_url, data, function(html){ + inner.html($('#content-main div', html).html()); + overlay.show(); + body.css('overflow', 'hidden'); + overlay.find('pre').trigger('click'); + // close preview + overlay.find('.close').click(function(e){ + e.preventDefault(); + closePreview(); + }); + }) + .error(function(xhr){ + // if validation error, show it on page + if (xhr.status == 400) { + $('#content-main form').trigger('submit'); + } + // 500 internal server error + // rare case, leaving it untranslated for simplicity + else{ + var message = 'Error while generating preview'; + if (gettext) { message = gettext(message); } + alert(message + ':\n\n' + xhr.responseText); + } + }); + }; + var closePreview = function () { + overlay.hide(); + inner.html(''); + body.attr('style', ''); + }; + $('.previewlink').click(function(e){ + var configUi = $('#id_config_jsoneditor, #id_config-0-config_jsoneditor'), + message; + e.preventDefault(); + // show preview only if there's a configuration + // (device items may not have one) + if(configUi.length){ + openPreview(); + } + else{ + message = 'No configuration available'; + if (gettext) { message = gettext(message); } + alert(message); + } + }); + $(document).keyup(function(e) { + // ALT+P + if (e.altKey && e.which == 80) { + // unfocus any active input before proceeding + $(document.activeElement).trigger('blur'); + // wait for JSON editor to update the + // corresonding raw value before proceding + setTimeout(openPreview, 15); + } + // ESC + else if (!e.ctrlKey && e.which == 27) { + closePreview(); + } + }); +}); diff --git a/django_jsonschema_widget/static/js/switcher.js b/django_jsonschema_widget/static/js/switcher.js new file mode 100644 index 0000000..1c7396d --- /dev/null +++ b/django_jsonschema_widget/static/js/switcher.js @@ -0,0 +1,28 @@ +django.jQuery(function($) { + var type_select = $('#id_type'), + vpn_specific = $('.field-vpn, .field-auto_cert'), + gettext = window.gettext || function(v){ return v; }, + toggle_vpn_specific = function(changed){ + if (type_select.val() == 'vpn') { + vpn_specific.show(); + if (changed === true && $('.autovpn').length < 1 && $('#id_config').val() === '{}') { + var p1 = gettext('Click on Save to automatically generate the ' + + 'VPN client configuration (will be based on ' + + 'the configuration of the server).'), + p2 = gettext('You can then tweak the VPN client ' + + 'configuration in the next step.'); + $('.jsoneditor-wrapper').hide() + .after('
'); + $('.autovpn').html('

' + p1 + '

' + + '

' + p2 + '

'); + } + } + else{ + vpn_specific.hide(); + } + }; + type_select.on('change', function(){ + toggle_vpn_specific(true); + }); + toggle_vpn_specific(); +}); diff --git a/django_jsonschema_widget/static/js/unsaved_changes.js b/django_jsonschema_widget/static/js/unsaved_changes.js new file mode 100644 index 0000000..6886934 --- /dev/null +++ b/django_jsonschema_widget/static/js/unsaved_changes.js @@ -0,0 +1,105 @@ +(function ($) { + var form = '#content-main form', + map_values = function(object) { + $('input, select, textarea', form).each(function(i, el){ + var field = $(el), + name = field.attr('name'), + value = field.val(); + // ignore fields that have no name attribute, begin with "_" or "initial-" + if (!name || name.substr(0, 1) == '_' || name.substr(0, 8) == 'initial-' || + // ignore hidden fields + name == 'csrfmiddlewaretoken' || + // ignore hidden inline helper fields + name.indexOf('__prefix__') >= 0 || + name.indexOf('root') === 0) { + return; + } + // fix checkbox values inconsistency + if (field.attr('type') == 'checkbox') { + object[name] = field.is(':checked'); + } + else { + object[name] = value; + } + // convert JSON string to Javascript object in order + // to perform object comparison with `objectIsEqual` + if (name == 'config' || name == 'config-0-config') { + try{ + object[name] = JSON.parse(value); + } + catch(ignore){} + } + }); + }; + + var unsaved_changes = function(e) { + // get current values + var current_values = {}; + map_values(current_values); + var changed = false, + message = 'You haven\'t saved your changes yet!', + initialField, initialValue, + name; + if (gettext) { message = gettext(message); } // i18n if enabled + // compare initial with current values + for (name in django._njc_initial_values) { + // use initial values from initial fields if present + initialField = $('#initial-id_' + name); + initialValue = initialField.length ? initialField.val() : django._njc_initial_values[name]; + // fix checkbox value inconsistency + if (initialValue == 'True') { initialValue = true; } + else if (initialValue == 'False') { initialValue = false; } + if (name == 'config') { initialValue = JSON.parse(initialValue); } + + if (!objectIsEqual(initialValue, current_values[name])) { + changed = true; + break; + } + } + if (changed) { + e.returnValue = message; + return message; + } + }; + + // compares equality of two objects + var objectIsEqual = function(obj1, obj2) { + if (typeof obj1 != 'object' && typeof obj2 != 'object') { + return obj1 == obj2; + } + + // jslint doesn't like comparing typeof with a non-constant + // see https://stackoverflow.com/a/18526510 + var obj1Type = typeof obj1, + obj2Type = typeof obj2; + if (obj1Type != obj2Type) { + return false; + } + var p; + for(p in obj1) { + switch(typeof obj1[p]) { + case 'object': + if (!objectIsEqual(obj1[p], obj2[p])) { return false; } break; + default: + if (obj1[p] != obj2[p]) { return false; } + } + } + for(p in obj2) { + if(obj1[p] === undefined) { return false; } + } + return true; + }; + + $(window).load(function(){ + if (!$('.submit-row').length) { return; } + // populate initial map of form values + django._njc_initial_values = {}; + map_values(django._njc_initial_values); + // do not perform unsaved_changes if submitting form + $(form).submit(function() { + $(window).unbind('beforeunload', unsaved_changes); + }); + // bind unload event + $(window).bind('beforeunload', unsaved_changes); + }); +}(django.jQuery)); diff --git a/django_jsonschema_widget/static/js/uuid.js b/django_jsonschema_widget/static/js/uuid.js new file mode 100644 index 0000000..55dcff5 --- /dev/null +++ b/django_jsonschema_widget/static/js/uuid.js @@ -0,0 +1,9 @@ +django.jQuery(function($) { + var container = $('.field-id_hex .readonly').eq(0), + value = container.text(); + container.html(''); + var id = $('#id_id'); + id.click(function(){ + $(this).select(); + }); +}); diff --git a/django_jsonschema_widget/static/js/widget.js b/django_jsonschema_widget/static/js/widget.js new file mode 100644 index 0000000..8163e61 --- /dev/null +++ b/django_jsonschema_widget/static/js/widget.js @@ -0,0 +1,579 @@ +(function ($) { + var inFullScreenMode = false, + oldHeight = 0, + oldWidth = 0; + var toggleFullScreen = function(){ + var advanced = $('#advanced_editor'); + if(!inFullScreenMode){ + // store the old height and width of the editor before going to fullscreen mode in order to be able to restore them + oldHeight = advanced.height(); + oldWidth = advanced.width(); + advanced.addClass('full-screen').height($(window).height()).width(window.outerWidth); + $('body').addClass('editor-full'); + $(window).resize(function(){ + advanced.height($(window).height()).width(window.outerWidth); + }); + inFullScreenMode = true; + advanced.find('.jsoneditor-menu a').show(); + advanced.find('.jsoneditor-menu label').show(); + window.scrollTo(0,0); + } + else{ + advanced.removeClass('full-screen').height(oldHeight).width(oldWidth); + $('body').removeClass('editor-full'); + // unbind all events listened to while going to full screen mode + $(window).unbind('resize'); + inFullScreenMode = false; + document.getElementById('advanced_editor').scrollIntoView(true); + advanced.find('.jsoneditor-menu a').hide(); + advanced.find('.jsoneditor-menu label').hide(); + } + }; + + var initAdvancedEditor = function(target, data, schema, disableSchema){ + var advanced = $("
"); + $(advanced).insertBefore($(target)); + $(target).hide(); + // if disableSchema is true, do not validate againsts schema, default is false + schema = disableSchema ? {} : schema; + var options = { + mode:'code', + theme: 'ace/theme/tomorrow_night_bright', + indentation: 4, + onEditable: function(node){ + return true; + }, + onChange:function() { + $(target).val(editor.getText()); + }, + schema: schema + }; + + var editor = new advancedJSONEditor(document.getElementById(advanced.attr('id')), options, data); + editor.aceEditor.setOptions({ + fontSize: 14, + showInvisibles: true + }); + // remove powered by ace link + advanced.find('.jsoneditor-menu a').remove(); + // add listener to .screen-mode button for toggleScreenMode + advanced.parents('.field-config').find('.screen-mode').click(toggleFullScreen); + // add controls to the editor header + advanced.find('.jsoneditor-menu') + .append($(' back to normal mode')) + .append(advanced.parents('.field-config').find('#netjsonconfig-hint') + .clone(true) + .attr('id','netjsonconfig-hint-advancedmode')); + return editor; + }; + + // returns true if JSON is well formed + // and valid according to its schema + var isValidJson = function(advanced){ + var valid; + try{ + valid = advanced.validateSchema(advanced.get()); + }catch (e){ + valid = false; + } + return valid; + }; + + var alertInvalidJson = function(){ + alert("The JSON entered is not valid"); + }; + + var loadUi = function(backend, schemas, setInitialValue){ + $('.jsoneditor-raw').each(function(i, el){ + var field = $(el), + form = field.parents('form').eq(0), + value = JSON.parse(field.val()), + id = field.attr('id') + '_jsoneditor', + initialField = $('#initial-' + field.attr('id')), + container = field.parents('.form-row').eq(0), + labelText = container.find('label:not(#netjsonconfig-hint)').text(), + startval = $.isEmptyObject(value) ? null : value, + editorContainer = $('#' + id), + html, editor, options, wrapper, header, + getEditorValue, updateRaw, advancedEditor, + $advancedEl; + // inject editor unless already present + if(!editorContainer.length){ + html = '
'; + html += '

'+ labelText +'

'; + html += '
'; + html += '
'; + container.hide().after(html); + editorContainer = $('#' + id); + } + else{ + editorContainer.html(''); + } + + // stop operation if empty admin inline object + if (field.attr('id').indexOf('__prefix__') > -1) { + return; + } + + wrapper = editorContainer.parents('.jsoneditor-wrapper'); + options = { + theme: 'django', + disable_collapse: true, + disable_edit_json: true, + startval: startval, + keep_oneof_values: false, + show_errors: 'change', + // if no backend selected use empty schema + schema: backend ? schemas[backend] : {} + }; + editor = new JSONEditor(document.getElementById(id), options); + // initialise advanced json editor here (disable schema validation in VPN admin) + advancedEditor = initAdvancedEditor(field, value, options.schema, $('#vpn_form').length === 1); + $advancedEl = $('#advanced_editor'); + getEditorValue = function(){ + return JSON.stringify(editor.getValue(), null, 4); + }; + updateRaw = function(){ + field.val(getEditorValue()); + }; + + editor.editors.root.addproperty_button.value = 'Configuration Menu'; + // set initial field value to the schema default + if (setInitialValue) { + initialField.val(getEditorValue()); + } + // update raw value on change event + editor.on('change', updateRaw); + + // update raw value before form submit + form.submit(function(e){ + if ($advancedEl.is(':hidden')) { return; } + // only submit the form if the json in the advanced editor is valid + if(!isValidJson(advancedEditor)){ + e.preventDefault(); + alertInvalidJson(); + } + else{ + if (container.is(':hidden')) { updateRaw(); } + } + }); + + // add advanced edit button + header = editorContainer.find('> div > h3'); + header.find('span:first-child').hide(); // hides editor title + header.attr('class', 'controls'); + // move advanced mode button in auto-generated UI + container.find('.advanced-mode').clone().prependTo(header); + // advanced mode button + header.find('.advanced-mode').click(function(){ + // update autogenrated advanced json editor with new data + advancedEditor.set(JSON.parse(field.val())); + wrapper.hide(); + container.show(); + // set the advanced editor container to full screen mode + toggleFullScreen(); + }); + + // back to normal mode button + $advancedEl.find('.jsoneditor-exit').click(function(){ + // check if json in advanced mode is valid before coming back to normal mode + if(isValidJson(advancedEditor)){ + // update autogenerated UI + editor.setValue(JSON.parse(field.val())); + toggleFullScreen(); + container.hide(); + wrapper.show(); + } + else{ + alertInvalidJson(); + } + }); + + // re-enable click on netjsonconfig hint + $advancedEl.find('#netjsonconfig-hint-advancedmode a').click(function(){ + var window_ = window.open($(this).attr('href'), '_blank'); + window_.focus(); + }); + + // allow to add object properties by pressing enter + form.on('keypress', '.jsoneditor .modal input[type=text]', function(e){ + if(e.keyCode == 13){ + e.preventDefault(); + $(e.target).siblings('input.json-editor-btn-add').trigger('click'); + $(e.target).val(''); + } + }); + }); + }; + + var bindLoadUi = function(){ + $.getJSON(django._netjsonconfigSchemaUrl).success(function(schemas){ + var backend = $('#id_backend, #id_config-0-backend'); + // load first time + loadUi(backend.val(), schemas, true); + // reload when backend is changed + backend.change(function(){ + loadUi(backend.val(), schemas); + }); + }); + }; + + $(function() { + var add_config = $('#config-group.inline-group .add-row'); + // if configuration is admin inline + // load it when add button is clicked + add_config.click(bindLoadUi); + // otherwise load immediately + bindLoadUi(); + }); +}(django.jQuery)); + +var matchKey = (function () { + var elem = document.documentElement; + if (elem.matches) { return 'matches'; } + if (elem.webkitMatchesSelector) { return 'webkitMatchesSelector'; } + if (elem.mozMatchesSelector) { return 'mozMatchesSelector'; } + if (elem.msMatchesSelector) { return 'msMatchesSelector'; } + if (elem.oMatchesSelector) { return 'oMatchesSelector'; } +}()); +// JSON-Schema Edtor django theme +JSONEditor.defaults.themes.django = JSONEditor.AbstractTheme.extend({ + getContainer: function() { + return document.createElement('div'); + }, + getFloatRightLinkHolder: function() { + var el = document.createElement('div'); + el.style = el.style || {}; + el.style.cssFloat = 'right'; + el.style.marginLeft = '10px'; + return el; + }, + getModal: function() { + var el = document.createElement('div'); + el.className = 'modal'; + el.style.display = 'none'; + return el; + }, + getGridContainer: function() { + var el = document.createElement('div'); + el.className = 'grid-container'; + return el; + }, + getGridRow: function() { + var el = document.createElement('div'); + el.className = 'grid-row'; + return el; + }, + getGridColumn: function() { + var el = document.createElement('div'); + el.className = 'grid-column'; + return el; + }, + setGridColumnSize: function(el, size) { + return el; + }, + getLink: function(text) { + var el = document.createElement('a'); + el.setAttribute('href', '#'); + el.appendChild(document.createTextNode(text)); + return el; + }, + disableHeader: function(header) { + header.style.color = '#ccc'; + }, + disableLabel: function(label) { + label.style.color = '#ccc'; + }, + enableHeader: function(header) { + header.style.color = ''; + }, + enableLabel: function(label) { + label.style.color = ''; + }, + getFormInputLabel: function(text) { + var el = document.createElement('label'); + el.appendChild(document.createTextNode(text)); + return el; + }, + getCheckboxLabel: function(text) { + var el = this.getFormInputLabel(text); + return el; + }, + getHeader: function(text) { + var el = document.createElement('h3'); + if (typeof text === "string") { + el.textContent = text; + } else { + el.appendChild(text); + } + return el; + }, + getCheckbox: function() { + var el = this.getFormInputField('checkbox'); + el.style.display = 'inline-block'; + el.style.width = 'auto'; + return el; + }, + getMultiCheckboxHolder: function(controls, label, description) { + var el = document.createElement('div'), + i; + + if (label) { + label.style.display = 'block'; + el.appendChild(label); + } + + for (i in controls) { + if (!controls.hasOwnProperty(i)) { continue; } + controls[i].style.display = 'inline-block'; + controls[i].style.marginRight = '20px'; + el.appendChild(controls[i]); + } + + if (description) { el.appendChild(description); } + + return el; + }, + getSelectInput: function(options) { + var select = document.createElement('select'); + if (options) { this.setSelectOptions(select, options); } + return select; + }, + getSwitcher: function(options) { + var switcher = this.getSelectInput(options); + switcher.className = 'switcher'; + return switcher; + }, + getSwitcherOptions: function(switcher) { + return switcher.getElementsByTagName('option'); + }, + setSwitcherOptions: function(switcher, options, titles) { + this.setSelectOptions(switcher, options, titles); + }, + setSelectOptions: function(select, options, titles) { + titles = titles || []; + select.innerHTML = ''; + var i, option; + for (i = 0; i < options.length; i++) { + option = document.createElement('option'); + option.setAttribute('value', options[i]); + option.textContent = titles[i] || options[i]; + select.appendChild(option); + } + }, + getTextareaInput: function() { + var el = document.createElement('textarea'); + el.className = 'vLargeTextField'; + return el; + }, + getRangeInput: function(min, max, step) { + var el = this.getFormInputField('range'); + el.setAttribute('min', min); + el.setAttribute('max', max); + el.setAttribute('step', step); + return el; + }, + getFormInputField: function(type) { + var el = document.createElement('input'); + el.className = 'vTextField'; + el.setAttribute('type', type); + return el; + }, + afterInputReady: function(input) { + return; + }, + getFormControl: function(label, input, description) { + var el = document.createElement('div'); + el.className = 'form-row'; + if (label) { el.appendChild(label); } + if (input.type === 'checkbox') { + label.insertBefore(input, label.firstChild); + } else { + el.appendChild(input); + } + if (description) { el.appendChild(description); } + return el; + }, + getIndentedPanel: function() { + var el = document.createElement('div'); + el.className = 'inline-related'; + return el; + }, + getChildEditorHolder: function() { + var el = document.createElement('div'); + el.className = 'inline-group'; + return el; + }, + getDescription: function(text) { + var el = document.createElement('p'); + el.className = 'help'; + el.innerHTML = text; + return el; + }, + getCheckboxDescription: function(text) { + return this.getDescription(text); + }, + getFormInputDescription: function(text) { + return this.getDescription(text); + }, + getHeaderButtonHolder: function() { + var el = document.createElement('span'); + el.className = 'control'; + return el; + }, + getButtonHolder: function() { + var el = document.createElement('div'); + el.className = 'control'; + return el; + }, + getButton: function(text, icon, title) { + var el = document.createElement('input'), + className = 'button'; + if (text.indexOf('Delete') > -1) { + className += ' deletelink'; + } + el.className = className; + el.type = 'button'; + this.setButtonText(el, text, icon, title); + return el; + }, + setButtonText: function(button, text, icon, title) { + button.value = text; + if (title) { button.setAttribute('title', title); } + }, + getTable: function() { + return document.createElement('table'); + }, + getTableRow: function() { + return document.createElement('tr'); + }, + getTableHead: function() { + return document.createElement('thead'); + }, + getTableBody: function() { + return document.createElement('tbody'); + }, + getTableHeaderCell: function(text) { + var el = document.createElement('th'); + el.textContent = text; + return el; + }, + getTableCell: function() { + var el = document.createElement('td'); + return el; + }, + getErrorMessage: function(text) { + var el = document.createElement('p'); + el.style = el.style || {}; + el.style.color = 'red'; + el.appendChild(document.createTextNode(text)); + return el; + }, + addInputError: function(input, text) { + input.parentNode.className += ' errors'; + if(!input.errmsg) { + input.errmsg = document.createElement('li'); + var ul = document.createElement('ul'); + ul.className = 'errorlist'; + ul.appendChild(input.errmsg); + input.parentNode.appendChild(ul); + } + else { + input.errmsg.parentNode.style.display = ''; + } + input.errmsg.textContent = text; + }, + removeInputError: function(input) { + if(!input.errmsg) { return; } + input.errmsg.parentNode.style.display = 'none'; + input.parentNode.className = input.parentNode.className.replace(/\s?errors/g,''); + }, + addTableRowError: function(row) { return; }, + removeTableRowError: function(row) { return; }, + getTabHolder: function() { + var el = document.createElement('div'); + el.innerHTML = "
"; + return el; + }, + applyStyles: function(el, styles) { + el.style = el.style || {}; + var i; + for (i in styles) { + if (!styles.hasOwnProperty(i)) { continue; } + el.style[i] = styles[i]; + } + }, + closest: function(elem, selector) { + while (elem && elem !== document) { + if (matchKey) { + if (elem[matchKey](selector)) { + return elem; + } + elem = elem.parentNode; + } else { + return false; + } + } + return false; + }, + getTab: function(span) { + var el = document.createElement('div'); + el.appendChild(span); + el.style = el.style || {}; + this.applyStyles(el, { + border: '1px solid #ccc', + borderWidth: '1px 0 1px 1px', + textAlign: 'center', + lineHeight: '30px', + borderRadius: '5px', + borderBottomRightRadius: 0, + borderTopRightRadius: 0, + fontWeight: 'bold', + cursor: 'pointer' + }); + return el; + }, + getTabContentHolder: function(tab_holder) { + return tab_holder.children[1]; + }, + getTabContent: function() { + return this.getIndentedPanel(); + }, + markTabActive: function(tab) { + this.applyStyles(tab, { + opacity: 1, + background: 'white' + }); + }, + markTabInactive: function(tab) { + this.applyStyles(tab, { + opacity: 0.5, + background: '' + }); + }, + addTab: function(holder, tab) { + holder.children[0].appendChild(tab); + }, + getBlockLink: function() { + var link = document.createElement('a'); + link.style.display = 'block'; + return link; + }, + getBlockLinkHolder: function() { + var el = document.createElement('div'); + return el; + }, + getLinksHolder: function() { + var el = document.createElement('div'); + return el; + }, + createMediaLink: function(holder, link, media) { + holder.appendChild(link); + media.style.width = '100%'; + holder.appendChild(media); + }, + createImageLink: function(holder, link, image) { + holder.appendChild(link); + link.appendChild(image); + } +}); diff --git a/django_jsonschema_widget/templates/django_jsonschema_widget/base.html b/django_jsonschema_widget/templates/django_jsonschema_widget/base.html new file mode 100644 index 0000000..09d26f3 --- /dev/null +++ b/django_jsonschema_widget/templates/django_jsonschema_widget/base.html @@ -0,0 +1,21 @@ + +{% comment %} +As the developer of this package, don't place anything here if you can help it +since this allows developers to have interoperability between your template +structure and their own. + +Example: Developer melding the 2SoD pattern to fit inside with another pattern:: + + {% extends "base.html" %} + {% load static %} + + + {% block extra_js %} + + + {% block javascript %} + + {% endblock javascript %} + + {% endblock extra_js %} +{% endcomment %} diff --git a/django_jsonschema_widget/urls.py b/django_jsonschema_widget/urls.py new file mode 100644 index 0000000..48f0bd9 --- /dev/null +++ b/django_jsonschema_widget/urls.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import url +from django.views.generic import TemplateView + + +app_name = 'django_jsonschema_widget' +urlpatterns = [ + url(r'', TemplateView.as_view(template_name="base.html")), +] diff --git a/django_jsonschema_widget/widgets.py b/django_jsonschema_widget/widgets.py new file mode 100644 index 0000000..0b2d33e --- /dev/null +++ b/django_jsonschema_widget/widgets.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import, unicode_literals + +from django import forms +from django.contrib.admin.templatetags.admin_static import static +from django.contrib.admin.widgets import AdminTextareaWidget +from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ + + +class JsonSchemaWidget(AdminTextareaWidget): + """ + JSON Schema Editor widget + """ + @property + def media(self): + prefix = 'django-netjsonconfig' + js = [static('{0}/js/{1}'.format(prefix, f)) + for f in ('lib/advanced-mode.js', + 'lib/tomorrow_night_bright.js', + 'lib/jsonschema-ui.js', + 'widget.js')] + css = {'all': [static('{0}/css/{1}'.format(prefix, f)) + for f in ('lib/jsonschema-ui.css', + 'lib/advanced-mode.css')]} + return forms.Media(js=js, css=css) + + def render(self, name, value, attrs=None, renderer=None, **kwargs): + attrs['class'] = 'vLargeTextField jsoneditor-raw' + html = """ + + + +""" + html = html.format(_('Advanced mode (raw JSON)'), + reverse('netjsonconfig:schema')) + html += super(JsonSchemaWidget, self).render(name, value, attrs, renderer, **kwargs) + return html diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..0e35bee --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/authors.rst b/docs/authors.rst new file mode 100644 index 0000000..e122f91 --- /dev/null +++ b/docs/authors.rst @@ -0,0 +1 @@ +.. include:: ../AUTHORS.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..819304d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -*- +# +# complexity documentation build configuration file, created by +# sphinx-quickstart on Tue Jul 9 22:26:36 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +cwd = os.getcwd() +parent = os.path.dirname(cwd) +sys.path.append(parent) + +import jsonschema_widget + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-jsonschema-widget' +copyright = u'2018, Federico Capoano' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = jsonschema_widget.__version__ +# The full version, including alpha/beta/rc tags. +release = jsonschema_widget.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-jsonschema-widgetdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'django-jsonschema-widget.tex', u'django-jsonschema-widget Documentation', + u'Federico Capoano', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'django-jsonschema-widget', u'django-jsonschema-widget Documentation', + [u'Federico Capoano'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'django-jsonschema-widget', u'django-jsonschema-widget Documentation', + u'Federico Capoano', 'django-jsonschema-widget', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..e582053 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..2506499 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1 @@ +.. include:: ../HISTORY.rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..19064bd --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,14 @@ +Welcome to django-jsonschema-widget's documentation! +================================================================= + +Contents: + +.. toctree:: + :maxdepth: 2 + + readme + installation + usage + contributing + authors + history diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..4e3a6a4 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,12 @@ +============ +Installation +============ + +At the command line:: + + $ pip instal django-jsonschema-widget + +Or, if you have virtualenvwrapper installed:: + + $ mkvirtualenv django-jsonschema-widget + $ pip install django-jsonschema-widget diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2df9a8c --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\complexity.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..72a3355 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1 @@ +.. include:: ../README.rst diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..1da180f --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,26 @@ +===== +Usage +===== + +To use django-jsonschema-widget in a project, add it to your `INSTALLED_APPS`: + +.. code-block:: python + + INSTALLED_APPS = ( + ... + 'jsonschema_widget.apps.JsonschemaWidgetConfig', + ... + ) + +Please add django-jsonschema-widget's URL patterns in your project's ``urls.py``: + +.. code-block:: python + + from jsonschema_widget import urls as jsonschema_widget_urls + + + urlpatterns = [ + ... + url(r'^', include(jsonschema_widget_urls)), + ... + ] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..a65dc05 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,3 @@ +bumpversion==0.5.3 +wheel>=0.31.1 + diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..c9c16f8 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,8 @@ +coverage==4.4.1 +mock>=1.0.1 +openwisp-utils[qa] +tox>=3.5.1 +codecov>=2.0.0 + + +# Additional test requirements go here diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b764fbb --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +# Additional requirements go here diff --git a/runtests.py b/runtests.py new file mode 100644 index 0000000..a68cf32 --- /dev/null +++ b/runtests.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 +from __future__ import unicode_literals, absolute_import + +import os +import sys + +import django +from django.conf import settings +from django.test.utils import get_runner + + +def run_tests(*test_args): + if not test_args: + test_args = ['tests'] + + os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' + django.setup() + TestRunner = get_runner(settings) + test_runner = TestRunner() + failures = test_runner.run_tests(test_args) + sys.exit(bool(failures)) + + +if __name__ == '__main__': + run_tests(*sys.argv[1:]) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6438845 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,32 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:setup.py] + +[bumpversion:file:django_jsonschema_widget/__init__.py] + +[wheel] +universal = 1 + +[flake8] +ignore = D203 +exclude = + django_jsonschema_widget/migrations, + .git, + .tox, + docs/conf.py, + build, + dist +max-line-length = 119 + +[isort] +line_length = 109 +skip = migrations, docs/source/conf.py +combine_as_imports = true +default_section = THIRDPARTY +include_trailing_comma = true +known_first_party = django_jsonschema_widget +multi_line_output = 5 +not_skip = __init__.py diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..3e34444 --- /dev/null +++ b/setup.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import re +import sys + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + + +def get_version(*file_paths): + """Retrieves the version from jsonschema_widget/__init__.py""" + filename = os.path.join(os.path.dirname(__file__), *file_paths) + version_file = open(filename).read() + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError('Unable to find version string.') + + +version = get_version("django_jsonschema_widget", "__init__.py") + + +if sys.argv[-1] == 'publish': + try: + import wheel + print("Wheel version: ", wheel.__version__) + except ImportError: + print('Wheel library missing. Please run "pip install wheel"') + sys.exit() + os.system('python setup.py sdist upload') + os.system('python setup.py bdist_wheel upload') + sys.exit() + +if sys.argv[-1] == 'tag': + print("Tagging the version on git:") + os.system("git tag -a %s -m 'version %s'" % (version, version)) + os.system("git push --tags") + sys.exit() + +readme = open('README.rst').read() +history = open('HISTORY.rst').read().replace('.. :changelog:', '') + +setup( + name='django-jsonschema-widget', + version=version, + description="""Configuration widget for embedded device.""", + long_description=readme + '\n\n' + history, + author='Federico Capoano', + author_email='nemesis@ninux.org', + url='https://github.com/openwisp/django-jsonschema-widget', + packages=[ + 'django_jsonschema_widget', + ], + include_package_data=True, + install_requires=['django'], + license="BSD", + zip_safe=False, + keywords='django-jsonschema-widget', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Framework :: Django :: 1.11', + 'Framework :: Django :: 2.0', + 'Framework :: Django :: 2.1', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/manage.py b/tests/manage.py new file mode 100644 index 0000000..8c5be8a --- /dev/null +++ b/tests/manage.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import + +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/tests/settings.py b/tests/settings.py new file mode 100644 index 0000000..e5959b5 --- /dev/null +++ b/tests/settings.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 +from __future__ import unicode_literals, absolute_import + + +DEBUG = True +USE_TZ = True + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "v+s=%bl$5s30!o@wnz1v^6##6pxl@0zs1iz+ie3q3%q+@t)igc" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } +} + +ROOT_URLCONF = "tests.urls" + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sites", + "django_jsonschema_widget", +] + +SITE_ID = 1 diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..cbe316a --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +test_django-jsonschema-widget +------------ + +Tests for `django-jsonschema-widget` models module. +""" + +from django.test import TestCase + + +class TestJsonschema_widget(TestCase): + + def setUp(self): + pass + + def test_something(self): + pass + + def tearDown(self): + pass diff --git a/tests/urls.py b/tests/urls.py new file mode 100644 index 0000000..e077823 --- /dev/null +++ b/tests/urls.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import + +from django.conf.urls import url, include + + +urlpatterns = [ + url(r'^', include( + 'django_jsonschema_widget.urls', + namespace='django_jsonschema_widget') + ), +] diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..5a991d4 --- /dev/null +++ b/tox.ini @@ -0,0 +1,20 @@ +[tox] +envlist = + {py35,py36}-django-21 + {py34,py35,py36}-django-20 + {py27,py34,py35,py36}-django-111 + +[testenv] +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/django_jsonschema_widget +commands = coverage run --source django_jsonschema_widget runtests.py +deps = + django-111: Django>=1.11,<1.12 + django-20: Django>=2.0,<2.1 + django-20: Django>=2.1,<2.2 + -r{toxinidir}/requirements-test.txt +basepython = + py36: python3.6 + py35: python3.5 + py34: python3.4 + py27: python2.7