diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..73efe61 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,95 @@ +version: 2.0 + +common: &common + working_directory: ~/repo + steps: + - checkout + - restore_cache: + keys: + - v2-deps-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} + - run: + name: install dependencies + command: pip install --user tox + - run: + name: run tox + command: ~/.local/bin/tox + - run: + name: upload coverage report + command: | + if [[ "$UPLOAD_COVERAGE" != 0 ]]; then + PATH=$HOME/.local/bin:$PATH + pip install --user codecov + coverage xml + ~/.local/bin/codecov --required -X search gcov pycov -f coverage.xml --flags $CIRCLE_JOB + fi + - save_cache: + paths: + - .tox + - ~/.cache/pip + - ~/.local + - ./eggs + key: v2-deps-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} + +jobs: + lint: + <<: *common + docker: + - image: circleci/python:3.6 + environment: + - TOXENV=checkqa + - UPLOAD_COVERAGE=0 + py27dj111: + <<: *common + docker: + - image: circleci/python:2.7 + environment: + TOXENV=py27-dj111 + py34dj111: + <<: *common + docker: + - image: circleci/python:3.4 + environment: + TOXENV=py34-dj111 + py34dj20: + <<: *common + docker: + - image: circleci/python:3.4 + environment: + TOXENV=py34-dj20 + py35dj111: + <<: *common + docker: + - image: circleci/python:3.5 + environment: + TOXENV=py35-dj111 + py35dj20: + <<: *common + docker: + - image: circleci/python:3.5 + environment: + TOXENV=py35-dj20 + py36dj111: + <<: *common + docker: + - image: circleci/python:3.6 + environment: + TOXENV=py36-dj111 + py36dj20: + <<: *common + docker: + - image: circleci/python:3.6 + environment: + TOXENV=py36-dj20 + +workflows: + version: 2 + test: + jobs: + - lint + - py27dj111 + - py34dj111 + - py34dj20 + - py35dj111 + - py35dj20 + - py36dj111 + - py36dj20 \ No newline at end of file diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 7b361ef..0000000 --- a/.coveragerc +++ /dev/null @@ -1,7 +0,0 @@ -[run] -source = formly -omit = formly/tests/*,formly/admin.py -branch = 1 - -[report] -omit = formly/tests/*,formly/admin.py diff --git a/.gitignore b/.gitignore index d405653..c6037eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,45 @@ -docs/_build -formly.egg-info -dist -*.pyc -.coverage -.tox +MANIFEST +.DS_Store + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +docs/_build/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +*.eggs .python-version -.eggs/ +# Pipfile +Pipfile +Pipfile.lock + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# IDEs +.idea/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 002bd17..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -sudo: false -language: python -python: - - "2.7" - - "3.4" - - "3.5" -env: - - DJANGO=1.8 - - DJANGO=1.9 - - DJANGO=1.10 -matrix: - exclude: -install: - - pip install tox coveralls -script: - - tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO -after_success: - - coveralls diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..9b7b778 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +Patrick Altman +Luke Hatcher +Ben Bliss +James Tauber +Jake Wegner +Ronan Amicel +Graham Ullrich \ No newline at end of file diff --git a/Makefile b/Makefile index 3dff49d..5aff318 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,10 @@ -all: init docs test +all: init test init: python setup.py develop - pip install detox coverage mkdocs + pip install detox coverage test: coverage erase detox coverage html - -docs: - mkdocs build - -.PHONY: docs diff --git a/README.md b/README.md new file mode 100644 index 0000000..17ed219 --- /dev/null +++ b/README.md @@ -0,0 +1,553 @@ + +# Formly + +[![](https://img.shields.io/pypi/v/formly.svg)](https://pypi.python.org/pypi/formly/) + +[![CircleCi](https://img.shields.io/circleci/project/github/eldarion/formly.svg)](https://circleci.com/gh/eldarion/formly) +[![Codecov](https://img.shields.io/codecov/c/github/eldarion/formly.svg)](https://codecov.io/gh/eldarion/formly) +[![](https://img.shields.io/github/contributors/eldarion/formly.svg)](https://github.com/eldarion/formly/graphs/contributors) +[![](https://img.shields.io/github/issues-pr/eldarion/formly.svg)](https://github.com/eldarion/formly/pulls) +[![](https://img.shields.io/github/issues-pr-closed/eldarion/formly.svg)](https://github.com/eldarion/formly/pulls?q=is%3Apr+is%3Aclosed) + +[![](https://img.shields.io/badge/license-BSD-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) + + +## Table of Contents + +* [Overview](#overview) + * [Supported Django and Python versions](#supported-django-and-python-versions) +* [Documentation](#documentation) + * [Installation](#installation) + * [Optional Requirements](#optional-requirements) + * [Usage](#usage) + * [Fields](#fields) + * [Templates](#templates) + * [Authorization](#authorization) + * [Callbacks](#callbacks) +* [Change Log](#change-log) +* [License](#license) + + +## formly + +### Overview + +``formly`` is a forms/survey generator for dynamically constructed multi-page surveys with the ability to be non-linear. + +`formly` is an app that provides an out of the box solution to building adhoc +forms to collect data from end users. It is multi-faceted in that it provides +interfaces for building multi-page forms, interfaces for executing the survey, +as well as views for reviewing results. + +Also, it is non-linear, meaning that you can route users taking the survey to +different pages based on what they answered on certain questions. This allows +you to create very rich surveys that dive deep on detail you care about while +not wasting the time of users who would otherwise have to go through questions +that do not apply to them. + +Development sponsored by [Midwest Communications](http://mwcradio.com/) and [Massachusetts General Hospital](http://www.massgeneral.org/). + + +#### Supported Django and Python versions + +Django \ Python | 2.7 | 3.4 | 3.5 | 3.6 +--------------- | --- | --- | --- | --- +1.11 | * | * | * | * +2.0 | | * | * | * + + +## Documentation + +### Installation + +To install formly: + +```shell + $ pip install formly +``` + +Add `formly` to your ``INSTALLED_APPS`` setting: + +```python + INSTALLED_APPS = [ + # other apps + "formly", + ] +``` + +Next, add `formly.urls` to your project urlpatterns: + +```python + urlpatterns = [ + # other urls + url(r"^surveys/", include("formly.urls", namespace="formly")), + ] +``` + +Finally, if you want to use formly's permission authentications, +add `formly.auth_backend.AuthenticationBackend` to your settings +AUTHENTICATION_BACKENDS: + +```python + AUTHENTICATION_BACKENDS = [ + # other authentication backends + "formly.auth_backend.AuthenticationBackend", + ] +``` + +### Optional Requirements + +In order to use built-in templates, add the following dependencies to your project: + +* pinax-theme-bootstrap (not required if you use different block names) +* django-bootstrap-form (required for form rendering in templates) + +```python + INSTALLED_APPS = [ + # other apps + "bootstrapform", + ] +``` + + +### Usage + +`formly` is designed to be plug-and-play, in that after you install `formly` +using it should be as simple as creating and publishing surveys through +the web interface. + +After installation, browse to wherever you mounted the urls for `formly` and +you'll see an interface to be able to create a new survey. From here you can +create a survey and begin editing pages. Pages in `formly` represent each +step of the survey. The user will be guided through each page of the survey +in the appropriate order saving each page at a time. + +For each page, you can give a title and add as many fields as you desire. If +the field is a type that requires choices, then you will have the option +on the fields detail/edit form to add choices. An optional value that can +be supplied for a choice answer is the page to redirect the user to if they +select that answer. + +If you have multiple choice fields in a single page with conflicting page +routing, `formly` resolves to the first page it encounters. For example, if +you had a question that had choice B route to page 3 and another question +later in the form that had choice C route to page 5 and the user answered +both questions with choice B and choice C, the user would go to page 3 +next. Keep this in mind when building surveys. + + +### Fields + +`formly` enables you to create questions with a multitude of field types that +will control the dynamic rendering and processing of form input on each page of +your survey. + +All field types tie directly to a specific `field` and `widget` +configuration as found in `django.forms`. The other attributes that can be +passed into every field, `label`, `help_text`, and `required` can be +set and managed at design time. + +For the field types that accept choices, there is the ability to set key/value +pairs for each field that are used to populate the choices attribute for the +field to be used for both display as well as form validation upon execution. + +#### Boolean (True/False) + +Renders and processes input using `django.forms.BooleanField`. + +#### Checkbox (Multiple Choice, Multiple Answers) + +A field generated from a `django.forms.MultipleChoiceField` with a +`django.forms.CheckboxInput` widget, populated with choices specified +at design time. This field allows for multiple selections. + +#### Date + +Provides a way to constrain input to dates only. It is +generated from a `django.forms.DateField`. + +#### Likert Scale + +A `django.forms.ChoiceField` populated with choices specified at design time. +The field template `formly/templates/bootstrapform/field.html` emits: + +```html + +``` + +for hooking in CSS design. The following sample CSS presents a Likert field +in familiar horizontal layout. You should add this (or similar) CSS to your +project to get Likert-scale presentation. + +```css + form .likert-question { + list-style:none; + width:100%; + margin:0; + padding:0 0 35px; + display:block; + border-bottom:2px solid #efefef; + } + form .likert-question:last-of-type { + border-bottom:0; + } + form .likert-question:before { + content: ''; + position:relative; + top:13px; + left:13%; + display:block; + background-color:#dfdfdf; + height:4px; + width:75%; + } + form .likert-question li { + display:inline-block; + width:19%; + text-align:center; + vertical-align: top; + } + form .likert-question li input[type=radio] { + display:block; + position:relative; + top:0; + left:50%; + margin-left:-6px; + } + form .likert-question li label { + width:100%; + } +``` + +#### Media (File Upload) + +Enables users to upload content as a response using `django.forms.FileField`. + +#### Multiple Text (Multiple Free Responses - Single Lines) + +Presents a number of single line fields. +The number of fields is specified at design time. + +#### Radio (Multiple Choice, Pick One) + +A `django.forms.ChoiceField` with a `django.forms.RadioSelect` widget, +populated with choices specified at design time. + +#### Rating Scale + +A `django.forms.ChoiceField` populated with choices specified at design time. +The field template `formly/templates/bootstrapform/field.html` emits: + +```html + +``` + +#### Select (Multiple Choice, Pick One - Dropdown) + +A select field generated from a `django.forms.ChoiceField` with a +`django.forms.Select` widget, populated with choices specified at design time. + +#### Text (Free Response, One Line) + +A field for open ended text input and is interpreted as `django.forms.CharField`. + +#### TextArea (Free Response, Box) + +A `django.forms.CharField` with a `django.forms.Textarea` widget used +to collect longer form text input. + + +### Templates + +`formly` ships with some stock templates that are based on +`pinax-theme-bootstrap` and `django-forms-bootstrap`. You are not required +to use these of course and in case you are rolling your own templates, here +is what the views in `formly` expect. + +#### `formly/design/choice_form.html` + +**Context:** `form`, `choice`, `page` + +**Extends:** `formly/design/survey_edit_base.html` + +Provides the ability to update the values for a particular choice for a choice field. + +#### `formly/design/field_confirm_delete.html` + +**Context:** `form`, `field` + +**Extends:** `site_base.html` + +Rendered to supply a delete confirmation form for field deletion. + +#### `formly/design/field_form.html` + +**Context:** `form`, `field`, `page`, `field_choice_form` + +**Extends:** `formly/design/survey_edit_base.html` + +Rendered for a user interface to update a field. + +#### `formly/design/fieldchoice_confirm_delete.html` + +**Context:** `form`, `fieldchoice` + +**Extends:** `site_base.html` + +Rendered to supply a delete confirmation form for field choice deletion. + +#### `formly/design/page_confirm_delete.html` + +**Context:** `form`, `page` + +**Extends:** `site_base.html` + +Rendered to supply a delete confirmation form for page deletion. + +#### `formly/design/page_form.html` + +**Context:** `form`, `page`, `field_form` + +**Extends:** `formly/design/survey_edit_base.html` + +Displays the user interface for updating a page object. + +#### `formly/design/survey_confirm_delete.html` + +**Context:** `form`, `survey` + +**Extends:** `site_base.html` + +Rendered to supply a delete confirmation form for survey deletion. + +#### `formly/design/survey_detail.html` + +**Context:** `survey` + +**Extends:** `site_base.html` + +Displays the detail for a survey. + +#### `formly/design/survey_edit_base.html` + +**Context:** `page` + +**Extends:** `subnav_base.html` + +**Extended By:** `formly/design/choice_form.html`, `formly/design/field_form.html`, `formly/design/page_form.html` + +A base template to provide common sub-navigation. + +#### `formly/design/survey_form.html` + +**Context:** `form` + +**Extends:** `site_base.html` + +Contains the creation form for creating a new survey object. + +#### `formly/design/survey_list.html` + +**Context:** `unpublished_surveys`, `published_surveys` + +**Extends:** `site_base.html` + +This template receives all surveys in the system split between two context objects, +one for published surveys and the other for unpublished surveys. + +#### `formly/results/home.html` + +**Context:** `survey` + +**Extends:** `site_base.html` + +Displays the results of a given survey. + +#### `formly/run/page.html` + +**Context:** `form`, `page` + +**Extends:** `site_base.html` + +Rendered for the end user to complete a particular survey. Always +rendered with the appropriate page for the user. + +#### `formly/bootstrapform/field.html` + +**Context:** `field` + +This modified `django-bootstrap-form` template renders the various field types, +including special handling for Likert and Rating fields. + + +### Authorization + +`formly` ships with an auth backend that by default, when added to +your `AUTHENTICATION_BACKENDS` setting will segment the create, +edit, delete and results viewing based on `request.user` being +the `Survey.creator`. + +You can override this by writing your own auth backend and using in +it's place. + +The permission labels used are as follows: + + +#### `formly.view_survey_list` + +User can see the list of published and unpublished surveys. + +#### `formly.create_survey` + +User can create a survey. + +#### `formly.view_survey_detail` + +User can view the survey's detail. The survey object in question is +passed to the `has_perm` method of the auth backend. + +#### `formly.change_survey_name` + +User can change the survey's name. The survey object in question is +passed to the `has_perm` method of the auth backend. + +#### `formly.publish_survey` + +User can publish the survey. The survey object in question is +passed to the `has_perm` method of the auth backend. + +#### `formly.duplicate_survey` + +User can duplicate the survey. The survey object in question is +passed to the `has_perm` method of the auth backend. + +#### `formly.edit_survey` + +User can edit the survey. The survey object in question is +passed to the `has_perm` method of the auth backend. + +#### `formly.view_results` + +User can view the survey's results. The survey object in question is +passed to the `has_perm` method of the auth backend. + +#### `formly.delete_object` + +User can delete the object in question. The object will be either a +`Survey`, `Page`, `Field`, or a `FieldChoice`. + + +### Callbacks + +Callbacks are a way to provide functionality to `formly` that requires some +runtime decision making instead of just a setting. They are callables +defined in settings.py and ship some sane defaults. + + +#### `FORMLY_COMPLETE_REDIRECT_CALLBACK` + +**Default:** `formly.callbacks.survey_complete_redirect` + +**Arguments:** `survey` + +**Expected Return:** a url that will be passed to `redirect()` + + +## Change Log + +### 1.0.0 + +* Add Django v1.11, 2.0 support +* Drop Django 1.8, 1.9, 1.10, and Python 3.3 support +* Add URL namespacing (i.e. urlname "formly_survey_results" is now "formly:survey_results") **Backwards Incompatible** +* Rename URL names, removing "dt_" and "rt_" prefixes **Backwards incompatible** +* Move documentation into README and standardize layout +* Convert CI and coverage to CircleCi and CodeCov +* Add PyPi-compatible long description +* Add migration checking to test suite + +### 0.15.0 + +* fix bug where widget instances were passed instead of widget classes (#34) + +### 0.14.0 + +* add hookset to support customizing available field type choices when designing a survey + +### 0.13.0 + +* fix field mapping bug (#30) +* improve output of MultipleTextField widget (#20) + +### 0.12.0 + +* fix broken migrations from 0.11.0 + +### 0.11.0 + +* add support for Rating field + +### 0.10.2 + +* fix app to work with a custom user module +* add missing migration for formly.Field + +### 0.10.1 + +* fix Field.form_field() bug when Likert field has no choices + +### 0.10 + +* add Likert-style field widget and presentation + + +### 0.9 + +* make label and help_text textfields + +### 0.6 + +* changed field label descriptions to be more suitable for less technical audiences +* made compatible with Django > 1.5 +* drop unique constraint on field label + +### 0.5 + +* made urls Django 1.5 compatible +* add maximum_choices field +* drop unique constraint on field label + +### 0.4.2 + +* fixed multiple choice field +* added survey to context + +### 0.4.1 + +* fixed serialization bug, note this is a backwards incompatible change + if you have previously stored results + +### 0.4 + +* added authorization checks for all the views + +### 0.3 + +* added ability to control redirection at the end of a survey + +### 0.2 + +* added ability to change the ordering of fields on a page + +### 0.1 + +* initial release + + +## License + +Copyright (c) 2012-2018 Patrick Altman and contributors under the [BSD license](https://opensource.org/licenses/BSD-3-Clause). diff --git a/README.rst b/README.rst deleted file mode 100644 index 0a2e291..0000000 --- a/README.rst +++ /dev/null @@ -1,27 +0,0 @@ -formly -====== - - -.. image:: https://img.shields.io/travis/eldarion/formly.svg - :target: https://travis-ci.org/eldarion/formly - -.. image:: https://img.shields.io/coveralls/eldarion/formly.svg - :target: https://coveralls.io/r/eldarion/formly - -.. image:: https://img.shields.io/pypi/dm/formly.svg - :target: https://pypi.python.org/pypi/formly/ - -.. image:: https://img.shields.io/pypi/v/formly.svg - :target: https://pypi.python.org/pypi/formly/ - -.. image:: https://img.shields.io/badge/license-BSD-blue.svg - :target: https://pypi.python.org/pypi/formly/ - - -A forms/survey generator for dynamically constructor multi-page surveys that have the ability to be non-linear. - - -development sponsored by `Midwest Communications`_ and `Massachusetts General Hospital`_. - -.. _Midwest Communications: http://mwcradio.com/ -.. _Massachusetts General Hospital: http://www.massgeneral.org/ diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index ad80acd..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,131 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -PROJECT = formly - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - -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 " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @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/$(PROJECT).qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(PROJECT).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/$(PROJECT)" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/$(PROJECT)" - @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." - -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." - -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." diff --git a/docs/authorization.rst b/docs/authorization.rst deleted file mode 100644 index 1a55352..0000000 --- a/docs/authorization.rst +++ /dev/null @@ -1,76 +0,0 @@ -.. _authorization: - - -Authorization -============= - -``formly`` ships with an auth backend that by default, when added to -your ``AUTHENTICATION_BACKENDS`` setting will segment the create, -edit, delete and results viewing based on the ``reauest.user`` being -the ``Survey.creator``. - -You can override this by writing your own auth backend and using in -it's place. - -The permission labels used are as follows: - - -formly.view_survey_list ------------------------ - -Can the user see the list of published and unpublished surveys - - -formly.create_survey --------------------- - -Can the user create a survey - - -formly.view_survey_detail -------------------------- - -Can the user view the survey's detail. The survey object in question is -passed to the ``has_perm`` method of the auth backend. - - -formly.change_survey_name -------------------------- - -Can the user change the survey's name. The survey object in question is -passed to the ``has_perm`` method of the auth backend. - - -formly.publish_survey ---------------------- - -Can the user publish the survey. The survey object in question is -passed to the ``has_perm`` method of the auth backend. - - -formly.duplicate_survey ------------------------ - -Can the user duplicate the survey. The survey object in question is -passed to the ``has_perm`` method of the auth backend. - - -formly.edit_survey ------------------- - -Can the user edit the survey. The survey object in question is -passed to the ``has_perm`` method of the auth backend. - - -formly.view_results -------------------- - -Can the user view the survey's results. The survey object in question is -passed to the ``has_perm`` method of the auth backend. - - -formly.delete_object --------------------- - -Can the user delete the object in question. The object will be either a -``Survey``, ``Page``, ``Field``, or a ``FieldChoice``. diff --git a/docs/callbacks.rst b/docs/callbacks.rst deleted file mode 100644 index e2548c8..0000000 --- a/docs/callbacks.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _callbacks: - - -Callbacks -========= - -Callbacks are a way to provide functionality to formly that requires some -runtime decision making instead of just a setting. They are callables -that are defined in settings and ship some sane defaults. - - -``FORMLY_COMPLETE_REDIRECT_CALLBACK`` -------------------------------------- - -:Default: ``formly.callbacks.survey_complete_redirect`` -:Arguments: ``survey`` -:Expected Return: a url that will be passed to ``redirect()`` diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index b90d905..0000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,100 +0,0 @@ -.. _changelog: - -ChangeLog -========= - -0.15.0 ------- -- fix bug where widget instances were passed instead of widget classes (#34) - -0.14.0 ------- -- add hookset to support customizing available field type choices when designing a survey - -0.13.0 ------- -- fix field mapping bug (#30) -- improve output of MultipleTextField widget (#20) - -0.12.0 ------- - -- fix broken migrations from 0.11.0 - -0.11.0 ------- - -- add support for Rating field - -0.10.2 ------- - -- fix app to work with a custom user module -- add missing migration for formly.Field - -0.10.1 ------- - -- fix Field.form_field() bug when Likert field has no choices - -0.10 ------ - -- add Likert-style field widget and presentation - - -0.9 ---- - -- make label and help_text textfields - - -0.6 ---- - -- changed field label descriptions to be more suitable for less technical audiences -- made compatible with Django > 1.5 -- drop unique constraint on field label - - -0.5 ---- - -- made urls Django 1.5 compatible -- add maximum_choices field -- drop unique constraint on field label - -0.4.2 ------ - -- fixed multiple choice field -- added survey to context - -0.4.1 ------ - -- fixed serialization bug, note this is a backwards incompatible change - if you have previously stored results - -0.4 ---- - -- added authorization checks for all the views - - -0.3 ---- - -- added ability to control redirection at the end of a survey - - -0.2 ---- - -- added ability to change the ordering of fields on a page - - -0.1 ---- - -- initial release diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 38e4fbd..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -import sys - -extensions = [] -templates_path = [] -source_suffix = '.rst' -master_doc = 'index' -project = u'formly' -copyright_holder = 'Eldarion' -copyright = u'2012, %s' % copyright_holder -exclude_patterns = ['_build'] -pygments_style = 'sphinx' -html_theme = 'default' -htmlhelp_basename = '%sdoc' % project -latex_documents = [ - ('index', '%s.tex' % project, u'%s Documentation' % project, - copyright_holder, 'manual'), -] -man_pages = [ - ('index', project, u'%s Documentation' % project, - [copyright_holder], 1) -] - -sys.path.insert(0, os.pardir) -m = __import__(project) - -version = m.__version__ -release = version diff --git a/docs/fields.rst b/docs/fields.rst deleted file mode 100644 index ac8fc99..0000000 --- a/docs/fields.rst +++ /dev/null @@ -1,150 +0,0 @@ -.. _fields: - -Fields -====== - -``formly`` enables you to create questions with a multitude of field types that -will control the dynamic rendering and processing of form input on each page of -your survey. - -All field types tie directly to a specific ``field`` and ``widget`` -configuration as found in ``django.forms``. The other attributes that can be -passed into every field, ``label``, ``help_text``, and ``required`` can be -set and managed at design time. - -For the field types that accept choices, there is the ability to set key/value -pairs for each field that are used to populate the choices attribute for the -field to be used for both display as well as form validation upon execution. - - -text field ----------- - -The ``text field`` is a field for open ended text input and is interpreted as -``django.forms.CharField``. - - -textarea --------- - -The ``textarea`` field type is a ``django.forms.CharField`` with a -``django.forms.Textarea`` widget to be used to collect longer form text input. - - -radio choices -------------- - -The ``radio choices`` field type is a ``django.forms.ChoiceField`` with a -``django.forms.RadioSelect`` widget, populated with choices specified at -design time. - - -dropdown field --------------- - -The ``dropdown field`` is a select field generated from a -``django.forms.ChoiceField`` with a ``django.forms.Select`` widget, populated -with choices specified at design time. - - -checkbox field --------------- - -The ``checkbox field`` is a field generated from a -``django.forms.MultipleChoiceField`` with a ``django.forms.CheckboxInput`` widget, -populated with choices specified at design time. This field allows for -multiple selections. - - -date field ----------- - -The ``date field`` provides a way to constrain input to dates only. It is -generated from a ``django.forms.DateField``. - - -media upload field ------------------- - -The ``media upload field`` enables users to upload content as a response. It -is uses ``django.forms.FileField``. - - -boolean field -------------- - -The ``boolean field`` renders and processes input using -``django.forms.BooleanField``. - - -multiple text field -------------------- - -The ``multiple text`` field type presents a number of single line fields. -The number of fields is specified at design time. - - -likert scale field ------------------- - -The ``likert scale`` field type is a ``django.forms.ChoiceField``, -populated with choices specified at design time. The field template -``formly/templates/bootstrapform/field.html`` emits: - - - -for hooking in CSS design. The following sample CSS presents a Likert field -in familiar horizontal layout. You should add this (or similar) -CSS to your project to get Likert-scale presentation. - - form .likert-question { - list-style:none; - width:100%; - margin:0; - padding:0 0 35px; - display:block; - border-bottom:2px solid #efefef; - } - form .likert-question:last-of-type { - border-bottom:0; - } - form .likert-question:before { - content: ''; - position:relative; - top:13px; - left:13%; - display:block; - background-color:#dfdfdf; - height:4px; - width:75%; - } - form .likert-question li { - display:inline-block; - width:19%; - text-align:center; - vertical-align: top; - } - form .likert-question li input[type=radio] { - display:block; - position:relative; - top:0; - left:50%; - margin-left:-6px; - } - form .likert-question li label { - width:100%; - } - - -rating scale field ------------------- - -The ``rating scale`` field type is a ``django.forms.ChoiceField``, -populated with choices specified at design time. The field template -``formly/templates/bootstrapform/field.html`` emits: - - diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index a49524c..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,37 +0,0 @@ -====== -formly -====== - -`formly` is an app that provides an out of the box solution to building adhoc -forms to collect data from end users. It is multi-faceted in that it provides -interfaces for building multi-page forms, interfaces for executing the survey, -as well as views for reviewing results. - -Also, it is non-linear, meaning that you can route users taking the survey to -different pages based on what they answered on certain questions. This allows -you to create very rich surveys that dive deep on detail you care about while -not wasting the time of users who would otherwise have to go through questions -that do not apply to them. - -This project is brought to you by Midwest Communications. - - -Development ------------ - -The source repository can be found at https://github.com/eldarion/formly/ - - -Contents -======== - -.. toctree:: - :maxdepth: 1 - - changelog - installation - usage - fields - templates - authorization - callbacks diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 5f5e716..0000000 --- a/docs/installation.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. _installation: - -Installation -============ - -* Requirements: - * django-jsonfield==0.9.13 - -* Optional Requirements (to use the built in templates): - * pinax-theme-bootstrap (not required if you use different block names) - * django-forms-bootstrap (required for form rendering in templates) - -* To install:: - - pip install formly - -* Add ``'formly'`` to your ``INSTALLED_APPS`` setting:: - - INSTALLED_APPS = ( - # other apps - "formly", - ) - -* Mount the ``formly.urls`` somewhere:: - - urlpatterns = patterns("", - ... - url(r"^surveys/", include("formly.urls")), - ... - ) diff --git a/docs/templates.rst b/docs/templates.rst deleted file mode 100644 index 2d4261f..0000000 --- a/docs/templates.rst +++ /dev/null @@ -1,144 +0,0 @@ -.. _templates: - -Templates -========= - -``formly`` ships with some stock templates that are based on -``pinax-theme-bootstrap`` and ``django-forms-bootstrap``. You are not required -to use these of course and in case you are rolling your own templates, here -is what the views in ``formly`` expect. - - -``formly/design/choice_form.html`` ----------------------------------- - -:Context: ``form``, ``choice``, ``page`` -:Extends: ``formly/design/survey_edit_base.html`` - -This is the template that provides the ability to update the values for a -particular choice for a choice field. - - -``formly/design/field_confirm_delete.html`` -------------------------------------------- - -:Context: ``form``, ``field`` -:Extends: ``site_base.html`` - -This is the template is rendered to supply a delete confirmation form for -field deletion. - - -``formly/design/field_form.html`` ----------------------------------- - -:Context: ``form``, ``field``, ``page``, ``field_choice_form`` -:Extends: ``formly/design/survey_edit_base.html`` - -This is the template is rendered for a user interface to update a field. - - -``formly/design/fieldchoice_confirm_delete.html`` -------------------------------------------------- - -:Context: ``form``, ``fieldchoice`` -:Extends: ``site_base.html`` - -This is the template is rendered to supply a delete confirmation form for -field choice deletion. - - -``formly/design/page_confirm_delete.html`` ------------------------------------------- - -:Context: ``form``, ``page`` -:Extends: ``site_base.html`` - -This is the template is rendered to supply a delete confirmation form for -page deletion. - - -``formly/design/page_form.html`` --------------------------------- - -:Context: ``form``, ``page``, ``field_form`` -:Extends: ``formly/design/survey_edit_base.html`` - -This is the template is that displays the user interface for updating a -page object. - - -``formly/design/survey_confirm_delete.html`` --------------------------------------------- - -:Context: ``form``, ``survey`` -:Extends: ``site_base.html`` - -This is the template is rendered to supply a delete confirmation form for -survey deletion. - - -``formly/design/survey_detail.html`` ------------------------------------- - -:Context: ``survey`` -:Extends: ``site_base.html`` - -This template displays the detail for a survey. - - -``formly/design/survey_edit_base.html`` ---------------------------------------- - -:Context: ``page`` -:Extends: ``subnav_base.html`` -:Extended By: ``formly/design/choice_form.html``, ``formly/design/field_form.html``, ``formly/design/page_form.html`` - -This a base template to provide some common subnav. - - -``formly/design/survey_form.html`` ----------------------------------- - -:Context: ``form`` -:Extends: ``site_base.html`` - -This template hosts the creation form for creating a new survey object. - - -``formly/design/survey_list.html`` ----------------------------------- - -:Context: ``unpublished_surveys``, ``published_surveys`` -:Extends: ``site_base.html`` - -This template receives all surveys in the system split between two context objects, -one for published surveys and the other for unpublished surveys. - - -``formly/results/home.html`` ----------------------------- - -:Context: ``survey`` -:Extends: ``site_base.html`` - -A template for displaying the results of a given survey. - - -``formly/run/page.html`` ------------------------- - -:Context: ``form``, ``page`` -:Extends: ``site_base.html`` - -This template is rendered for the end user to complete a particular survey, it is always -rendered with the appropriate page for the user. - - -``formly/bootstrapform/field.html`` ------------------------- - -:Context: ``field`` - -This modified ``django-bootstrap-form`` template renders the various field types, -including special handling for Likert and Rating fields. diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index 5154833..0000000 --- a/docs/usage.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. _usage: - -Usage -===== - -``formly`` is designed to be pretty plug-and-play, in that after you install -it, using it should be as simple as creating and publishing surveys through -the web interface. - -After installation, browse to whereever you mounted the urls for `formly` and -you'll see an interface to be able to create a new survey. From here you can -create a survey and begin editing pages. Pages in `formly` represent each -step of the survey. The user will be guided through each page of the survey -in the appropriate order saving each page at a time. - -For each page, you can give a title and add as many fields as you desire. If -the field is a type that requires choices, then you will have the option -on the fields detail/edit form to add choices. An optional value that can -be supplied for a choice answer is the page to redirect the user to if they -select that answer. - -If you have multiple choice fields in a single page with conflicting page -routing, `formly` resolves to the first page it encounters. For example, if -you had a question that had choice B route to page 3 and another question -later in the form that had choice C route to page 5 and the user answered -both questions with choice B and choice C, the user would go to page 3 -next. Keep this in mind when building surveys. diff --git a/formly/auth_backend.py b/formly/auth_backend.py index 00f7935..934c7dc 100644 --- a/formly/auth_backend.py +++ b/formly/auth_backend.py @@ -38,7 +38,7 @@ def has_perm(self, user, perm, obj=None): "formly.view_results" ] if perm in permissions: - return user.is_authenticated() + return user.is_authenticated if perm in survey_permissions: return obj and user == obj.creator if perm == "formly.delete_object": diff --git a/formly/callbacks.py b/formly/callbacks.py index 7bf5675..a7c8452 100644 --- a/formly/callbacks.py +++ b/formly/callbacks.py @@ -1,4 +1,4 @@ -from django.core.urlresolvers import reverse +from django.urls import reverse def survey_complete_redirect(survey): diff --git a/formly/fields.py b/formly/fields.py index 328304e..5f66812 100644 --- a/formly/fields.py +++ b/formly/fields.py @@ -10,7 +10,7 @@ def __init__(self, *args, **kwargs): self.maximum_choices = kwargs.pop("maximum_choices") self.default_error_messages.update({ - 'maximum_choices': _('You may select at most %(maximum)d choices (%(selected)d selected)') + "maximum_choices": _("You may select at most %(maximum)d choices (%(selected)d selected)") }) super(LimitedMultipleChoiceField, self).__init__(*args, **kwargs) @@ -21,9 +21,9 @@ def validate(self, value): selected_count = len(value) if self.maximum_choices and selected_count > self.maximum_choices: raise ValidationError( - self.error_messages['maximum_choices'], - code='maximum_choices', - params={'maximum': self.maximum_choices, 'selected': selected_count}, + self.error_messages["maximum_choices"], + code="maximum_choices", + params={"maximum": self.maximum_choices, "selected": selected_count}, ) diff --git a/formly/forms/design.py b/formly/forms/design.py index fbd7279..a3f57a9 100644 --- a/formly/forms/design.py +++ b/formly/forms/design.py @@ -1,7 +1,7 @@ from django import forms from ..hooks import hookset -from ..models import Survey, Page, Field, FieldChoice, OrdinalScale +from ..models import Field, FieldChoice, OrdinalScale, Page, Survey class SurveyCreateForm(forms.ModelForm): diff --git a/formly/forms/run.py b/formly/forms/run.py index 1366954..f0fcfb1 100644 --- a/formly/forms/run.py +++ b/formly/forms/run.py @@ -1,6 +1,6 @@ from django import forms -from formly.models import SurveyResult, Field, FieldResult +from formly.models import Field, FieldResult, SurveyResult class FieldResultMixin(object): diff --git a/formly/forms/widgets.py b/formly/forms/widgets.py index c1e55e8..a36a97f 100644 --- a/formly/forms/widgets.py +++ b/formly/forms/widgets.py @@ -17,7 +17,9 @@ def decompress(self, value): def format_output(self, rendered_widgets): return render_to_string( "formly/run/_multiple_input.html", - {"inputs": rendered_widgets} + context={ + "inputs": rendered_widgets + } ) diff --git a/formly/migrations/0001_initial.py b/formly/migrations/0001_initial.py index 47bc778..b21e830 100644 --- a/formly/migrations/0001_initial.py +++ b/formly/migrations/0001_initial.py @@ -34,8 +34,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('label', models.CharField(max_length=100)), - ('field', models.ForeignKey(related_name='choices', to='formly.Field')), - ('target', models.ForeignKey(related_name='target_choices', blank=True, to='formly.Field', null=True)), + ('field', models.ForeignKey(related_name='choices', to='formly.Field', on_delete=models.CASCADE)), + ('target', models.ForeignKey(related_name='target_choices', blank=True, to='formly.Field', null=True, on_delete=models.SET_NULL)), ], ), migrations.CreateModel( @@ -68,7 +68,7 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(default=django.utils.timezone.now)), ('updated', models.DateTimeField(default=django.utils.timezone.now)), ('published', models.DateTimeField(null=True, blank=True)), - ('creator', models.ForeignKey(related_name='surveys', to=settings.AUTH_USER_MODEL)), + ('creator', models.ForeignKey(related_name='surveys', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), migrations.CreateModel( @@ -76,49 +76,49 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('date_submitted', models.DateTimeField(default=django.utils.timezone.now)), - ('survey', models.ForeignKey(related_name='survey_results', to='formly.Survey')), - ('user', models.ForeignKey(related_name='survey_results', to=settings.AUTH_USER_MODEL)), + ('survey', models.ForeignKey(related_name='survey_results', to='formly.Survey', on_delete=models.CASCADE)), + ('user', models.ForeignKey(related_name='survey_results', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), migrations.AddField( model_name='page', name='survey', - field=models.ForeignKey(related_name='pages', to='formly.Survey'), + field=models.ForeignKey(related_name='pages', to='formly.Survey', on_delete=models.CASCADE), ), migrations.AddField( model_name='page', name='target', - field=models.ForeignKey(blank=True, to='formly.Page', null=True), + field=models.ForeignKey(blank=True, to='formly.Page', null=True, on_delete=models.SET_NULL), ), migrations.AddField( model_name='fieldresult', name='page', - field=models.ForeignKey(related_name='results', to='formly.Page'), + field=models.ForeignKey(related_name='results', to='formly.Page', on_delete=models.CASCADE), ), migrations.AddField( model_name='fieldresult', name='question', - field=models.ForeignKey(related_name='results', to='formly.Field'), + field=models.ForeignKey(related_name='results', to='formly.Field', on_delete=models.CASCADE), ), migrations.AddField( model_name='fieldresult', name='result', - field=models.ForeignKey(related_name='results', to='formly.SurveyResult'), + field=models.ForeignKey(related_name='results', to='formly.SurveyResult', on_delete=models.CASCADE), ), migrations.AddField( model_name='fieldresult', name='survey', - field=models.ForeignKey(related_name='results', to='formly.Survey'), + field=models.ForeignKey(related_name='results', to='formly.Survey', on_delete=models.CASCADE), ), migrations.AddField( model_name='field', name='page', - field=models.ForeignKey(related_name='fields', blank=True, to='formly.Page', null=True), + field=models.ForeignKey(related_name='fields', blank=True, to='formly.Page', null=True, on_delete=models.SET_NULL), ), migrations.AddField( model_name='field', name='survey', - field=models.ForeignKey(related_name='fields', to='formly.Survey'), + field=models.ForeignKey(related_name='fields', to='formly.Survey', on_delete=models.CASCADE), ), migrations.AlterUniqueTogether( name='page', diff --git a/formly/migrations/0005_field_scale.py b/formly/migrations/0005_field_scale.py index 59325e8..19979b9 100644 --- a/formly/migrations/0005_field_scale.py +++ b/formly/migrations/0005_field_scale.py @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='field', name='scale', - field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='formly.LikertScale'), + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fields', to='formly.LikertScale'), ), ] diff --git a/formly/migrations/0006_auto_20161206_1415.py b/formly/migrations/0006_auto_20161206_1415.py index 99cc3c3..f3b96d4 100644 --- a/formly/migrations/0006_auto_20161206_1415.py +++ b/formly/migrations/0006_auto_20161206_1415.py @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='field', name='scale', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='formly.LikertScale'), + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fields', to='formly.LikertScale'), ), ] diff --git a/formly/migrations/0008_auto_20170609_0800.py b/formly/migrations/0008_auto_20170609_0800.py index 3700550..8623954 100644 --- a/formly/migrations/0008_auto_20170609_0800.py +++ b/formly/migrations/0008_auto_20170609_0800.py @@ -10,6 +10,7 @@ class Migration(migrations.Migration): dependencies = [ ('formly', '0007_help_text_and_label_to_textfield'), ] + atomic = False operations = [ migrations.RenameModel( diff --git a/formly/models.py b/formly/models.py index bc894af..1550a95 100644 --- a/formly/models.py +++ b/formly/models.py @@ -3,10 +3,10 @@ from django import forms from django.conf import settings from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse from django.db import models from django.db.models import Max from django.template.defaultfilters import slugify +from django.urls import reverse from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible @@ -34,12 +34,12 @@ def __str__(self): @python_2_unicode_compatible class OrdinalChoice(models.Model): - scale = models.ForeignKey(OrdinalScale, related_name="choices") + scale = models.ForeignKey(OrdinalScale, related_name="choices", on_delete=models.CASCADE) label = models.CharField(max_length=100) score = models.IntegerField() def __str__(self): - return "{} ({})".format(self.label, self.score) + return "{} ({})".format(self.label, self.score) # pragma: no cover class Meta: unique_together = [("scale", "score"), ("scale", "label")] @@ -48,7 +48,7 @@ class Meta: @python_2_unicode_compatible class Survey(models.Model): name = models.CharField(max_length=255) - creator = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="surveys") + creator = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="surveys", on_delete=models.CASCADE) created = models.DateTimeField(default=timezone.now) updated = models.DateTimeField(default=timezone.now) published = models.DateTimeField(null=True, blank=True) @@ -59,10 +59,13 @@ def save(self, *args, **kwargs): return super(Survey, self).save(*args, **kwargs) def __str__(self): - return self.name + return self.name # pragma: no cover def get_absolute_url(self): - return reverse("formly_dt_survey_detail", kwargs={"pk": self.pk}) + return reverse("formly:survey_detail", kwargs={"pk": self.pk}) + + def get_run_url(self): + return reverse("formly:take_survey", kwargs={"pk": self.pk}) def duplicate(self): # @@@ This could like use with some refactoring survey = Survey.objects.get(pk=self.pk) @@ -137,11 +140,11 @@ def publish(self): @python_2_unicode_compatible class Page(models.Model): - survey = models.ForeignKey(Survey, related_name="pages") + survey = models.ForeignKey(Survey, related_name="pages", on_delete=models.CASCADE) page_num = models.PositiveIntegerField(null=True, blank=True) subtitle = models.CharField(max_length=255, blank=True) # Should be null when a FieldChoice on it's last field has a target. - target = models.ForeignKey("self", null=True, blank=True) + target = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL) class Meta: unique_together = [ @@ -156,7 +159,7 @@ def save(self, *args, **kwargs): return super(Page, self).save(*args, **kwargs) def __str__(self): - return self.label() + return self.label() # pragma: no cover def label(self): if self.subtitle: @@ -165,35 +168,7 @@ def label(self): return "Page %d" % self.page_num def get_absolute_url(self): - return reverse("formly_dt_page_detail", kwargs={"pk": self.pk}) - - def move_up(self): - try: - other_field = self.survey.pages.order_by("-page_num").filter( - page_num__lt=self.page_num - )[0] - existing = self.page_num - other = other_field.page_num - self.page_num = other - other_field.page_num = existing - other_field.save() - self.save() - except IndexError: - return - - def move_down(self): - try: - other_field = self.page.fields.order_by("page_num").filter( - page_num__gt=self.page_num - )[0] - existing = self.page_num - other = other_field.page_num - self.page_num = other - other_field.page_num = existing - other_field.save() - self.save() - except IndexError: - return + return reverse("formly:page_detail", kwargs={"pk": self.pk}) def next_page(self, user): target = self @@ -249,11 +224,11 @@ class Field(models.Model): (RATING_FIELD, "Rating Scale") ] - survey = models.ForeignKey(Survey, related_name="fields") # Denorm - page = models.ForeignKey(Page, null=True, blank=True, related_name="fields") + survey = models.ForeignKey(Survey, related_name="fields", on_delete=models.CASCADE) # Denorm + page = models.ForeignKey(Page, null=True, blank=True, related_name="fields", on_delete=models.SET_NULL) label = models.TextField() field_type = models.IntegerField(choices=FIELD_TYPE_CHOICES) - scale = models.ForeignKey(OrdinalScale, default=None, null=True, blank=True, related_name="fields") + scale = models.ForeignKey(OrdinalScale, default=None, null=True, blank=True, related_name="fields", on_delete=models.SET_NULL) help_text = models.TextField(blank=True) ordinal = models.IntegerField() maximum_choices = models.IntegerField(null=True, blank=True) @@ -271,6 +246,9 @@ class Field(models.Model): # ) def save(self, *args, **kwargs): + if not self.ordinal: + # Set ordinal, since full_clean() will fail if not set + self.ordinal = 1 self.full_clean() if not self.pk and self.page is not None: self.ordinal = (self.page.fields.aggregate( @@ -315,7 +293,7 @@ def __str__(self): ) def get_absolute_url(self): - return reverse("formly_dt_field_update", kwargs={"pk": self.pk}) + return reverse("formly:field_update", kwargs={"pk": self.pk}) @property def needs_choices(self): @@ -430,9 +408,9 @@ def _get_field_class(self, choices): @python_2_unicode_compatible class FieldChoice(models.Model): - field = models.ForeignKey(Field, related_name="choices") + field = models.ForeignKey(Field, related_name="choices", on_delete=models.CASCADE) label = models.CharField(max_length=100) - target = models.ForeignKey(Field, null=True, blank=True, related_name="target_choices") + target = models.ForeignKey(Field, null=True, blank=True, related_name="target_choices", on_delete=models.SET_NULL) def clean(self): super(FieldChoice, self).clean() @@ -452,8 +430,8 @@ def __str__(self): @python_2_unicode_compatible class SurveyResult(models.Model): - survey = models.ForeignKey(Survey, related_name="survey_results") - user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="survey_results") + survey = models.ForeignKey(Survey, related_name="survey_results", on_delete=models.CASCADE) + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="survey_results", on_delete=models.CASCADE) date_submitted = models.DateTimeField(default=timezone.now) def get_absolute_url(self): @@ -465,10 +443,10 @@ def __str__(self): @python_2_unicode_compatible class FieldResult(models.Model): - survey = models.ForeignKey(Survey, related_name="results") # Denorm - page = models.ForeignKey(Page, related_name="results") # Denorm - result = models.ForeignKey(SurveyResult, related_name="results") - question = models.ForeignKey(Field, related_name="results") + survey = models.ForeignKey(Survey, related_name="results", on_delete=models.CASCADE) # Denorm + page = models.ForeignKey(Page, related_name="results", on_delete=models.CASCADE) # Denorm + result = models.ForeignKey(SurveyResult, related_name="results", on_delete=models.CASCADE) + question = models.ForeignKey(Field, related_name="results", on_delete=models.CASCADE) upload = models.FileField(upload_to="formly/", blank=True) answer = JSONField(blank=True) # @@@ I think this should be something different than a string diff --git a/formly/receivers.py b/formly/receivers.py index 894605c..d42877d 100644 --- a/formly/receivers.py +++ b/formly/receivers.py @@ -1,5 +1,5 @@ -from django.dispatch import receiver from django.db.models import signals +from django.dispatch import receiver from formly.models import Survey diff --git a/formly/templates/formly/design/_field_edit.html b/formly/templates/formly/design/_field_edit.html index a28ed7c..4d8a9c9 100644 --- a/formly/templates/formly/design/_field_edit.html +++ b/formly/templates/formly/design/_field_edit.html @@ -2,19 +2,19 @@ {% if field_form %}

Edit Field

-
+ {% csrf_token %} {{ field_form|bootstrap }}
- Delete + Delete
{% if selected_field.needs_choices %}

Choices

-
+ {% csrf_token %} {{ field_choice_form|bootstrap }} @@ -23,7 +23,7 @@

Choices

{% for choice in selected_field.choices.all %}
- + {{ choice.label }}
{% endfor %} diff --git a/formly/templates/formly/design/_fields.html b/formly/templates/formly/design/_fields.html index ec4cc29..678a102 100644 --- a/formly/templates/formly/design/_fields.html +++ b/formly/templates/formly/design/_fields.html @@ -1,6 +1,6 @@ {% if selected_page %}

Fields

- + {% csrf_token %} @@ -11,20 +11,20 @@

Fields

- + {{ field.label }} diff --git a/formly/templates/formly/design/_likert_scale.html b/formly/templates/formly/design/_likert_scale.html index 4fc1e0f..e21e1cf 100644 --- a/formly/templates/formly/design/_likert_scale.html +++ b/formly/templates/formly/design/_likert_scale.html @@ -1,5 +1,5 @@
- +
diff --git a/formly/templates/formly/design/_likert_scale_form.html b/formly/templates/formly/design/_likert_scale_form.html index 92d265e..1690e95 100644 --- a/formly/templates/formly/design/_likert_scale_form.html +++ b/formly/templates/formly/design/_likert_scale_form.html @@ -1,5 +1,5 @@ {% load bootstrap %} -
+ {% csrf_token %} {{ likert_scale_form|bootstrap }} diff --git a/formly/templates/formly/design/_pages.html b/formly/templates/formly/design/_pages.html index dff5fff..eb9ee41 100644 --- a/formly/templates/formly/design/_pages.html +++ b/formly/templates/formly/design/_pages.html @@ -1,6 +1,6 @@ {% if selected_survey %}

Pages

- + {% csrf_token %}
diff --git a/formly/templates/formly/design/_rating_scale.html b/formly/templates/formly/design/_rating_scale.html index b358559..2bb288c 100644 --- a/formly/templates/formly/design/_rating_scale.html +++ b/formly/templates/formly/design/_rating_scale.html @@ -1,5 +1,5 @@
- +
diff --git a/formly/templates/formly/design/_rating_scale_form.html b/formly/templates/formly/design/_rating_scale_form.html index 16f0022..1eb9245 100644 --- a/formly/templates/formly/design/_rating_scale_form.html +++ b/formly/templates/formly/design/_rating_scale_form.html @@ -1,5 +1,5 @@ {% load bootstrap %} -
+ {% csrf_token %} {{ rating_scale_form|bootstrap }} diff --git a/formly/templates/formly/design/_survey.html b/formly/templates/formly/design/_survey.html index d1aa761..37a7ffc 100644 --- a/formly/templates/formly/design/_survey.html +++ b/formly/templates/formly/design/_survey.html @@ -6,30 +6,30 @@

{{ survey.name }}

-
+ {% csrf_token %}