├── .coveragerc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.txt
├── MANIFEST.in
├── Makefile
├── README.rst
├── example
├── core
│ ├── __init__.py
│ ├── admin.py
│ ├── forms.py
│ ├── models.py
│ ├── static
│ │ └── core
│ │ │ ├── css
│ │ │ ├── bootstrap-responsive_2.3.2.css
│ │ │ └── bootstrap_2.3.2.css
│ │ │ └── js
│ │ │ └── bootstrap-tab.min.js
│ ├── templates
│ │ └── home.html
│ ├── tests.py
│ └── views.py
├── example
│ ├── __init__.py
│ ├── settings
│ │ ├── __init__.py
│ │ ├── common.py
│ │ └── local.py-dist
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── parsley
└── requirements.txt
├── parsley
├── __init__.py
├── decorators.py
├── mixins.py
├── models.py
├── static
│ └── parsley
│ │ └── js
│ │ ├── parsley.django-admin.js
│ │ ├── parsley.js
│ │ └── parsley.min.js
├── templatetags
│ ├── __init__.py
│ └── parsley.py
├── tests
│ ├── __init__.py
│ ├── admin.py
│ ├── forms.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── widgets.py
├── runtests.py
├── setup.py
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | source = parsley
3 | branch = 1
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | *egg-info/
3 | *.egg/
4 | .coverage
5 | .tox
6 | htmlcov/
7 | build/
8 | _build/
9 | *.pyc
10 | test.db
11 | example/example/settings/local.py
12 | /.idea
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | python:
4 | - 2.7
5 | - 3.4
6 | - 3.5
7 | - 3.6
8 |
9 | env:
10 | - DJANGO='Django>=1.8,<1.9'
11 | - DJANGO='Django>=1.9,<1.10'
12 | - DJANGO='Django>=1.10,<1.11'
13 | - DJANGO='Django>=1.11,<2.0'
14 |
15 | install:
16 | - pip install $DJANGO
17 | - pip install coverage coveralls
18 |
19 | script: coverage run setup.py test
20 |
21 | matrix:
22 | exclude:
23 | - python: 3.6
24 | env: DJANGO='Django>=1.8,<1.9'
25 | - python: 3.6
26 | env: DJANGO='Django>=1.9,<1.10'
27 | - python: 3.6
28 | env: DJANGO='Django>=1.10,<1.11'
29 |
30 | after_success: coveralls
31 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Django-parsley changelog
2 |
3 | ## 0.7
4 |
5 | - Added support for django 1.10 and 1.11
6 | - Dropped support for django 1.3 - 1.7
7 | - Fixed issue where django version would be forced to 1.8 when installing django-parsley. More details here.
8 | - Added parsleyfy template tag
9 |
10 | ## 0.6
11 |
12 | - Added checkbox and radio button validation support.
13 | - Updated parsley.js, included with this library, to 2.0.7
14 | - Changed error message attributes to confirm with parsley.js 2.x. More details here.
15 | - Changed RegexField attributes to confirm with parsley.js 2.x. More details here.
16 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Agiliq(http://www.agiliq.com/)
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | Neither the name of the Agiliq nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include parsley/static *
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: check-ver
2 |
3 | all: init test
4 |
5 | init:
6 | pip install tox coverage
7 |
8 | clean:
9 | pyclean .
10 | rm -rf __pycache__ .tox dist django_parsley.egg-info
11 |
12 | test:
13 | coverage erase
14 | tox
15 | coverage html
16 |
17 | fast_test:
18 | coverage run setup.py test
19 | coverage report
20 | coverage html
21 |
22 | release: check-ver
23 | @echo ${VER}
24 | sed -i "s/^VERSION = .*/VERSION = '${VER}'/g" setup.py
25 | git add setup.py
26 | git commit -m "version bump"
27 | git tag v${VER}
28 | git push --tags
29 | python setup.py sdist upload
30 |
31 | check-ver:
32 | ifndef VER
33 | $(error VER is undefined)
34 | endif
35 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | django-parsley
2 | ==============
3 |
4 | .. image:: https://img.shields.io/pypi/dm/django-parsley.svg
5 | :target: https://pypi.python.org/pypi/django-parsley
6 | :alt: Downloads
7 |
8 | .. image:: https://img.shields.io/pypi/v/django-parsley.svg
9 | :target: https://pypi.python.org/pypi/django-parsley
10 | :alt: Latest Release
11 |
12 | .. image:: https://travis-ci.org/agiliq/Django-parsley.png?branch=master
13 | :target: https://travis-ci.org/agiliq/Django-parsley
14 | :alt: Build Status
15 |
16 | .. image:: https://coveralls.io/repos/agiliq/Django-parsley/badge.png?branch=master
17 | :target: https://coveralls.io/r/agiliq/Django-parsley
18 | :alt: Coverage Status
19 |
20 | What is it?
21 | -----------
22 |
23 | `Parsleyjs`_ is a JavaScript library to do client side data validations.
24 | It does this in a non-intrusive way via adding a ``data-parsley-*`` attributes to form fields.
25 |
26 | When you define a Django form, you get server side validations for free using
27 | the form field attributes. Django-parsley adds these validations to client side, by tagging your form with ``data-parsley-*`` attributes.
28 |
29 | Parsley plays well with ``crispy-forms`` et all.
30 |
31 | Demo
32 | ----
33 | `Demo`_ at https://agiliq.com/demo/parsley/
34 |
35 |
36 | Installation
37 | ------------
38 |
39 | 1. pip install ``django-parsley`` (or add to your requirements.txt)
40 | 2. add ``parsley`` to your ``INSTALLED_APPS`` (required for static files)
41 |
42 | Upgrading
43 | ---------
44 |
45 | Upgrading from 0.2 to 0.3:
46 | ..........................
47 |
48 | If you're using parsley.js 1.x, make sure to set the ``parsley_namespace`` Meta attribute
49 | to ``parsley`` for backward compatibility.
50 |
51 | .. code-block:: python
52 |
53 | class Meta:
54 | parsley_namespace = 'parsley'
55 |
56 | Usage
57 | -----
58 |
59 | ``parsley`` provides a single class decorator called ``parsleyfy``. Decorate your ``Form`` or ``ModelForm`` with ``parsleyfy`` to get the validations.
60 |
61 | Eg.
62 |
63 | .. code-block:: python
64 |
65 | from parsley.decorators import parsleyfy
66 |
67 |
68 | @parsleyfy
69 | class FieldTypeForm(forms.Form):
70 | name = forms.CharField(min_length=3, max_length=30)
71 | url = forms.URLField()
72 | url2 = forms.URLField(required=False)
73 | email = forms.EmailField()
74 | email2 = forms.EmailField(required=False)
75 | age = forms.IntegerField()
76 | income = forms.DecimalField()
77 |
78 | Your rendered form's HTML will look like this
79 |
80 | .. code-block:: html
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Note the ``data-parsley-*`` attributes.
91 |
92 | You could also do
93 |
94 | .. code-block:: python
95 |
96 | FieldTypeForm = parsleyfy(FieldTypeForm)
97 |
98 | Which is the same thing.
99 |
100 | Put this form inside a
101 |
102 | .. code-block:: html
103 |
104 |
107 |
108 | .. note:: Make sure `jquery.js` and `parsley.js` are included in the template.
109 |
110 | Admin
111 | -----
112 |
113 | To add parsley validations to admin, use the ``ParsleyAdminMixin`` with your ``ModelAdmin`` like so:
114 |
115 | .. code-block:: python
116 |
117 | class StudentAdmin(ParsleyAdminMixin, admin.ModelAdmin):
118 | pass
119 |
120 | .. note:: Use the `parsley.django-admin.js` helper from parsley static to auto-validate admin forms.
121 |
122 | Advanced Usage
123 | --------------
124 |
125 | In addition to the default validators if you want to add extra client side validations
126 | or if you want to add custom validators, add a ``parsley_extras`` Meta attribute. For e.g
127 | if you wanted to add ``minlength`` and ``equalto`` validations on a ``PasswordChangeForm``:
128 |
129 | .. code-block:: python
130 |
131 | @parsleyfy
132 | class PasswordChangeForm(BasePasswordChangeForm):
133 | class Meta:
134 | parsley_extras = {
135 | 'new_password1': {
136 | 'minlength': "5",
137 | },
138 | 'new_password2': {
139 | 'equalto': "new_password1",
140 | 'error-message': "Your passwords do not match.",
141 | },
142 | }
143 |
144 | To use a custom namespace for parsley (e.g when using parsley with the ``data-parsley-namespace``
145 | option) you can provide a namespace by using the ``parsley_namespace`` Meta attribute.
146 |
147 | .. code-block:: python
148 |
149 | class Meta:
150 | parsley_namespace = 'custom'
151 |
152 | License
153 | -------
154 |
155 | 3 Clause BSD.
156 |
157 | Bug report and Help
158 | -------------------
159 |
160 | For bug reports open a github ticket. Patches gratefully accepted. Need help? `Contact us here`_
161 |
162 | .. _parsleyjs: http://parsleyjs.org/
163 | .. _contact us here: http://agiliq.com/contactus
164 | .. _demo: http://agiliq.com/demo/parsley/
165 |
--------------------------------------------------------------------------------
/example/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agiliq/Django-parsley/f1901625034029a09a926e39ed7cff24b4d512b2/example/core/__init__.py
--------------------------------------------------------------------------------
/example/core/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from django.contrib.auth.models import User
4 | from django.contrib.auth.admin import UserAdmin
5 |
6 | from parsley.mixins import ParsleyAdminMixin
7 |
8 |
9 | class MyUserAdmin(ParsleyAdminMixin, UserAdmin):
10 |
11 | class Media:
12 | js = (
13 | "//code.jquery.com/jquery-latest.min.js",
14 | "parsley/js/parsley.min.js",
15 | "parsley/js/parsley.django-admin.js"
16 | )
17 |
18 |
19 | admin.site.unregister(User)
20 | admin.site.register(User, MyUserAdmin)
21 |
--------------------------------------------------------------------------------
/example/core/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from parsley.decorators import parsleyfy
3 |
4 |
5 | @parsleyfy
6 | class FieldTypeForm(forms.Form):
7 | name = forms.CharField(min_length=3, max_length=30)
8 | url = forms.URLField()
9 | url2 = forms.URLField(required=False)
10 | email = forms.EmailField()
11 | email2 = forms.EmailField(required=False)
12 | age = forms.IntegerField()
13 | income = forms.DecimalField()
14 |
--------------------------------------------------------------------------------
/example/core/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
--------------------------------------------------------------------------------
/example/core/static/core/css/bootstrap-responsive_2.3.2.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.3.2
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */
10 |
11 | .clearfix {
12 | *zoom: 1;
13 | }
14 |
15 | .clearfix:before,
16 | .clearfix:after {
17 | display: table;
18 | line-height: 0;
19 | content: "";
20 | }
21 |
22 | .clearfix:after {
23 | clear: both;
24 | }
25 |
26 | .hide-text {
27 | font: 0/0 a;
28 | color: transparent;
29 | text-shadow: none;
30 | background-color: transparent;
31 | border: 0;
32 | }
33 |
34 | .input-block-level {
35 | display: block;
36 | width: 100%;
37 | min-height: 30px;
38 | -webkit-box-sizing: border-box;
39 | -moz-box-sizing: border-box;
40 | box-sizing: border-box;
41 | }
42 |
43 | @-ms-viewport {
44 | width: device-width;
45 | }
46 |
47 | .hidden {
48 | display: none;
49 | visibility: hidden;
50 | }
51 |
52 | .visible-phone {
53 | display: none !important;
54 | }
55 |
56 | .visible-tablet {
57 | display: none !important;
58 | }
59 |
60 | .hidden-desktop {
61 | display: none !important;
62 | }
63 |
64 | .visible-desktop {
65 | display: inherit !important;
66 | }
67 |
68 | @media (min-width: 768px) and (max-width: 979px) {
69 | .hidden-desktop {
70 | display: inherit !important;
71 | }
72 | .visible-desktop {
73 | display: none !important ;
74 | }
75 | .visible-tablet {
76 | display: inherit !important;
77 | }
78 | .hidden-tablet {
79 | display: none !important;
80 | }
81 | }
82 |
83 | @media (max-width: 767px) {
84 | .hidden-desktop {
85 | display: inherit !important;
86 | }
87 | .visible-desktop {
88 | display: none !important;
89 | }
90 | .visible-phone {
91 | display: inherit !important;
92 | }
93 | .hidden-phone {
94 | display: none !important;
95 | }
96 | }
97 |
98 | .visible-print {
99 | display: none !important;
100 | }
101 |
102 | @media print {
103 | .visible-print {
104 | display: inherit !important;
105 | }
106 | .hidden-print {
107 | display: none !important;
108 | }
109 | }
110 |
111 | @media (min-width: 1200px) {
112 | .row {
113 | margin-left: -30px;
114 | *zoom: 1;
115 | }
116 | .row:before,
117 | .row:after {
118 | display: table;
119 | line-height: 0;
120 | content: "";
121 | }
122 | .row:after {
123 | clear: both;
124 | }
125 | [class*="span"] {
126 | float: left;
127 | min-height: 1px;
128 | margin-left: 30px;
129 | }
130 | .container,
131 | .navbar-static-top .container,
132 | .navbar-fixed-top .container,
133 | .navbar-fixed-bottom .container {
134 | width: 1170px;
135 | }
136 | .span12 {
137 | width: 1170px;
138 | }
139 | .span11 {
140 | width: 1070px;
141 | }
142 | .span10 {
143 | width: 970px;
144 | }
145 | .span9 {
146 | width: 870px;
147 | }
148 | .span8 {
149 | width: 770px;
150 | }
151 | .span7 {
152 | width: 670px;
153 | }
154 | .span6 {
155 | width: 570px;
156 | }
157 | .span5 {
158 | width: 470px;
159 | }
160 | .span4 {
161 | width: 370px;
162 | }
163 | .span3 {
164 | width: 270px;
165 | }
166 | .span2 {
167 | width: 170px;
168 | }
169 | .span1 {
170 | width: 70px;
171 | }
172 | .offset12 {
173 | margin-left: 1230px;
174 | }
175 | .offset11 {
176 | margin-left: 1130px;
177 | }
178 | .offset10 {
179 | margin-left: 1030px;
180 | }
181 | .offset9 {
182 | margin-left: 930px;
183 | }
184 | .offset8 {
185 | margin-left: 830px;
186 | }
187 | .offset7 {
188 | margin-left: 730px;
189 | }
190 | .offset6 {
191 | margin-left: 630px;
192 | }
193 | .offset5 {
194 | margin-left: 530px;
195 | }
196 | .offset4 {
197 | margin-left: 430px;
198 | }
199 | .offset3 {
200 | margin-left: 330px;
201 | }
202 | .offset2 {
203 | margin-left: 230px;
204 | }
205 | .offset1 {
206 | margin-left: 130px;
207 | }
208 | .row-fluid {
209 | width: 100%;
210 | *zoom: 1;
211 | }
212 | .row-fluid:before,
213 | .row-fluid:after {
214 | display: table;
215 | line-height: 0;
216 | content: "";
217 | }
218 | .row-fluid:after {
219 | clear: both;
220 | }
221 | .row-fluid [class*="span"] {
222 | display: block;
223 | float: left;
224 | width: 100%;
225 | min-height: 30px;
226 | margin-left: 2.564102564102564%;
227 | *margin-left: 2.5109110747408616%;
228 | -webkit-box-sizing: border-box;
229 | -moz-box-sizing: border-box;
230 | box-sizing: border-box;
231 | }
232 | .row-fluid [class*="span"]:first-child {
233 | margin-left: 0;
234 | }
235 | .row-fluid .controls-row [class*="span"] + [class*="span"] {
236 | margin-left: 2.564102564102564%;
237 | }
238 | .row-fluid .span12 {
239 | width: 100%;
240 | *width: 99.94680851063829%;
241 | }
242 | .row-fluid .span11 {
243 | width: 91.45299145299145%;
244 | *width: 91.39979996362975%;
245 | }
246 | .row-fluid .span10 {
247 | width: 82.90598290598291%;
248 | *width: 82.8527914166212%;
249 | }
250 | .row-fluid .span9 {
251 | width: 74.35897435897436%;
252 | *width: 74.30578286961266%;
253 | }
254 | .row-fluid .span8 {
255 | width: 65.81196581196582%;
256 | *width: 65.75877432260411%;
257 | }
258 | .row-fluid .span7 {
259 | width: 57.26495726495726%;
260 | *width: 57.21176577559556%;
261 | }
262 | .row-fluid .span6 {
263 | width: 48.717948717948715%;
264 | *width: 48.664757228587014%;
265 | }
266 | .row-fluid .span5 {
267 | width: 40.17094017094017%;
268 | *width: 40.11774868157847%;
269 | }
270 | .row-fluid .span4 {
271 | width: 31.623931623931625%;
272 | *width: 31.570740134569924%;
273 | }
274 | .row-fluid .span3 {
275 | width: 23.076923076923077%;
276 | *width: 23.023731587561375%;
277 | }
278 | .row-fluid .span2 {
279 | width: 14.52991452991453%;
280 | *width: 14.476723040552828%;
281 | }
282 | .row-fluid .span1 {
283 | width: 5.982905982905983%;
284 | *width: 5.929714493544281%;
285 | }
286 | .row-fluid .offset12 {
287 | margin-left: 105.12820512820512%;
288 | *margin-left: 105.02182214948171%;
289 | }
290 | .row-fluid .offset12:first-child {
291 | margin-left: 102.56410256410257%;
292 | *margin-left: 102.45771958537915%;
293 | }
294 | .row-fluid .offset11 {
295 | margin-left: 96.58119658119658%;
296 | *margin-left: 96.47481360247316%;
297 | }
298 | .row-fluid .offset11:first-child {
299 | margin-left: 94.01709401709402%;
300 | *margin-left: 93.91071103837061%;
301 | }
302 | .row-fluid .offset10 {
303 | margin-left: 88.03418803418803%;
304 | *margin-left: 87.92780505546462%;
305 | }
306 | .row-fluid .offset10:first-child {
307 | margin-left: 85.47008547008548%;
308 | *margin-left: 85.36370249136206%;
309 | }
310 | .row-fluid .offset9 {
311 | margin-left: 79.48717948717949%;
312 | *margin-left: 79.38079650845607%;
313 | }
314 | .row-fluid .offset9:first-child {
315 | margin-left: 76.92307692307693%;
316 | *margin-left: 76.81669394435352%;
317 | }
318 | .row-fluid .offset8 {
319 | margin-left: 70.94017094017094%;
320 | *margin-left: 70.83378796144753%;
321 | }
322 | .row-fluid .offset8:first-child {
323 | margin-left: 68.37606837606839%;
324 | *margin-left: 68.26968539734497%;
325 | }
326 | .row-fluid .offset7 {
327 | margin-left: 62.393162393162385%;
328 | *margin-left: 62.28677941443899%;
329 | }
330 | .row-fluid .offset7:first-child {
331 | margin-left: 59.82905982905982%;
332 | *margin-left: 59.72267685033642%;
333 | }
334 | .row-fluid .offset6 {
335 | margin-left: 53.84615384615384%;
336 | *margin-left: 53.739770867430444%;
337 | }
338 | .row-fluid .offset6:first-child {
339 | margin-left: 51.28205128205128%;
340 | *margin-left: 51.175668303327875%;
341 | }
342 | .row-fluid .offset5 {
343 | margin-left: 45.299145299145295%;
344 | *margin-left: 45.1927623204219%;
345 | }
346 | .row-fluid .offset5:first-child {
347 | margin-left: 42.73504273504273%;
348 | *margin-left: 42.62865975631933%;
349 | }
350 | .row-fluid .offset4 {
351 | margin-left: 36.75213675213675%;
352 | *margin-left: 36.645753773413354%;
353 | }
354 | .row-fluid .offset4:first-child {
355 | margin-left: 34.18803418803419%;
356 | *margin-left: 34.081651209310785%;
357 | }
358 | .row-fluid .offset3 {
359 | margin-left: 28.205128205128204%;
360 | *margin-left: 28.0987452264048%;
361 | }
362 | .row-fluid .offset3:first-child {
363 | margin-left: 25.641025641025642%;
364 | *margin-left: 25.53464266230224%;
365 | }
366 | .row-fluid .offset2 {
367 | margin-left: 19.65811965811966%;
368 | *margin-left: 19.551736679396257%;
369 | }
370 | .row-fluid .offset2:first-child {
371 | margin-left: 17.094017094017094%;
372 | *margin-left: 16.98763411529369%;
373 | }
374 | .row-fluid .offset1 {
375 | margin-left: 11.11111111111111%;
376 | *margin-left: 11.004728132387708%;
377 | }
378 | .row-fluid .offset1:first-child {
379 | margin-left: 8.547008547008547%;
380 | *margin-left: 8.440625568285142%;
381 | }
382 | input,
383 | textarea,
384 | .uneditable-input {
385 | margin-left: 0;
386 | }
387 | .controls-row [class*="span"] + [class*="span"] {
388 | margin-left: 30px;
389 | }
390 | input.span12,
391 | textarea.span12,
392 | .uneditable-input.span12 {
393 | width: 1156px;
394 | }
395 | input.span11,
396 | textarea.span11,
397 | .uneditable-input.span11 {
398 | width: 1056px;
399 | }
400 | input.span10,
401 | textarea.span10,
402 | .uneditable-input.span10 {
403 | width: 956px;
404 | }
405 | input.span9,
406 | textarea.span9,
407 | .uneditable-input.span9 {
408 | width: 856px;
409 | }
410 | input.span8,
411 | textarea.span8,
412 | .uneditable-input.span8 {
413 | width: 756px;
414 | }
415 | input.span7,
416 | textarea.span7,
417 | .uneditable-input.span7 {
418 | width: 656px;
419 | }
420 | input.span6,
421 | textarea.span6,
422 | .uneditable-input.span6 {
423 | width: 556px;
424 | }
425 | input.span5,
426 | textarea.span5,
427 | .uneditable-input.span5 {
428 | width: 456px;
429 | }
430 | input.span4,
431 | textarea.span4,
432 | .uneditable-input.span4 {
433 | width: 356px;
434 | }
435 | input.span3,
436 | textarea.span3,
437 | .uneditable-input.span3 {
438 | width: 256px;
439 | }
440 | input.span2,
441 | textarea.span2,
442 | .uneditable-input.span2 {
443 | width: 156px;
444 | }
445 | input.span1,
446 | textarea.span1,
447 | .uneditable-input.span1 {
448 | width: 56px;
449 | }
450 | .thumbnails {
451 | margin-left: -30px;
452 | }
453 | .thumbnails > li {
454 | margin-left: 30px;
455 | }
456 | .row-fluid .thumbnails {
457 | margin-left: 0;
458 | }
459 | }
460 |
461 | @media (min-width: 768px) and (max-width: 979px) {
462 | .row {
463 | margin-left: -20px;
464 | *zoom: 1;
465 | }
466 | .row:before,
467 | .row:after {
468 | display: table;
469 | line-height: 0;
470 | content: "";
471 | }
472 | .row:after {
473 | clear: both;
474 | }
475 | [class*="span"] {
476 | float: left;
477 | min-height: 1px;
478 | margin-left: 20px;
479 | }
480 | .container,
481 | .navbar-static-top .container,
482 | .navbar-fixed-top .container,
483 | .navbar-fixed-bottom .container {
484 | width: 724px;
485 | }
486 | .span12 {
487 | width: 724px;
488 | }
489 | .span11 {
490 | width: 662px;
491 | }
492 | .span10 {
493 | width: 600px;
494 | }
495 | .span9 {
496 | width: 538px;
497 | }
498 | .span8 {
499 | width: 476px;
500 | }
501 | .span7 {
502 | width: 414px;
503 | }
504 | .span6 {
505 | width: 352px;
506 | }
507 | .span5 {
508 | width: 290px;
509 | }
510 | .span4 {
511 | width: 228px;
512 | }
513 | .span3 {
514 | width: 166px;
515 | }
516 | .span2 {
517 | width: 104px;
518 | }
519 | .span1 {
520 | width: 42px;
521 | }
522 | .offset12 {
523 | margin-left: 764px;
524 | }
525 | .offset11 {
526 | margin-left: 702px;
527 | }
528 | .offset10 {
529 | margin-left: 640px;
530 | }
531 | .offset9 {
532 | margin-left: 578px;
533 | }
534 | .offset8 {
535 | margin-left: 516px;
536 | }
537 | .offset7 {
538 | margin-left: 454px;
539 | }
540 | .offset6 {
541 | margin-left: 392px;
542 | }
543 | .offset5 {
544 | margin-left: 330px;
545 | }
546 | .offset4 {
547 | margin-left: 268px;
548 | }
549 | .offset3 {
550 | margin-left: 206px;
551 | }
552 | .offset2 {
553 | margin-left: 144px;
554 | }
555 | .offset1 {
556 | margin-left: 82px;
557 | }
558 | .row-fluid {
559 | width: 100%;
560 | *zoom: 1;
561 | }
562 | .row-fluid:before,
563 | .row-fluid:after {
564 | display: table;
565 | line-height: 0;
566 | content: "";
567 | }
568 | .row-fluid:after {
569 | clear: both;
570 | }
571 | .row-fluid [class*="span"] {
572 | display: block;
573 | float: left;
574 | width: 100%;
575 | min-height: 30px;
576 | margin-left: 2.7624309392265194%;
577 | *margin-left: 2.709239449864817%;
578 | -webkit-box-sizing: border-box;
579 | -moz-box-sizing: border-box;
580 | box-sizing: border-box;
581 | }
582 | .row-fluid [class*="span"]:first-child {
583 | margin-left: 0;
584 | }
585 | .row-fluid .controls-row [class*="span"] + [class*="span"] {
586 | margin-left: 2.7624309392265194%;
587 | }
588 | .row-fluid .span12 {
589 | width: 100%;
590 | *width: 99.94680851063829%;
591 | }
592 | .row-fluid .span11 {
593 | width: 91.43646408839778%;
594 | *width: 91.38327259903608%;
595 | }
596 | .row-fluid .span10 {
597 | width: 82.87292817679558%;
598 | *width: 82.81973668743387%;
599 | }
600 | .row-fluid .span9 {
601 | width: 74.30939226519337%;
602 | *width: 74.25620077583166%;
603 | }
604 | .row-fluid .span8 {
605 | width: 65.74585635359117%;
606 | *width: 65.69266486422946%;
607 | }
608 | .row-fluid .span7 {
609 | width: 57.18232044198895%;
610 | *width: 57.12912895262725%;
611 | }
612 | .row-fluid .span6 {
613 | width: 48.61878453038674%;
614 | *width: 48.56559304102504%;
615 | }
616 | .row-fluid .span5 {
617 | width: 40.05524861878453%;
618 | *width: 40.00205712942283%;
619 | }
620 | .row-fluid .span4 {
621 | width: 31.491712707182323%;
622 | *width: 31.43852121782062%;
623 | }
624 | .row-fluid .span3 {
625 | width: 22.92817679558011%;
626 | *width: 22.87498530621841%;
627 | }
628 | .row-fluid .span2 {
629 | width: 14.3646408839779%;
630 | *width: 14.311449394616199%;
631 | }
632 | .row-fluid .span1 {
633 | width: 5.801104972375691%;
634 | *width: 5.747913483013988%;
635 | }
636 | .row-fluid .offset12 {
637 | margin-left: 105.52486187845304%;
638 | *margin-left: 105.41847889972962%;
639 | }
640 | .row-fluid .offset12:first-child {
641 | margin-left: 102.76243093922652%;
642 | *margin-left: 102.6560479605031%;
643 | }
644 | .row-fluid .offset11 {
645 | margin-left: 96.96132596685082%;
646 | *margin-left: 96.8549429881274%;
647 | }
648 | .row-fluid .offset11:first-child {
649 | margin-left: 94.1988950276243%;
650 | *margin-left: 94.09251204890089%;
651 | }
652 | .row-fluid .offset10 {
653 | margin-left: 88.39779005524862%;
654 | *margin-left: 88.2914070765252%;
655 | }
656 | .row-fluid .offset10:first-child {
657 | margin-left: 85.6353591160221%;
658 | *margin-left: 85.52897613729868%;
659 | }
660 | .row-fluid .offset9 {
661 | margin-left: 79.8342541436464%;
662 | *margin-left: 79.72787116492299%;
663 | }
664 | .row-fluid .offset9:first-child {
665 | margin-left: 77.07182320441989%;
666 | *margin-left: 76.96544022569647%;
667 | }
668 | .row-fluid .offset8 {
669 | margin-left: 71.2707182320442%;
670 | *margin-left: 71.16433525332079%;
671 | }
672 | .row-fluid .offset8:first-child {
673 | margin-left: 68.50828729281768%;
674 | *margin-left: 68.40190431409427%;
675 | }
676 | .row-fluid .offset7 {
677 | margin-left: 62.70718232044199%;
678 | *margin-left: 62.600799341718584%;
679 | }
680 | .row-fluid .offset7:first-child {
681 | margin-left: 59.94475138121547%;
682 | *margin-left: 59.838368402492065%;
683 | }
684 | .row-fluid .offset6 {
685 | margin-left: 54.14364640883978%;
686 | *margin-left: 54.037263430116376%;
687 | }
688 | .row-fluid .offset6:first-child {
689 | margin-left: 51.38121546961326%;
690 | *margin-left: 51.27483249088986%;
691 | }
692 | .row-fluid .offset5 {
693 | margin-left: 45.58011049723757%;
694 | *margin-left: 45.47372751851417%;
695 | }
696 | .row-fluid .offset5:first-child {
697 | margin-left: 42.81767955801105%;
698 | *margin-left: 42.71129657928765%;
699 | }
700 | .row-fluid .offset4 {
701 | margin-left: 37.01657458563536%;
702 | *margin-left: 36.91019160691196%;
703 | }
704 | .row-fluid .offset4:first-child {
705 | margin-left: 34.25414364640884%;
706 | *margin-left: 34.14776066768544%;
707 | }
708 | .row-fluid .offset3 {
709 | margin-left: 28.45303867403315%;
710 | *margin-left: 28.346655695309746%;
711 | }
712 | .row-fluid .offset3:first-child {
713 | margin-left: 25.69060773480663%;
714 | *margin-left: 25.584224756083227%;
715 | }
716 | .row-fluid .offset2 {
717 | margin-left: 19.88950276243094%;
718 | *margin-left: 19.783119783707537%;
719 | }
720 | .row-fluid .offset2:first-child {
721 | margin-left: 17.12707182320442%;
722 | *margin-left: 17.02068884448102%;
723 | }
724 | .row-fluid .offset1 {
725 | margin-left: 11.32596685082873%;
726 | *margin-left: 11.219583872105325%;
727 | }
728 | .row-fluid .offset1:first-child {
729 | margin-left: 8.56353591160221%;
730 | *margin-left: 8.457152932878806%;
731 | }
732 | input,
733 | textarea,
734 | .uneditable-input {
735 | margin-left: 0;
736 | }
737 | .controls-row [class*="span"] + [class*="span"] {
738 | margin-left: 20px;
739 | }
740 | input.span12,
741 | textarea.span12,
742 | .uneditable-input.span12 {
743 | width: 710px;
744 | }
745 | input.span11,
746 | textarea.span11,
747 | .uneditable-input.span11 {
748 | width: 648px;
749 | }
750 | input.span10,
751 | textarea.span10,
752 | .uneditable-input.span10 {
753 | width: 586px;
754 | }
755 | input.span9,
756 | textarea.span9,
757 | .uneditable-input.span9 {
758 | width: 524px;
759 | }
760 | input.span8,
761 | textarea.span8,
762 | .uneditable-input.span8 {
763 | width: 462px;
764 | }
765 | input.span7,
766 | textarea.span7,
767 | .uneditable-input.span7 {
768 | width: 400px;
769 | }
770 | input.span6,
771 | textarea.span6,
772 | .uneditable-input.span6 {
773 | width: 338px;
774 | }
775 | input.span5,
776 | textarea.span5,
777 | .uneditable-input.span5 {
778 | width: 276px;
779 | }
780 | input.span4,
781 | textarea.span4,
782 | .uneditable-input.span4 {
783 | width: 214px;
784 | }
785 | input.span3,
786 | textarea.span3,
787 | .uneditable-input.span3 {
788 | width: 152px;
789 | }
790 | input.span2,
791 | textarea.span2,
792 | .uneditable-input.span2 {
793 | width: 90px;
794 | }
795 | input.span1,
796 | textarea.span1,
797 | .uneditable-input.span1 {
798 | width: 28px;
799 | }
800 | }
801 |
802 | @media (max-width: 767px) {
803 | body {
804 | padding-right: 20px;
805 | padding-left: 20px;
806 | }
807 | .navbar-fixed-top,
808 | .navbar-fixed-bottom,
809 | .navbar-static-top {
810 | margin-right: -20px;
811 | margin-left: -20px;
812 | }
813 | .container-fluid {
814 | padding: 0;
815 | }
816 | .dl-horizontal dt {
817 | float: none;
818 | width: auto;
819 | clear: none;
820 | text-align: left;
821 | }
822 | .dl-horizontal dd {
823 | margin-left: 0;
824 | }
825 | .container {
826 | width: auto;
827 | }
828 | .row-fluid {
829 | width: 100%;
830 | }
831 | .row,
832 | .thumbnails {
833 | margin-left: 0;
834 | }
835 | .thumbnails > li {
836 | float: none;
837 | margin-left: 0;
838 | }
839 | [class*="span"],
840 | .uneditable-input[class*="span"],
841 | .row-fluid [class*="span"] {
842 | display: block;
843 | float: none;
844 | width: 100%;
845 | margin-left: 0;
846 | -webkit-box-sizing: border-box;
847 | -moz-box-sizing: border-box;
848 | box-sizing: border-box;
849 | }
850 | .span12,
851 | .row-fluid .span12 {
852 | width: 100%;
853 | -webkit-box-sizing: border-box;
854 | -moz-box-sizing: border-box;
855 | box-sizing: border-box;
856 | }
857 | .row-fluid [class*="offset"]:first-child {
858 | margin-left: 0;
859 | }
860 | .input-large,
861 | .input-xlarge,
862 | .input-xxlarge,
863 | input[class*="span"],
864 | select[class*="span"],
865 | textarea[class*="span"],
866 | .uneditable-input {
867 | display: block;
868 | width: 100%;
869 | min-height: 30px;
870 | -webkit-box-sizing: border-box;
871 | -moz-box-sizing: border-box;
872 | box-sizing: border-box;
873 | }
874 | .input-prepend input,
875 | .input-append input,
876 | .input-prepend input[class*="span"],
877 | .input-append input[class*="span"] {
878 | display: inline-block;
879 | width: auto;
880 | }
881 | .controls-row [class*="span"] + [class*="span"] {
882 | margin-left: 0;
883 | }
884 | .modal {
885 | position: fixed;
886 | top: 20px;
887 | right: 20px;
888 | left: 20px;
889 | width: auto;
890 | margin: 0;
891 | }
892 | .modal.fade {
893 | top: -100px;
894 | }
895 | .modal.fade.in {
896 | top: 20px;
897 | }
898 | }
899 |
900 | @media (max-width: 480px) {
901 | .nav-collapse {
902 | -webkit-transform: translate3d(0, 0, 0);
903 | }
904 | .page-header h1 small {
905 | display: block;
906 | line-height: 20px;
907 | }
908 | input[type="checkbox"],
909 | input[type="radio"] {
910 | border: 1px solid #ccc;
911 | }
912 | .form-horizontal .control-label {
913 | float: none;
914 | width: auto;
915 | padding-top: 0;
916 | text-align: left;
917 | }
918 | .form-horizontal .controls {
919 | margin-left: 0;
920 | }
921 | .form-horizontal .control-list {
922 | padding-top: 0;
923 | }
924 | .form-horizontal .form-actions {
925 | padding-right: 10px;
926 | padding-left: 10px;
927 | }
928 | .media .pull-left,
929 | .media .pull-right {
930 | display: block;
931 | float: none;
932 | margin-bottom: 10px;
933 | }
934 | .media-object {
935 | margin-right: 0;
936 | margin-left: 0;
937 | }
938 | .modal {
939 | top: 10px;
940 | right: 10px;
941 | left: 10px;
942 | }
943 | .modal-header .close {
944 | padding: 10px;
945 | margin: -10px;
946 | }
947 | .carousel-caption {
948 | position: static;
949 | }
950 | }
951 |
952 | @media (max-width: 979px) {
953 | body {
954 | padding-top: 0;
955 | }
956 | .navbar-fixed-top,
957 | .navbar-fixed-bottom {
958 | position: static;
959 | }
960 | .navbar-fixed-top {
961 | margin-bottom: 20px;
962 | }
963 | .navbar-fixed-bottom {
964 | margin-top: 20px;
965 | }
966 | .navbar-fixed-top .navbar-inner,
967 | .navbar-fixed-bottom .navbar-inner {
968 | padding: 5px;
969 | }
970 | .navbar .container {
971 | width: auto;
972 | padding: 0;
973 | }
974 | .navbar .brand {
975 | padding-right: 10px;
976 | padding-left: 10px;
977 | margin: 0 0 0 -5px;
978 | }
979 | .nav-collapse {
980 | clear: both;
981 | }
982 | .nav-collapse .nav {
983 | float: none;
984 | margin: 0 0 10px;
985 | }
986 | .nav-collapse .nav > li {
987 | float: none;
988 | }
989 | .nav-collapse .nav > li > a {
990 | margin-bottom: 2px;
991 | }
992 | .nav-collapse .nav > .divider-vertical {
993 | display: none;
994 | }
995 | .nav-collapse .nav .nav-header {
996 | color: #777777;
997 | text-shadow: none;
998 | }
999 | .nav-collapse .nav > li > a,
1000 | .nav-collapse .dropdown-menu a {
1001 | padding: 9px 15px;
1002 | font-weight: bold;
1003 | color: #777777;
1004 | -webkit-border-radius: 3px;
1005 | -moz-border-radius: 3px;
1006 | border-radius: 3px;
1007 | }
1008 | .nav-collapse .btn {
1009 | padding: 4px 10px 4px;
1010 | font-weight: normal;
1011 | -webkit-border-radius: 4px;
1012 | -moz-border-radius: 4px;
1013 | border-radius: 4px;
1014 | }
1015 | .nav-collapse .dropdown-menu li + li a {
1016 | margin-bottom: 2px;
1017 | }
1018 | .nav-collapse .nav > li > a:hover,
1019 | .nav-collapse .nav > li > a:focus,
1020 | .nav-collapse .dropdown-menu a:hover,
1021 | .nav-collapse .dropdown-menu a:focus {
1022 | background-color: #f2f2f2;
1023 | }
1024 | .navbar-inverse .nav-collapse .nav > li > a,
1025 | .navbar-inverse .nav-collapse .dropdown-menu a {
1026 | color: #999999;
1027 | }
1028 | .navbar-inverse .nav-collapse .nav > li > a:hover,
1029 | .navbar-inverse .nav-collapse .nav > li > a:focus,
1030 | .navbar-inverse .nav-collapse .dropdown-menu a:hover,
1031 | .navbar-inverse .nav-collapse .dropdown-menu a:focus {
1032 | background-color: #111111;
1033 | }
1034 | .nav-collapse.in .btn-group {
1035 | padding: 0;
1036 | margin-top: 5px;
1037 | }
1038 | .nav-collapse .dropdown-menu {
1039 | position: static;
1040 | top: auto;
1041 | left: auto;
1042 | display: none;
1043 | float: none;
1044 | max-width: none;
1045 | padding: 0;
1046 | margin: 0 15px;
1047 | background-color: transparent;
1048 | border: none;
1049 | -webkit-border-radius: 0;
1050 | -moz-border-radius: 0;
1051 | border-radius: 0;
1052 | -webkit-box-shadow: none;
1053 | -moz-box-shadow: none;
1054 | box-shadow: none;
1055 | }
1056 | .nav-collapse .open > .dropdown-menu {
1057 | display: block;
1058 | }
1059 | .nav-collapse .dropdown-menu:before,
1060 | .nav-collapse .dropdown-menu:after {
1061 | display: none;
1062 | }
1063 | .nav-collapse .dropdown-menu .divider {
1064 | display: none;
1065 | }
1066 | .nav-collapse .nav > li > .dropdown-menu:before,
1067 | .nav-collapse .nav > li > .dropdown-menu:after {
1068 | display: none;
1069 | }
1070 | .nav-collapse .navbar-form,
1071 | .nav-collapse .navbar-search {
1072 | float: none;
1073 | padding: 10px 15px;
1074 | margin: 10px 0;
1075 | border-top: 1px solid #f2f2f2;
1076 | border-bottom: 1px solid #f2f2f2;
1077 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
1078 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
1079 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
1080 | }
1081 | .navbar-inverse .nav-collapse .navbar-form,
1082 | .navbar-inverse .nav-collapse .navbar-search {
1083 | border-top-color: #111111;
1084 | border-bottom-color: #111111;
1085 | }
1086 | .navbar .nav-collapse .nav.pull-right {
1087 | float: none;
1088 | margin-left: 0;
1089 | }
1090 | .nav-collapse,
1091 | .nav-collapse.collapse {
1092 | height: 0;
1093 | overflow: hidden;
1094 | }
1095 | .navbar .btn-navbar {
1096 | display: block;
1097 | }
1098 | .navbar-static .navbar-inner {
1099 | padding-right: 10px;
1100 | padding-left: 10px;
1101 | }
1102 | }
1103 |
1104 | @media (min-width: 980px) {
1105 | .nav-collapse.collapse {
1106 | height: auto !important;
1107 | overflow: visible !important;
1108 | }
1109 | }
1110 |
--------------------------------------------------------------------------------
/example/core/static/core/js/bootstrap-tab.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Bootstrap.js by @fat & @mdo
3 | * plugins: bootstrap-tab.js
4 | * Copyright 2013 Twitter, Inc.
5 | * http://www.apache.org/licenses/LICENSE-2.0.txt
6 | */
7 | !function(a){var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active:last a")[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery)
--------------------------------------------------------------------------------
/example/core/templates/home.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
31 |
32 |
33 |
34 | {{ form.media }}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
59 |
60 |
61 |
62 |
63 |
64 |
Its time to Play !
65 |
Try submitting this form without entering anything.
66 |
67 |
Form
68 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/example/core/tests.py:
--------------------------------------------------------------------------------
1 | """
2 | This file demonstrates writing tests using the unittest module. These will pass
3 | when you run "manage.py test".
4 |
5 | Replace this with more appropriate tests for your application.
6 | """
7 |
8 | from django.test import TestCase
9 |
10 |
11 | class SimpleTest(TestCase):
12 | def test_basic_addition(self):
13 | """
14 | Tests that 1 + 1 always equals 2.
15 | """
16 | self.assertEqual(1 + 1, 2)
17 |
--------------------------------------------------------------------------------
/example/core/views.py:
--------------------------------------------------------------------------------
1 | from django.views.generic.edit import FormView
2 |
3 | from core.forms import FieldTypeForm
4 |
5 |
6 | class HomeView(FormView):
7 | template_name = "home.html"
8 | form_class = FieldTypeForm
9 | success_url = '/'
10 |
--------------------------------------------------------------------------------
/example/example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agiliq/Django-parsley/f1901625034029a09a926e39ed7cff24b4d512b2/example/example/__init__.py
--------------------------------------------------------------------------------
/example/example/settings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agiliq/Django-parsley/f1901625034029a09a926e39ed7cff24b4d512b2/example/example/settings/__init__.py
--------------------------------------------------------------------------------
/example/example/settings/common.py:
--------------------------------------------------------------------------------
1 | # Django settings for example project.
2 | import os
3 | PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
4 |
5 | DEBUG = False
6 | TEMPLATE_DEBUG = DEBUG
7 |
8 | ADMINS = (
9 | # ('Your Name', 'your_email@example.com'),
10 | )
11 |
12 | MANAGERS = ADMINS
13 |
14 | DATABASES = {
15 | 'default': {
16 | 'ENGINE': '', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
17 | 'NAME': '', # Or path to database file if using sqlite3.
18 | 'USER': '', # Not used with sqlite3.
19 | 'PASSWORD': '', # Not used with sqlite3.
20 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
21 | 'PORT': '', # Set to empty string for default. Not used with sqlite3.
22 | }
23 | }
24 |
25 | # Local time zone for this installation. Choices can be found here:
26 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
27 | # although not all choices may be available on all operating systems.
28 | # In a Windows environment this must be set to your system time zone.
29 | TIME_ZONE = 'America/Chicago'
30 |
31 | # Language code for this installation. All choices can be found here:
32 | # http://www.i18nguy.com/unicode/language-identifiers.html
33 | LANGUAGE_CODE = 'en-us'
34 |
35 | SITE_ID = 1
36 |
37 | # If you set this to False, Django will make some optimizations so as not
38 | # to load the internationalization machinery.
39 | USE_I18N = True
40 |
41 | # If you set this to False, Django will not format dates, numbers and
42 | # calendars according to the current locale.
43 | USE_L10N = True
44 |
45 | # If you set this to False, Django will not use timezone-aware datetimes.
46 | USE_TZ = True
47 |
48 | # Absolute filesystem path to the directory that will hold user-uploaded files.
49 | # Example: "/home/media/media.lawrence.com/media/"
50 | MEDIA_ROOT = ''
51 |
52 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
53 | # trailing slash.
54 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
55 | MEDIA_URL = ''
56 |
57 | # Absolute path to the directory static files should be collected to.
58 | # Don't put anything in this directory yourself; store your static files
59 | # in apps' "static/" subdirectories and in STATICFILES_DIRS.
60 | # Example: "/home/media/media.lawrence.com/static/"
61 | STATIC_ROOT = 'static'
62 |
63 | # URL prefix for static files.
64 | # Example: "http://media.lawrence.com/static/"
65 | STATIC_URL = '/static/'
66 |
67 | # Additional locations of static files
68 | STATICFILES_DIRS = (
69 | # Put strings here, like "/home/html/static" or "C:/www/django/static".
70 | # Always use forward slashes, even on Windows.
71 | # Don't forget to use absolute paths, not relative paths.
72 | )
73 |
74 | # List of finder classes that know how to find static files in
75 | # various locations.
76 | STATICFILES_FINDERS = (
77 | 'django.contrib.staticfiles.finders.FileSystemFinder',
78 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
79 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
80 | )
81 |
82 | # Make this unique, and don't share it with anybody.
83 | SECRET_KEY = '1&(uu+$bep+e%8%cg^)i2#--@o83208&#yr&1bi49&5u(gsl1('
84 |
85 | # List of callables that know how to import templates from various sources.
86 | TEMPLATE_LOADERS = (
87 | 'django.template.loaders.filesystem.Loader',
88 | 'django.template.loaders.app_directories.Loader',
89 | # 'django.template.loaders.eggs.Loader',
90 | )
91 |
92 | MIDDLEWARE_CLASSES = (
93 | 'django.middleware.common.CommonMiddleware',
94 | 'django.contrib.sessions.middleware.SessionMiddleware',
95 | 'django.middleware.csrf.CsrfViewMiddleware',
96 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
97 | 'django.contrib.messages.middleware.MessageMiddleware',
98 | # Uncomment the next line for simple clickjacking protection:
99 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
100 | )
101 |
102 | ROOT_URLCONF = 'example.urls'
103 |
104 | # Python dotted path to the WSGI application used by Django's runserver.
105 | WSGI_APPLICATION = 'example.wsgi.application'
106 |
107 | TEMPLATES = [
108 | {
109 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
110 | 'DIRS': [
111 | os.path.join(PROJECT_PATH, 'templates'),
112 | ],
113 | 'APP_DIRS': True,
114 | 'OPTIONS': {
115 | 'context_processors': [
116 | 'django.template.context_processors.debug',
117 | 'django.template.context_processors.request',
118 | 'django.contrib.auth.context_processors.auth',
119 | 'django.contrib.messages.context_processors.messages',
120 | ],
121 | },
122 | },
123 | ]
124 |
125 |
126 |
127 | INSTALLED_APPS = (
128 | 'django.contrib.auth',
129 | 'django.contrib.contenttypes',
130 | 'django.contrib.sessions',
131 | 'django.contrib.sites',
132 | 'django.contrib.messages',
133 | 'django.contrib.staticfiles',
134 | 'django.contrib.admin',
135 | 'core',
136 | 'parsley',
137 | # Uncomment the next line to enable the admin:
138 | # Uncomment the next line to enable admin documentation:
139 | # 'django.contrib.admindocs',
140 | )
141 |
142 | # A sample logging configuration. The only tangible logging
143 | # performed by this configuration is to send an email to
144 | # the site admins on every HTTP 500 error when DEBUG=False.
145 | # See http://docs.djangoproject.com/en/dev/topics/logging for
146 | # more details on how to customize your logging configuration.
147 | LOGGING = {
148 | 'version': 1,
149 | 'disable_existing_loggers': False,
150 | 'filters': {
151 | 'require_debug_false': {
152 | '()': 'django.utils.log.RequireDebugFalse'
153 | }
154 | },
155 | 'handlers': {
156 | 'mail_admins': {
157 | 'level': 'ERROR',
158 | 'filters': ['require_debug_false'],
159 | 'class': 'django.utils.log.AdminEmailHandler'
160 | }
161 | },
162 | 'loggers': {
163 | 'django.request': {
164 | 'handlers': ['mail_admins'],
165 | 'level': 'ERROR',
166 | 'propagate': True,
167 | },
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/example/example/settings/local.py-dist:
--------------------------------------------------------------------------------
1 | from .common import *
2 |
3 | DEBUG = True
4 |
5 |
6 | DATABASES = {
7 | 'default': {
8 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
9 | 'NAME': 'test.db', # Or path to database file if using sqlite3.
10 | 'USER': '', # Not used with sqlite3.
11 | 'PASSWORD': '', # Not used with sqlite3.
12 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
13 | 'PORT': '', # Set to empty string for default. Not used with sqlite3.
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/example/example/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.conf.urls import include, url
3 |
4 | from core.views import HomeView
5 |
6 | admin.autodiscover()
7 |
8 |
9 | urlpatterns =[
10 | url(r'^admin/', include(admin.site.urls)),
11 | url(r'^$', HomeView.as_view(), name='home'),
12 | ]
13 |
--------------------------------------------------------------------------------
/example/example/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for example project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 |
18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings.local")
19 |
20 | # This application object is used by any WSGI server configured to use this
21 | # file. This includes Django's development server, if the WSGI_APPLICATION
22 | # setting points here.
23 | from django.core.wsgi import get_wsgi_application
24 | application = get_wsgi_application()
25 |
26 | # Apply WSGI middleware here.
27 | # from helloworld.wsgi import HelloWorldApplication
28 | # application = HelloWorldApplication(application)
29 |
--------------------------------------------------------------------------------
/example/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings.local")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/example/parsley:
--------------------------------------------------------------------------------
1 | ../parsley/
--------------------------------------------------------------------------------
/example/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==1.10
2 | gunicorn
3 |
--------------------------------------------------------------------------------
/parsley/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agiliq/Django-parsley/f1901625034029a09a926e39ed7cff24b4d512b2/parsley/__init__.py
--------------------------------------------------------------------------------
/parsley/decorators.py:
--------------------------------------------------------------------------------
1 | import re
2 | import types
3 |
4 | from django import forms
5 | from parsley.widgets import ParsleyChoiceFieldRendererMixin
6 |
7 | FIELD_TYPES = [
8 | (forms.URLField, "url"),
9 | (forms.EmailField, "email"),
10 | (forms.IntegerField, "digits"),
11 | (forms.DecimalField, "number"),
12 | (forms.FloatField, "number"),
13 | ]
14 |
15 |
16 | FIELD_ATTRS = [
17 | ("min_length", "minlength"),
18 | ("max_length", "maxlength"),
19 | ("min_value", "min"),
20 | ("max_value", "max"),
21 | ]
22 |
23 |
24 | def update_widget_attrs(field, prefix='data'):
25 | attrs = field.widget.attrs
26 | if field.required:
27 | if isinstance(field.widget, forms.widgets.RadioSelect):
28 | try:
29 | # django >= 1.11
30 | original_create_option = field.widget.create_option
31 |
32 | def new_create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
33 | if index == len(self.choices) - 1:
34 | attrs = attrs or {}
35 | attrs["{prefix}-required".format(prefix=prefix)] = "true"
36 |
37 | return original_create_option(name, value, label, selected, index, subindex, attrs)
38 |
39 | field.widget.create_option = types.MethodType(new_create_option, field.widget)
40 | except AttributeError:
41 | # django < 1.11
42 |
43 | # Use a mixin, to try and support non-standard renderers if possible
44 | class ParsleyChoiceFieldRenderer(ParsleyChoiceFieldRendererMixin, field.widget.renderer):
45 | parsley_namespace = prefix
46 | field.widget.renderer = ParsleyChoiceFieldRenderer
47 | else:
48 | attrs["{prefix}-required".format(prefix=prefix)] = "true"
49 | error_message = field.error_messages.get('required', None)
50 | if error_message:
51 | attrs["{prefix}-required-message".format(prefix=prefix)] = error_message
52 |
53 | if isinstance(field, forms.RegexField):
54 | attrs.update({"{prefix}-pattern".format(prefix=prefix): field.regex.pattern})
55 |
56 | error_message = field.error_messages.get('invalid', None)
57 | if error_message:
58 | attrs["{prefix}-pattern-message".format(prefix=prefix)] = error_message
59 |
60 | if field.regex.flags & re.IGNORECASE:
61 | attrs.update({"{prefix}-regexp-flag".format(prefix=prefix): "i"})
62 |
63 | if isinstance(field, forms.MultiValueField):
64 | for subfield in field.fields:
65 | update_widget_attrs(subfield)
66 |
67 | # Set {prefix}-* attributes for parsley based on Django field attributes
68 | for attr, data_attr, in FIELD_ATTRS:
69 | if getattr(field, attr, None):
70 | attrs["{prefix}-{0}".format(data_attr, prefix=prefix)] = getattr(field, attr)
71 |
72 | error_message = field.error_messages.get(attr, None)
73 | if error_message:
74 | attrs["{prefix}-{0}-message".format(data_attr, prefix=prefix)] = error_message
75 |
76 | # Set {prefix}-type attribute based on Django field instance type
77 | for klass, field_type in FIELD_TYPES:
78 | if isinstance(field, klass):
79 | attrs["{prefix}-type".format(prefix=prefix)] = field_type
80 |
81 | error_message = field.error_messages.get('invalid', None)
82 | if error_message:
83 | attrs["{prefix}-type-message".format(field_type, prefix=prefix)] = error_message
84 |
85 |
86 | def parsley_form(form):
87 | prefix = getattr(getattr(form, 'Meta', None), 'parsley_namespace', 'data-parsley')
88 | for _, field in form.fields.items():
89 | update_widget_attrs(field, prefix)
90 | extras = getattr(getattr(form, 'Meta', None), 'parsley_extras', {})
91 | for field_name, data in extras.items():
92 | for key, value in data.items():
93 | if field_name not in form.fields:
94 | continue
95 | attrs = form.fields[field_name].widget.attrs
96 | if key == 'equalto':
97 | # Use HTML id for {prefix}-equalto
98 | value = '#' + form[value].id_for_label
99 | if isinstance(value, bool):
100 | value = "true" if value else "false"
101 | attrs["{prefix}-%s".format(prefix=prefix) % key] = value
102 | return form
103 |
104 |
105 | def parsleyfy(klass):
106 | """
107 | A decorator to add {prefix}-* attributes to your form.fields
108 | """
109 |
110 | old_init = klass.__init__
111 |
112 | def new_init(self, *args, **kwargs):
113 | old_init(self, *args, **kwargs)
114 | parsley_form(self)
115 |
116 | klass.__init__ = new_init
117 |
118 | return klass
119 |
--------------------------------------------------------------------------------
/parsley/mixins.py:
--------------------------------------------------------------------------------
1 | from parsley.decorators import parsleyfy
2 |
3 |
4 | class ParsleyAdminMixin(object):
5 |
6 | def get_form(self, *args, **kwargs):
7 | form = super(ParsleyAdminMixin, self).get_form(*args, **kwargs)
8 | return parsleyfy(form)
9 |
--------------------------------------------------------------------------------
/parsley/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agiliq/Django-parsley/f1901625034029a09a926e39ed7cff24b4d512b2/parsley/models.py
--------------------------------------------------------------------------------
/parsley/static/parsley/js/parsley.django-admin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Parsley.js - django admin helper
3 | */
4 | ! function($) {
5 | 'use strict';
6 |
7 | $(window).on('load', function() {
8 | // Don't parsleyfy Django search boxes. It breaks layout
9 | $('form:not(#changelist-search, #changelist-form)').each(function() {
10 | $(this).parsley({});
11 | });
12 | });
13 | }(window.jQuery || window.Zepto);
14 |
--------------------------------------------------------------------------------
/parsley/static/parsley/js/parsley.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Parsleyjs
3 | * Guillaume Potier -
4 | * Version 2.0.7 - built Sat Jan 24 2015 14:50:11
5 | * MIT Licensed
6 | *
7 | */
8 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"undefined"==typeof a&&"undefined"!=typeof window.jQuery&&(a=window.jQuery);var b={attr:function(a,b,c){var d,e={},f=this.msieversion(),g=new RegExp("^"+b,"i");if("undefined"==typeof a||"undefined"==typeof a[0])return{};for(var h in a[0].attributes)if(d=a[0].attributes[h],"undefined"!=typeof d&&null!==d&&(!f||f>=8||d.specified)&&g.test(d.name)){if("undefined"!=typeof c&&new RegExp(c+"$","i").test(d.name))return!0;e[this.camelize(d.name.replace(b,""))]=this.deserializeValue(d.value)}return"undefined"==typeof c?e:!1},setAttr:function(a,b,c,d){a[0].setAttribute(this.dasherize(b+c),String(d))},get:function(a,b){for(var c=0,d=(b||"").split(".");this.isObject(a)||this.isArray(a);)if(a=a[d[c++]],c===d.length)return a;return void 0},hash:function(a){return String(Math.random()).substring(2,a?a+2:9)},isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},isObject:function(a){return a===Object(a)},deserializeValue:function(b){var c;try{return b?"true"==b||("false"==b?!1:"null"==b?null:isNaN(c=Number(b))?/^[\[\{]/.test(b)?a.parseJSON(b):b:c):b}catch(d){return b}},camelize:function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},dasherize:function(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()},msieversion:function(){var a=window.navigator.userAgent,b=a.indexOf("MSIE ");return b>0||navigator.userAgent.match(/Trident.*rv\:11\./)?parseInt(a.substring(b+5,a.indexOf(".",b)),10):0}},c={namespace:"data-parsley-",inputs:"input, textarea, select",excluded:"input[type=button], input[type=submit], input[type=reset], input[type=hidden]",priorityEnabled:!0,uiEnabled:!0,validationThreshold:3,focus:"first",trigger:!1,errorClass:"parsley-error",successClass:"parsley-success",classHandler:function(){},errorsContainer:function(){},errorsWrapper:'',errorTemplate:""},d=function(){};d.prototype={asyncSupport:!1,actualizeOptions:function(){return this.options=this.OptionsFactory.get(this),this},validateThroughValidator:function(a,b,c){return window.ParsleyValidator.validate(a,b,c)},subscribe:function(b,c){return a.listenTo(this,b.toLowerCase(),c),this},unsubscribe:function(b){return a.unsubscribeTo(this,b.toLowerCase()),this},reset:function(){if("ParsleyForm"!==this.__class__)return a.emit("parsley:field:reset",this);for(var b=0;b=0;l--)"Required"!==k[l].__class__||(i=k[l].requiresValidation(b));if(this.has(h,a)||this.options.strict||i)try{this.has(h,this.options.strict||i?a:void 0)||(new e).HaveProperty(h).validate(a),c=this._check(h,a[h],b),(g(c)&&c.length>0||!g(c)&&!f(c))&&(d[h]=c)}catch(m){d[h]=m}}return f(d)?!0:d},add:function(a,b){if(b instanceof e||g(b)&&b[0]instanceof e)return this.nodes[a]=b,this;if("object"==typeof b&&!g(b))return this.nodes[a]=b instanceof c?b:new c(b),this;throw new Error("Should give an Assert, an Asserts array, a Constraint",b)},has:function(a,b){return b="undefined"!=typeof b?b:this.nodes,"undefined"!=typeof b[a]},get:function(a,b){return this.has(a)?this.nodes[a]:b||null},remove:function(a){var b=[];for(var c in this.nodes)c!==a&&(b[c]=this.nodes[c]);return this.nodes=b,this},_bootstrap:function(a){if(a instanceof c)return this.nodes=a.nodes;for(var b in a)this.add(b,a[b])},_check:function(a,b,d){if(this.nodes[a]instanceof e)return this._checkAsserts(b,[this.nodes[a]],d);if(g(this.nodes[a]))return this._checkAsserts(b,this.nodes[a],d);if(this.nodes[a]instanceof c)return this.nodes[a].check(b,d);throw new Error("Invalid node",this.nodes[a])},_checkAsserts:function(a,b,c){for(var d,e=[],f=0;f0},addGroup:function(a){return g(a)?this.addGroups(a):(this.hasGroup(a)||this.groups.push(a),this)},removeGroup:function(a){for(var b=[],c=0;c=a)throw new d(this,a,{threshold:this.threshold});return!0},this},GreaterThanOrEqual:function(a){if(this.__class__="GreaterThanOrEqual","undefined"==typeof a)throw new Error("Should give a threshold value");return this.threshold=a,this.validate=function(a){if(""===a||isNaN(Number(a)))throw new d(this,a,{value:b.errorCode.must_be_a_number});if(this.threshold>a)throw new d(this,a,{threshold:this.threshold});return!0},this},InstanceOf:function(a){if(this.__class__="InstanceOf","undefined"==typeof a)throw new Error("InstanceOf must be instanciated with a value");return this.classRef=a,this.validate=function(a){if(!0!=a instanceof this.classRef)throw new d(this,a,{classRef:this.classRef});return!0},this},Length:function(a){if(this.__class__="Length",!a.min&&!a.max)throw new Error("Lenth assert must be instanciated with a { min: x, max: y } object");return this.min=a.min,this.max=a.max,this.validate=function(a){if("string"!=typeof a&&!g(a))throw new d(this,a,{value:b.errorCode.must_be_a_string_or_array});if("undefined"!=typeof this.min&&this.min===this.max&&a.length!==this.min)throw new d(this,a,{min:this.min,max:this.max});if("undefined"!=typeof this.max&&a.length>this.max)throw new d(this,a,{max:this.max});if("undefined"!=typeof this.min&&a.length>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});var f=function(a){for(var b in a)return!1;return!0},g=function(a){return"[object Array]"===Object.prototype.toString.call(a)};return"function"==typeof define&&define.amd?define("vendors/validator.js/dist/validator",[],function(){return a}):"undefined"!=typeof module&&module.exports?module.exports=a:window["undefined"!=typeof validatorjs_ns?validatorjs_ns:"Validator"]=a,a}();e="undefined"!=typeof e?e:"undefined"!=typeof module?module.exports:null;var f=function(a,b){this.__class__="ParsleyValidator",this.Validator=e,this.locale="en",this.init(a||{},b||{})};f.prototype={init:function(b,c){this.catalog=c;for(var d in b)this.addValidator(d,b[d].fn,b[d].priority,b[d].requirementsTransformer);a.emit("parsley:validator:init")},setLocale:function(a){if("undefined"==typeof this.catalog[a])throw new Error(a+" is not available in the catalog");return this.locale=a,this},addCatalog:function(a,b,c){return"object"==typeof b&&(this.catalog[a]=b),!0===c?this.setLocale(a):this},addMessage:function(a,b,c){return"undefined"==typeof this.catalog[a]&&(this.catalog[a]={}),this.catalog[a][b.toLowerCase()]=c,this},validate:function(){return(new this.Validator.Validator).validate.apply(new e.Validator,arguments)},addValidator:function(b,c,d,f){return this.validators[b.toLowerCase()]=function(b){return a.extend((new e.Assert).Callback(c,b),{priority:d,requirementsTransformer:f})},this},updateValidator:function(a,b,c,d){return this.addValidator(a,b,c,d)},removeValidator:function(a){return delete this.validators[a],this},getErrorMessage:function(a){var b;return b="type"===a.name?this.catalog[this.locale][a.name][a.requirements]:this.formatMessage(this.catalog[this.locale][a.name],a.requirements),""!==b?b:this.catalog[this.locale].defaultMessage},formatMessage:function(a,b){if("object"==typeof b){for(var c in b)a=this.formatMessage(a,b[c]);return a}return"string"==typeof a?a.replace(new RegExp("%s","i"),b):""},validators:{notblank:function(){return a.extend((new e.Assert).NotBlank(),{priority:2})},required:function(){return a.extend((new e.Assert).Required(),{priority:512})},type:function(b){var c;switch(b){case"email":c=(new e.Assert).Email();break;case"range":case"number":c=(new e.Assert).Regexp("^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$");break;case"integer":c=(new e.Assert).Regexp("^-?\\d+$");break;case"digits":c=(new e.Assert).Regexp("^\\d+$");break;case"alphanum":c=(new e.Assert).Regexp("^\\w+$","i");break;case"url":c=(new e.Assert).Regexp("(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,24}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)","i");break;default:throw new Error("validator type `"+b+"` is not supported")}return a.extend(c,{priority:256})},pattern:function(b){var c="";return/^\/.*\/(?:[gimy]*)$/.test(b)&&(c=b.replace(/.*\/([gimy]*)$/,"$1"),b=b.replace(new RegExp("^/(.*?)/"+c+"$"),"$1")),a.extend((new e.Assert).Regexp(b,c),{priority:64})},minlength:function(b){return a.extend((new e.Assert).Length({min:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},maxlength:function(b){return a.extend((new e.Assert).Length({max:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},length:function(b){return a.extend((new e.Assert).Length({min:b[0],max:b[1]}),{priority:32})},mincheck:function(a){return this.minlength(a)},maxcheck:function(a){return this.maxlength(a)},check:function(a){return this.length(a)},min:function(b){return a.extend((new e.Assert).GreaterThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},max:function(b){return a.extend((new e.Assert).LessThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},range:function(b){return a.extend((new e.Assert).Range(b[0],b[1]),{priority:32,requirementsTransformer:function(){for(var a=0;a0?this._errorClass(a):this._resetClass(a)},manageErrorsMessages:function(b,c){if("undefined"==typeof b.options.errorsMessagesDisabled){if("undefined"!=typeof b.options.errorMessage)return c.added.length||c.kept.length?(0===b._ui.$errorsWrapper.find(".parsley-custom-error-message").length&&b._ui.$errorsWrapper.append(a(b.options.errorTemplate).addClass("parsley-custom-error-message")),b._ui.$errorsWrapper.addClass("filled").find(".parsley-custom-error-message").html(b.options.errorMessage)):b._ui.$errorsWrapper.removeClass("filled").find(".parsley-custom-error-message").remove();for(var d=0;d0&&"undefined"==typeof a.fields[b].options.noFocus){if("first"===a.options.focus)return a._focusedField=a.fields[b].$element,a._focusedField.focus();a._focusedField=a.fields[b].$element}return null===a._focusedField?null:a._focusedField.focus()},_getErrorMessage:function(a,b){var c=b.name+"Message";return"undefined"!=typeof a.options[c]?window.ParsleyValidator.formatMessage(a.options[c],b.requirements):window.ParsleyValidator.getErrorMessage(b)},_diff:function(a,b,c){for(var d=[],e=[],f=0;f0&&this.validationResult&&(this.validationResult=!1));return a.emit("parsley:form:"+(this.validationResult?"success":"error"),this),a.emit("parsley:form:validated",this),this.validationResult},isValid:function(a,b){this._refreshFields();for(var c=0;c1){var c=[];return this.each(function(){c.push(a(this).parsley(b))}),c}return a(this).length?new o(this,b):void(window.console&&window.console.warn&&window.console.warn("You must bind Parsley on an existing element."))},window.ParsleyUI="function"==typeof b.get(window,"ParsleyConfig.ParsleyUI")?(new window.ParsleyConfig.ParsleyUI).listen():(new g).listen(),"undefined"==typeof window.ParsleyExtend&&(window.ParsleyExtend={}),"undefined"==typeof window.ParsleyConfig&&(window.ParsleyConfig={}),window.Parsley=window.psly=o,window.ParsleyUtils=b,window.ParsleyValidator=new f(window.ParsleyConfig.validators,window.ParsleyConfig.i18n),!1!==b.get(window,"ParsleyConfig.autoBind")&&a(function(){a("[data-parsley-validate]").length&&a("[data-parsley-validate]").parsley()})});
--------------------------------------------------------------------------------
/parsley/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agiliq/Django-parsley/f1901625034029a09a926e39ed7cff24b4d512b2/parsley/templatetags/__init__.py
--------------------------------------------------------------------------------
/parsley/templatetags/parsley.py:
--------------------------------------------------------------------------------
1 | from django import template, VERSION
2 |
3 | from ..decorators import parsley_form
4 |
5 | register = template.Library()
6 |
7 | if VERSION[:2] >= (1, 9):
8 | # in django 1.9 and above, the simple_tag can do assignment
9 | tag_decorator = register.simple_tag
10 | else:
11 | # django 1.8 and below needs the assignment_tag
12 | tag_decorator = register.assignment_tag
13 |
14 |
15 | @tag_decorator()
16 | def parsleyfy(form):
17 | return parsley_form(form)
18 |
--------------------------------------------------------------------------------
/parsley/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from .tests import *
2 |
--------------------------------------------------------------------------------
/parsley/tests/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from parsley.mixins import ParsleyAdminMixin
4 |
5 | from .models import Student
6 |
7 |
8 | class StudentAdmin(ParsleyAdminMixin, admin.ModelAdmin):
9 | pass
10 |
--------------------------------------------------------------------------------
/parsley/tests/forms.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from django import forms
4 | from parsley.decorators import parsleyfy
5 | from .models import Student
6 |
7 |
8 | class TextForm(forms.Form):
9 | name = forms.CharField(required=True,)
10 | university = forms.CharField(required=False)
11 |
12 |
13 | @parsleyfy
14 | class TextForm2(forms.Form):
15 | name = forms.CharField(required=True)
16 | university = forms.CharField(required=False)
17 |
18 |
19 | @parsleyfy
20 | class FieldTypeForm(forms.Form):
21 | name = forms.CharField(min_length=3, max_length=30)
22 | url = forms.URLField()
23 | url2 = forms.URLField(required=False)
24 | email = forms.EmailField()
25 | email2 = forms.EmailField(required=False)
26 | age = forms.IntegerField()
27 | income = forms.DecimalField()
28 | income2 = forms.FloatField()
29 | topnav = forms.RegexField(regex="#[A-Fa-f0-9]{6}")
30 | topnav2 = forms.RegexField(regex=re.compile("#[a-z]+", re.IGNORECASE))
31 | some_num = forms.IntegerField(min_value=10, max_value=100)
32 | amount = forms.DecimalField(max_digits=12, decimal_places=2, required=True, max_value=999999999999.99, min_value=-999999999999.99)
33 |
34 |
35 | @parsleyfy
36 | class ExtraDataForm(forms.Form):
37 | name = forms.CharField()
38 | email = forms.EmailField()
39 | email2 = forms.EmailField()
40 | hide_errors = forms.CharField()
41 |
42 | class Meta:
43 | parsley_extras = {
44 | "name": {
45 | "error-message": "Name invalid",
46 | },
47 | "email2": {
48 | "equalto": "email",
49 | "equalto-message": "Must match",
50 | },
51 | "hide_errors": {
52 | "show-errors": False,
53 | },
54 | }
55 |
56 |
57 | class ExtraDataMissingFieldForm(ExtraDataForm):
58 | def __init__(self, *args, **kwargs):
59 | del self.base_fields['email2']
60 | super(ExtraDataMissingFieldForm, self).__init__(*args, **kwargs)
61 |
62 |
63 | @parsleyfy
64 | class FormWithWidgets(forms.Form):
65 | description = forms.CharField(widget=forms.TextInput)
66 | blurb = forms.CharField(widget=forms.TextInput(attrs={
67 | "class": "highlight"}))
68 |
69 | @parsleyfy
70 | class StudentModelForm(forms.ModelForm):
71 | class Meta:
72 | model = Student
73 | fields = '__all__'
74 |
75 | def __init__(self, *args, **kwargs):
76 | super(StudentModelForm, self).__init__(*args, **kwargs)
77 |
78 | def save(self, *args, **kwargs):
79 | if not self.instance.name:
80 | self.name = "Luke Skywalker"
81 | return super(StudentModelForm, self).save(*args, **kwargs)
82 |
83 | @parsleyfy
84 | class FormWithCustomInit(forms.Form):
85 | description = forms.CharField()
86 |
87 | def __init__(self, *args, **kwargs):
88 | super(FormWithCustomInit, self).__init__(*args, **kwargs)
89 | self.fields["description"].initial = "Hello"
90 |
91 |
92 |
93 | class FormWithCleanField(forms.Form):
94 | description = forms.CharField(widget=forms.TextInput)
95 |
96 | def clean_description(self):
97 | raise forms.ValidationError("Error")
98 |
99 |
100 | def get_state_choices():
101 | return [("NY", "NY"), ("OH", "OH")]
102 |
103 | @parsleyfy
104 | class FormWithCustomChoices(forms.Form):
105 | state = forms.ChoiceField(widget=forms.Select(choices=[]))
106 | def __init__(self, *args, **kwargs):
107 | super(FormWithCustomChoices, self).__init__(*args, **kwargs)
108 | self.fields['state'] = forms.ChoiceField(
109 | choices=get_state_choices())
110 |
111 | @parsleyfy
112 | class FormWithRadioSelect(forms.Form):
113 | state = forms.ChoiceField(choices=get_state_choices(), widget=forms.RadioSelect)
114 |
115 |
116 | @parsleyfy
117 | class FormWithRadioSelectNotRequired(forms.Form):
118 | state = forms.ChoiceField(choices=get_state_choices(), required=False, widget=forms.RadioSelect)
119 |
120 |
121 | @parsleyfy
122 | class FormWithMedia(forms.Form):
123 | name = forms.CharField(required=True)
124 |
125 | class Media:
126 | js = ("jquery.min.js",)
127 | css = {"all": ("jquery.css",)}
128 |
129 |
130 | @parsleyfy
131 | class FormWithoutMedia(forms.Form):
132 | name = forms.CharField(required=True)
133 |
134 |
135 | class SSNWidget(forms.MultiWidget):
136 | def __init__(self, *args, **kwargs):
137 | kwargs['widgets'] = [
138 | forms.TextInput(),
139 | forms.TextInput(),
140 | forms.TextInput(),
141 | ]
142 | super(SSNWidget, self).__init__(*args, **kwargs)
143 |
144 | def decompress(self, value):
145 | return value.split('-') if value else [None, None, None]
146 |
147 |
148 | class SSN(forms.MultiValueField):
149 | widget = SSNWidget
150 | def __init__(self, *args, **kwargs):
151 | fields = (
152 | forms.RegexField(r'^(\d)+$', min_length=3, max_length=3),
153 | forms.RegexField(r'^(\d)+$', min_length=3, max_length=3),
154 | forms.RegexField(r'^(\d)+$', min_length=4, max_length=4),
155 | )
156 | super(SSN, self).__init__(fields=fields, *args, **kwargs)
157 |
158 |
159 | @parsleyfy
160 | class MultiWidgetForm(forms.Form):
161 | ssn = SSN()
162 |
163 |
164 | @parsleyfy
165 | class CustomErrorMessageForm(forms.Form):
166 | name = forms.CharField(error_messages={"max_length": "Please only 30 characters"}, max_length=30, required=False)
167 | email = forms.EmailField(error_messages={"invalid": "Invalid email"}, required=False)
168 | favorite_color = forms.CharField(error_messages={"required": "Favorite color is required"})
169 |
170 |
171 | @parsleyfy
172 | class CustomPrefixForm(forms.Form):
173 | name = forms.CharField(required=True)
174 |
175 | class Meta:
176 | parsley_namespace = 'custom'
177 |
--------------------------------------------------------------------------------
/parsley/tests/models.py:
--------------------------------------------------------------------------------
1 | #Django requires every app to have models.
2 | from django.db import models
3 |
4 | class Student(models.Model):
5 | name = models.CharField(max_length=100)
6 |
--------------------------------------------------------------------------------
/parsley/tests/tests.py:
--------------------------------------------------------------------------------
1 | import re
2 | import six
3 |
4 | from django import forms
5 | from django.template import Context, Template
6 | from django.test import TestCase
7 | from django.utils.translation import ugettext_lazy as _
8 |
9 | from parsley.decorators import parsleyfy, parsley_form
10 |
11 | from .forms import (TextForm, TextForm2, FieldTypeForm, ExtraDataForm,
12 | ExtraDataMissingFieldForm, FormWithWidgets, StudentModelForm,
13 | FormWithCleanField, FormWithCustomInit, FormWithCustomChoices,
14 | MultiWidgetForm, CustomErrorMessageForm,
15 | CustomPrefixForm, FormWithRadioSelect, FormWithRadioSelectNotRequired)
16 |
17 |
18 | class ParsleyTestCase(TestCase):
19 |
20 | def assertAttrsEqual(self, a, b):
21 | for k in a.keys(): # ignore unspecified keys
22 | if k in b:
23 | if six.PY3:
24 | x, y = str(a[k]), str(b[k])
25 | else:
26 | x, y = unicode(a[k]), unicode(b[k])
27 | self.assertEqual(x, y)
28 |
29 |
30 | class CharFieldTest(ParsleyTestCase):
31 | def test_basic(self):
32 | """
33 | Tests that parsleyfy will add data-parsley-required for required fields,
34 | but not for required=False fields for CharFields
35 | """
36 | form = TextForm()
37 | self.assertEqual(form.fields["name"].widget.attrs, {})
38 | self.assertEqual(form.fields["university"].widget.attrs, {})
39 | form = parsley_form(TextForm())
40 | self.assertAttrsEqual(form.fields["name"].widget.attrs, {
41 | "data-parsley-required": "true",
42 | "data-parsley-required-message": _("This field is required.")
43 | })
44 | self.assertEqual(form.fields["university"].widget.attrs, {})
45 |
46 |
47 | class CharFieldDecoratedTest(ParsleyTestCase):
48 | def test_decorated(self):
49 | "Tests that parsleyfy works as a class Decorator"
50 | form = TextForm2()
51 | self.assertAttrsEqual(form.fields["name"].widget.attrs, {
52 | "data-parsley-required": "true",
53 | "data-parsley-required-message": _("This field is required.")
54 | })
55 | self.assertEqual(form.fields["university"].widget.attrs, {})
56 |
57 |
58 | class FieldTypeFormTest(ParsleyTestCase):
59 | def test_fields(self):
60 | "Tests that parsleyfy adds data-parsley-required for things other than CharField"
61 | form = FieldTypeForm()
62 | fields = form.fields
63 | self.assertEqual(fields["url"].widget.attrs["data-parsley-required"], "true")
64 | self.assertFalse("data-parsley-required" in fields["url2"].widget.attrs)
65 | self.assertEqual(fields["email"].widget.attrs["data-parsley-required"], "true")
66 | self.assertFalse("data-parsley-required" in fields["email2"].widget.attrs)
67 |
68 |
69 | class DataTypeTest(ParsleyTestCase):
70 | def test_data_types(self):
71 | "Test that different field types get correct data-parsley-type"
72 | form = FieldTypeForm()
73 | fields = form.fields
74 | self.assertTrue("data-parsley-type" in fields["url"].widget.attrs)
75 | self.assertEqual(fields["url"].widget.attrs["data-parsley-type"], "url")
76 | self.assertTrue("data-parsley-type" in fields["email"].widget.attrs)
77 | self.assertEqual(fields["email"].widget.attrs["data-parsley-type"], "email")
78 | self.assertEqual(fields["age"].widget.attrs["data-parsley-type"], "digits")
79 | self.assertEqual(fields["income"].widget.attrs["data-parsley-type"], "number")
80 | self.assertEqual(fields["income2"].widget.attrs["data-parsley-type"], "number")
81 | self.assertEqual(fields["topnav"].widget.attrs["data-parsley-pattern"], "#[A-Fa-f0-9]{6}")
82 | self.assertNotIn("data-parsley-regexp-flag", fields["topnav"].widget.attrs)
83 | self.assertEqual(fields["topnav2"].widget.attrs["data-parsley-pattern"], "#[a-z]+")
84 | self.assertEqual(fields["topnav2"].widget.attrs["data-parsley-regexp-flag"], "i")
85 |
86 |
87 | class LengthTest(ParsleyTestCase):
88 | def test_length(self):
89 | form = FieldTypeForm()
90 | fields = form.fields
91 | name_attrs = fields["name"].widget.attrs
92 | self.assertTrue("data-parsley-minlength" in name_attrs)
93 | self.assertEqual(name_attrs["data-parsley-minlength"], 3)
94 | self.assertEqual(name_attrs["data-parsley-maxlength"], 30)
95 | self.assertEqual(fields["amount"].widget.attrs["data-parsley-max"], 999999999999.99)
96 | self.assertEqual(fields["amount"].widget.attrs["data-parsley-min"], -999999999999.99)
97 |
98 | class ValueTest(ParsleyTestCase):
99 | def test_value(self):
100 | form = FieldTypeForm()
101 | fields = form.fields
102 | num_attrs = fields['some_num'].widget.attrs
103 | self.assertTrue("data-parsley-min" in num_attrs, True)
104 | self.assertTrue("data-parsley-max" in num_attrs, True)
105 | self.assertEqual(num_attrs["data-parsley-min"], 10)
106 | self.assertEqual(num_attrs["data-parsley-max"], 100)
107 |
108 |
109 | class FormWithWidgetsTest(ParsleyTestCase):
110 | def test_widgets(self):
111 | "Assert that @parsleyfy doesn't cloober existing attrs"
112 | form = FormWithWidgets()
113 | self.assertTrue(form.fields["description"].widget, forms.TextInput)
114 | self.assertEqual("highlight", form.fields["blurb"].widget.attrs["class"])
115 |
116 |
117 | class TestMetadata(ParsleyTestCase):
118 | def test_docstring(self):
119 | form1 = TextForm()
120 | form2 = parsley_form(TextForm())
121 | self.assertEqual(form1.__doc__, form2.__doc__)
122 |
123 | def test_module(self):
124 | form1 = TextForm()
125 | form2 = parsley_form(TextForm())
126 | self.assertEqual(form1.__module__, form2.__module__)
127 |
128 | def test_name(self):
129 | form1 = TextForm()
130 | form2 = parsley_form(TextForm())
131 | self.assertEqual(form1.__class__.__name__, form2.__class__.__name__)
132 |
133 |
134 | class TestModelForm(ParsleyTestCase):
135 | def test_model_form(self):
136 | form = StudentModelForm()
137 | fields = form.fields
138 | foo_attrs = fields["name"].widget.attrs
139 | self.assertEqual(foo_attrs["data-parsley-required"], "true")
140 |
141 | def test_model_form_save(self):
142 | form = StudentModelForm({"name": "Luke Skywalker"})
143 | form.save(commit=False)
144 |
145 |
146 | class TestCustomInit(ParsleyTestCase):
147 | def test_custom_init(self):
148 | form = FormWithCustomInit()
149 | self.assertEqual(form.fields["description"].initial, "Hello")
150 |
151 | def test_custom_choices(self):
152 | form = FormWithCustomChoices()
153 | self.assertNotEqual(len(form.fields['state'].choices), 0)
154 | self.assertEqual(form.fields['state'].choices,
155 | [("NY", "NY"), ("OH", "OH")])
156 |
157 |
158 | class TestRadioSelect(ParsleyTestCase):
159 | def test_radio_select(self):
160 | form = FormWithRadioSelect()
161 | self.assertEqual(form.fields['state'].choices,
162 | [("NY", "NY"), ("OH", "OH")])
163 | radio_select_html = form.fields['state'].widget.render("state", "NY")
164 | self.assertEqual(1, len(re.findall('data-parsley-required', radio_select_html)))
165 |
166 | def test_radio_select_not_required(self):
167 | form = FormWithRadioSelectNotRequired()
168 | self.assertEqual(form.fields['state'].choices,
169 | [("NY", "NY"), ("OH", "OH")])
170 | radio_select_html = form.fields['state'].widget.render("state", "NY")
171 | self.assertEqual(0, len(re.findall('data-parsley-required', radio_select_html)))
172 |
173 |
174 | class TestCleanFields(ParsleyTestCase):
175 | def test_clean(self):
176 | form = FormWithCleanField(data={"description": "foo"})
177 | self.assertEqual(form.is_bound, True)
178 | self.assertEqual(form.is_valid(), False)
179 | self.assertTrue(hasattr(form, "clean_description"))
180 |
181 | def test_clean_parslyfied(self):
182 | form = parsleyfy(FormWithCleanField)(data={"description": "foo"})
183 | self.assertEqual(form.is_bound, True)
184 | self.assertEqual(form.is_valid(), False)
185 | self.assertTrue(hasattr(form, "clean_description"))
186 |
187 |
188 | class TestExtraAttributes(ParsleyTestCase):
189 | def test_equalto(self):
190 | form = ExtraDataForm()
191 | attrs = form.fields["email2"].widget.attrs
192 | self.assertAttrsEqual(attrs, {
193 | "data-parsley-type": "email",
194 | "data-parsley-required": "true",
195 | "data-parsley-equalto-message": "Must match",
196 | "data-parsley-equalto": "#id_email",
197 | "data-parsley-required-message": _("This field is required."),
198 | })
199 |
200 | def test_default_data(self):
201 | form = ExtraDataForm()
202 | attrs = form.fields["name"].widget.attrs
203 | self.assertAttrsEqual(attrs, {
204 | "data-parsley-required": "true",
205 | "data-parsley-error-message": "Name invalid",
206 | "data-parsley-required-message": _("This field is required.")
207 | })
208 |
209 | def test_boolean_values(self):
210 | form = ExtraDataForm()
211 | attrs = form.fields["hide_errors"].widget.attrs
212 | self.assertAttrsEqual(attrs, {
213 | "data-parsley-required": "true",
214 | "data-parsley-show-errors": "false",
215 | "data-parsley-required-message": _("This field is required.")
216 | })
217 |
218 | def test_missing_field(self):
219 | ExtraDataMissingFieldForm() # No error should be raised
220 |
221 |
222 | class TestMultiValueField(ParsleyTestCase):
223 | def test_parsley_attributes(self):
224 | form = MultiWidgetForm()
225 | fields = form.fields["ssn"].fields
226 | self.assertAttrsEqual(fields[0].widget.attrs, {
227 | "data-parsley-minlength": 3,
228 | "data-parsley-maxlength": 3,
229 | "maxlength": "3",
230 | "data-parsley-pattern": r'^(\d)+$',
231 | })
232 | self.assertAttrsEqual(fields[1].widget.attrs, {
233 | "data-parsley-minlength": 3,
234 | "data-parsley-maxlength": 3,
235 | "maxlength": "3",
236 | "data-parsley-pattern": r'^(\d)+$',
237 | })
238 | self.assertAttrsEqual(fields[2].widget.attrs, {
239 | "data-parsley-minlength": 4,
240 | "data-parsley-maxlength": 4,
241 | "maxlength": "4",
242 | "data-parsley-pattern": r'^(\d)+$',
243 | })
244 |
245 |
246 | class TestCustomErrorMessages(TestCase):
247 |
248 | def test_new_message(self):
249 | form = CustomErrorMessageForm()
250 | attrs = form.fields['name'].widget.attrs
251 | self.assertEqual(attrs, {
252 | "maxlength": '30',
253 | "data-parsley-maxlength": 30,
254 | "data-parsley-maxlength-message": "Please only 30 characters"
255 | })
256 |
257 | def test_field_type_message(self):
258 | form = CustomErrorMessageForm()
259 | attrs = form.fields['email'].widget.attrs
260 | self.assertEqual(attrs, {
261 | "data-parsley-type": "email",
262 | "data-parsley-type-message": "Invalid email"
263 | })
264 |
265 | def test_override_default_message(self):
266 | form = CustomErrorMessageForm()
267 | attrs = form.fields['favorite_color'].widget.attrs
268 | self.assertEqual(attrs, {
269 | "data-parsley-required": "true",
270 | "data-parsley-required-message": "Favorite color is required"
271 | })
272 |
273 |
274 | class TestCustomPrefix(TestCase):
275 |
276 | def test_default_prefix(self):
277 | form = TextForm2()
278 | attrs = form.fields['name'].widget.attrs
279 | self.assertTrue('data-parsley-required' in attrs)
280 |
281 | def test_custom_prefix(self):
282 | form = CustomPrefixForm()
283 | attrs = form.fields['name'].widget.attrs
284 | self.assertTrue('custom-required' in attrs)
285 |
286 |
287 | class TemplateTagTest(ParsleyTestCase):
288 | def test_basic(self):
289 | """
290 | Tests that parsleyfy will work using the template tag
291 | """
292 |
293 | template = Template("{% load parsley %}")
294 | form = TextForm()
295 | context = Context({'form': form})
296 | template.render(context)
297 |
298 | self.assertEqual(context['form'].fields["name"].widget.attrs, {})
299 | self.assertEqual(context['form'].fields["university"].widget.attrs, {})
300 |
301 | template = Template("{% load parsley %}{% parsleyfy form as form %}")
302 | form = TextForm()
303 | context = Context({'form': form})
304 | template.render(context)
305 |
306 | self.assertAttrsEqual(context['form'].fields["name"].widget.attrs, {
307 | "data-parsley-required": "true",
308 | "data-parsley-required-message": _("This field is required.")
309 | })
310 | self.assertEqual(context['form'].fields["university"].widget.attrs, {})
--------------------------------------------------------------------------------
/parsley/tests/views.py:
--------------------------------------------------------------------------------
1 | # Create your views here.
2 |
--------------------------------------------------------------------------------
/parsley/widgets.py:
--------------------------------------------------------------------------------
1 | class ParsleyChoiceFieldRendererMixin(object):
2 | def __init__(self, name, value, attrs, choices):
3 | self.name = name
4 | self.value = value
5 | self.attrs = attrs
6 | self._choices = choices
7 |
8 | @property
9 | def choices(self):
10 | def choices_iter():
11 | choices_list = self._choices
12 | for idx, choice in enumerate(choices_list):
13 | if idx == len(choices_list) - 1:
14 | self.attrs["{prefix}-required".format(prefix=self.parsley_namespace)] = "true"
15 | yield choice
16 | return choices_iter()
17 |
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | import os
5 | from os.path import dirname, abspath
6 | from optparse import OptionParser
7 |
8 | from django.conf import settings
9 | from django.test.runner import DiscoverRunner
10 |
11 | # For convenience configure settings if they are not pre-configured or if we
12 | # haven't been provided settings to use by environment variable.
13 | if not settings.configured and not os.environ.get('DJANGO_SETTINGS_MODULE'):
14 | settings.configure(
15 | DATABASES={
16 | 'default': {
17 | 'ENGINE': 'django.db.backends.sqlite3',
18 | }
19 | },
20 | INSTALLED_APPS=[
21 | 'parsley',
22 | ],
23 | STATIC_URL="/static/",
24 | TEMPLATES=[
25 | {
26 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
27 | 'APP_DIRS': True,
28 | },
29 | ]
30 | )
31 |
32 | # Setup Django 1.7+ (AppRegistryNotReady).
33 | import django
34 | if hasattr(django, 'setup'):
35 | django.setup()
36 |
37 |
38 | def runtests(*test_args, **kwargs):
39 | if not test_args:
40 | test_args = ['parsley']
41 | parent = dirname(abspath(__file__))
42 | sys.path.insert(0, parent)
43 | test_runner = DiscoverRunner(
44 | verbosity=kwargs.get('verbosity', 1),
45 | interactive=kwargs.get('interactive', False),
46 | failfast=kwargs.get('failfast'))
47 | failures = test_runner.run_tests(test_args)
48 | sys.exit(failures)
49 |
50 | if __name__ == '__main__':
51 | parser = OptionParser()
52 | parser.add_option('--failfast', action='store_true', default=False, dest='failfast')
53 | (options, args) = parser.parse_args()
54 | runtests(failfast=options.failfast, *args)
55 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | VERSION = '0.7'
2 |
3 | import os
4 | import sys
5 | from fnmatch import fnmatchcase
6 | from distutils.util import convert_path
7 | from setuptools import setup, find_packages
8 |
9 |
10 | def read(fname):
11 | try:
12 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
13 | except IOError:
14 | return ""
15 |
16 | # Provided as an attribute, so you can append to these instead
17 | # of replicating them:
18 | standard_exclude = ('*.py', '*.pyc', '*$py.class', '*~', '.*', '*.bak')
19 | standard_exclude_directories = ('.*', 'CVS', '_darcs', './build',
20 | './dist', 'EGG-INFO', '*.egg-info')
21 |
22 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
23 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
24 | # Note: you may want to copy this into your setup.py file verbatim, as
25 | # you can't import this from another package, when you don't know if
26 | # that package is installed yet.
27 | def find_package_data(
28 | where='.', package='',
29 | exclude=standard_exclude,
30 | exclude_directories=standard_exclude_directories,
31 | only_in_packages=True,
32 | show_ignored=False):
33 | """
34 | Return a dictionary suitable for use in ``package_data``
35 | in a distutils ``setup.py`` file.
36 |
37 | The dictionary looks like::
38 |
39 | {'package': [files]}
40 |
41 | Where ``files`` is a list of all the files in that package that
42 | don't match anything in ``exclude``.
43 |
44 | If ``only_in_packages`` is true, then top-level directories that
45 | are not packages won't be included (but directories under packages
46 | will).
47 |
48 | Directories matching any pattern in ``exclude_directories`` will
49 | be ignored; by default directories with leading ``.``, ``CVS``,
50 | and ``_darcs`` will be ignored.
51 |
52 | If ``show_ignored`` is true, then all the files that aren't
53 | included in package data are shown on stderr (for debugging
54 | purposes).
55 |
56 | Note patterns use wildcards, or can be exact paths (including
57 | leading ``./``), and all searching is case-insensitive.
58 | """
59 |
60 | out = {}
61 | stack = [(convert_path(where), '', package, only_in_packages)]
62 | while stack:
63 | where, prefix, package, only_in_packages = stack.pop(0)
64 | for name in os.listdir(where):
65 | fn = os.path.join(where, name)
66 | if os.path.isdir(fn):
67 | bad_name = False
68 | for pattern in exclude_directories:
69 | if (fnmatchcase(name, pattern)
70 | or fn.lower() == pattern.lower()):
71 | bad_name = True
72 | if show_ignored:
73 | print >> sys.stderr, (
74 | "Directory %s ignored by pattern %s"
75 | % (fn, pattern))
76 | break
77 | if bad_name:
78 | continue
79 | if (os.path.isfile(os.path.join(fn, '__init__.py'))
80 | and not prefix):
81 | if not package:
82 | new_package = name
83 | else:
84 | new_package = package + '.' + name
85 | stack.append((fn, '', new_package, False))
86 | else:
87 | stack.append((fn, prefix + name + '/', package, only_in_packages))
88 | elif package or not only_in_packages:
89 | # is a file
90 | bad_name = False
91 | for pattern in exclude:
92 | if (fnmatchcase(name, pattern)
93 | or fn.lower() == pattern.lower()):
94 | bad_name = True
95 | if show_ignored:
96 | print >> sys.stderr, (
97 | "File %s ignored by pattern %s"
98 | % (fn, pattern))
99 | break
100 | if bad_name:
101 | continue
102 | out.setdefault(package, []).append(prefix+name)
103 | return out
104 |
105 | setup(
106 | name="django-parsley",
107 | version=VERSION,
108 | description="Django app for client-side form validation. Wrapper for parsley.js",
109 | long_description=read("README.md"),
110 | author="Agiliq",
111 | author_email="hello@agiliq.com",
112 | license="BSD",
113 | url="http://github.com/agiliq/django-parsley",
114 | packages=find_packages(),
115 | package_data=find_package_data("parsley", only_in_packages=False),
116 | classifiers=[
117 | "Development Status :: 3 - Alpha",
118 | "Environment :: Web Environment",
119 | "Intended Audience :: Developers",
120 | "License :: OSI Approved :: BSD License",
121 | "Operating System :: OS Independent",
122 | "Programming Language :: Python",
123 | "Programming Language :: Python :: 3",
124 | "Framework :: Django",
125 | ],
126 | zip_safe=False,
127 | install_requires=["Django>=1.8"],
128 | tests_require=["Django>=1.8", "six"],
129 | test_suite='runtests.runtests',
130 | )
131 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | py{27,34,35}-dj{18,19,110}
4 | py{27,34,35,36}-dj{111}
5 | skipsdist = True
6 |
7 |
8 | [testenv]
9 | commands = coverage run -a setup.py test
10 | basepython =
11 | py27: python2.7
12 | py34: python3.4
13 | py35: python3.5
14 | py36: python3.6
15 | deps =
16 | coverage>=4
17 | dj18: django==1.8.18
18 | dj19: django==1.9.13
19 | dj110: django==1.10.7
20 | dj111: django==1.11.1
21 |
--------------------------------------------------------------------------------