django-forms-frontend-validation 1.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "webpack",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "private": true,
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "build": "webpack"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "devDependencies": {
14
+ "css-loader": "^7.1.2",
15
+ "style-loader": "^4.0.0",
16
+ "webpack": "^5.97.1",
17
+ "webpack-cli": "^5.1.4"
18
+ }
19
+ }
@@ -0,0 +1,42 @@
1
+ /****** Forms ******/
2
+ input, select {
3
+ font-size: 18px!important;
4
+ }
5
+
6
+ .errorlist li, .error-msg {
7
+ color: red;
8
+ }
9
+
10
+ .error {
11
+ color: red;
12
+ font-style: italic;
13
+ }
14
+
15
+ .error-input {
16
+ background: #edbabf;
17
+ border-color: #dc3545;
18
+ }
19
+
20
+ .error-msg {
21
+ color: #dc3545;
22
+ margin-bottom: 0;
23
+ }
24
+
25
+ .success {
26
+ color: blue;
27
+ }
28
+
29
+ .readonly {
30
+ background-color: #e9ecef;
31
+ opacity: 1;
32
+ cursor: not-allowed;
33
+ }
34
+
35
+ .required-field {
36
+ font-weight: 800;
37
+ }
38
+
39
+ .required-field::after {
40
+ content: " *";
41
+ color: red;
42
+ }
@@ -0,0 +1,263 @@
1
+ // ########## Getting the csrf token for the fetch calls ##########
2
+ function getCookie(name) {
3
+ let cookieValue = null;
4
+ if (document.cookie && document.cookie !== '') {
5
+ const cookies = document.cookie.split(';');
6
+ for (let i = 0; i < cookies.length; i++) {
7
+ const cookie = cookies[i].trim();
8
+ // Does this cookie string begin with the name we want?
9
+ if (cookie.substring(0, name.length + 1) === (name + '=')) {
10
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
11
+ break;
12
+ }
13
+ }
14
+ }
15
+ return cookieValue;
16
+ }
17
+ export const csrftoken = getCookie('csrftoken');
18
+
19
+ // ##### Fetch Calls #####
20
+ // performs fetch call to view
21
+ export const fetchHandleForm = async (form) => {
22
+ let formData = new FormData(form);
23
+ return await fetch(form.action, {
24
+ method: "POST",
25
+ credentials: "same-origin",
26
+ headers: {
27
+ // "Accept": "m",
28
+ // "X-Requested-With": "XMLHttpRequest",
29
+ "X-CSRFToken": csrftoken,
30
+ },
31
+ body: formData,
32
+ }).then(async (response) => {
33
+ return response.json();
34
+ });
35
+ }
36
+ // ***** Adding asterisks to labels of required inputs *****
37
+ function addAsterisks(form) {
38
+ // gathering all inputs
39
+ let formGroups = document.querySelectorAll(`#${form.id} .form-group`);
40
+ let inputs = getRequiredFields(formGroups);
41
+
42
+ // adding the required-field class which will add the asterisk
43
+ for (let i = 0; i < inputs.length; i++) {
44
+ let label = document.querySelector(`label[for=${inputs[i].name}]`);
45
+ if (inputs[i].required) {
46
+ label.classList.add("required-field");
47
+ }
48
+ }
49
+ }
50
+
51
+ // In-line validation on required fields
52
+ function validateInputs(form) {
53
+ // gathering all inputs
54
+ let formGroups = document.querySelectorAll(`#${form.id} .form-group`);
55
+ let inputs = getRequiredFields(formGroups);
56
+
57
+ // adding listeners to each input for validation
58
+ for (let i = 0; i < inputs.length; i++) {
59
+ inputs[i].addEventListener("focusout", () => {
60
+ if (inputs[i].value === "" || inputs[i].value === null) {
61
+ addError(inputs[i]);
62
+ } else {
63
+ removeError(inputs[i]);
64
+ }
65
+ });
66
+ }
67
+ }
68
+ // validateInputs();
69
+
70
+ // check form function
71
+ export function checkForm(form) {
72
+ let errors = false;
73
+
74
+ // gathering all inputs
75
+ let formGroups = document.querySelectorAll(`#${form.id} .form-group`);
76
+ let inputs = getRequiredFields(formGroups);
77
+
78
+ // iterating through all required fields to check for invalidation
79
+ for (let i = 0; i < inputs.length; i++) {
80
+ let input = inputs[i];
81
+ if (input.value === "" || !input.value) {
82
+ addError(input);
83
+ errors = true;
84
+ }
85
+ }
86
+
87
+ return errors
88
+ }
89
+
90
+ // submit button validation check
91
+ function submitValidation(form) {
92
+ form.form.addEventListener("submit", (e) => {
93
+ // preventing default submission behavior
94
+ e.preventDefault();
95
+
96
+ // gathering all inputs
97
+ let f = e.target;
98
+ // let formGroups = document.querySelectorAll(`#${form.id} .form-group`);
99
+ // let inputs = getRequiredFields(formGroups);
100
+ // // let form = document.getElementById("form") !== null ? document.getElementById("form") : document.getElementById("userForm");
101
+ let errors = checkForm(f);
102
+
103
+ // submitting the form if there aren't any errors
104
+ if (!errors) {
105
+ form.form.submit();
106
+ } else {
107
+ let invalidInputs = document.getElementsByClassName("validation-error");
108
+ // scroll to the first invalid input
109
+ invalidInputs[0].parentElement.scrollIntoView();
110
+ }
111
+ });
112
+ }
113
+
114
+ // adds an error to an input
115
+ function addError(input) {
116
+ let inputParent = input.parentElement;
117
+ if (!inputParent.className.includes("form-group")){
118
+ inputParent = input.parentElement.parentElement;
119
+ }
120
+ let errorAdded = inputParent.dataset.errorAdded;
121
+ inputParent.classList.add("validation-error");
122
+ input.classList.add("error-input")
123
+ if (errorAdded === "false" || !errorAdded) {
124
+ // creating the error message p
125
+ let eP = document.createElement("p");
126
+ eP.setAttribute("class", "error-msg");
127
+ eP.innerText = "This input is required";
128
+ inputParent.appendChild(eP);
129
+ inputParent.dataset.errorAdded = "true";
130
+ }
131
+ }
132
+
133
+ // removes the error from an input
134
+ function removeError(input) {
135
+ let inputParent = input.parentElement;
136
+ if (!inputParent.className.includes("form-group")){
137
+ inputParent = input.parentElement.parentElement;
138
+ }
139
+ let errorAdded = inputParent.dataset.errorAdded;
140
+ inputParent.classList.remove("validation-error");
141
+ input.classList.remove("error-input");
142
+ // removing the error message p
143
+ if (errorAdded === "true") {
144
+ inputParent.removeChild(inputParent.lastElementChild);
145
+ inputParent.dataset.errorAdded = "false";
146
+ }
147
+ }
148
+
149
+ // function to get all required input
150
+ function getRequiredFields(formGroups) {
151
+ let nameList = ["SELECT", "INPUT", "TEXTAREA"];
152
+ let inputs = [];
153
+ // let formGroups = document.getElementsByClassName("form-group");
154
+ for (let i = 0; i < formGroups.length; i++) {
155
+ let children = formGroups[i].children;
156
+ for (let j = 0; j < children.length; j++) {
157
+ if (children[j].tagName === "DIV"){
158
+ let grandChildren = children[j].children;
159
+ for (let a = 0; a < grandChildren.length; a++) {
160
+ if (nameList.includes(grandChildren[a].tagName)) {
161
+ if (grandChildren[a].required) {
162
+ inputs.push(grandChildren[a]);
163
+ }
164
+ }
165
+ }
166
+ }
167
+ else{
168
+ if (nameList.includes(children[j].tagName)) {
169
+ if (children[j].required) {
170
+ inputs.push(children[j]);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return inputs
177
+ }
178
+
179
+ // checks if the form will be ignored form validation.
180
+ function getIgnored(form, ignoredClasses) {
181
+ let isIgnored = false;
182
+ let classes = form.classList;
183
+ if (ignoredClasses.length > 0) {
184
+ classes.forEach((_class) => {
185
+ if (ignoredClasses.includes(_class)) {
186
+ isIgnored = true;
187
+ return isIgnored;
188
+ }
189
+ });
190
+ }
191
+ return isIgnored
192
+ }
193
+
194
+ // Checks if the form will be validated.
195
+ function isValidate(form, ignoredClasses) {
196
+ let enableValidate = true;
197
+ let classes = form.classList;
198
+ if (ignoredClasses.length > 0) {
199
+ classes.forEach((_class) => {
200
+ if (ignoredClasses.includes(_class)) {
201
+ enableValidate = false;
202
+ return enableValidate;
203
+ }
204
+ });
205
+ }
206
+ return enableValidate
207
+ }
208
+
209
+ // Confirms if a form will only validate the form on submit, only.
210
+ function getValidateOnlyValidateOnSubmit(form, validateOnlyOnSubmit, enableValidation) {
211
+ let validateOnSubmit = false;
212
+ let trueFlags = ["all", "__all__", "*", "true"];
213
+ if (enableValidation) {
214
+ let classes = form.classList;
215
+ if (trueFlags.includes(validateOnlyOnSubmit[0])) {
216
+ validateOnSubmit = true;
217
+ return true;
218
+ }
219
+ else {
220
+ classes.forEach((_class) => {
221
+ if (validateOnlyOnSubmit.includes(_class)) {
222
+ validateOnSubmit = true;
223
+ return validateOnlyOnSubmit;
224
+ }
225
+ });
226
+ }
227
+ }
228
+ return validateOnSubmit;
229
+ }
230
+
231
+ // adds listener logic to the form
232
+ export function _Initialize(form={}){
233
+ if (!form.ignored || form.enableValidation) {
234
+ // add all listeners to each form
235
+ addAsterisks(form);
236
+ if (!form.validateOnlyOnSubmit) {
237
+ validateInputs(form);
238
+ }
239
+ if (!form.ignored) {
240
+ submitValidation(form);
241
+ }
242
+ }
243
+ }
244
+
245
+ // function to initialize forms into a json object
246
+ export function _InitializeForms(formNodes, ignoredFormClasses=[], ignoredValidationClasses=[], validateOnlyOnSubmit){
247
+ for (let i = 0; i < formNodes.length; i++) {
248
+ let currentForm = formNodes[i];
249
+ let ignored = getIgnored(currentForm, ignoredFormClasses);
250
+ let enableValidation = isValidate(currentForm, ignoredValidationClasses);
251
+ let validateOnSubmit = getValidateOnlyValidateOnSubmit(currentForm, validateOnlyOnSubmit, enableValidation);
252
+ let _form = {
253
+ id: currentForm.id,
254
+ form: currentForm,
255
+ ignored: ignored,
256
+ enableValidation: enableValidation,
257
+ validateOnlyOnSubmit: validateOnSubmit,
258
+ }
259
+
260
+ // adding form functionality
261
+ _Initialize(_form);
262
+ }
263
+ }
@@ -0,0 +1,2 @@
1
+ import './formFunctions';
2
+ import '../css/style.css';
@@ -0,0 +1,23 @@
1
+ const path = require("path");
2
+
3
+ module.exports = {
4
+ mode: "development",
5
+ entry: {
6
+ forms: ["./src/css/style.css", "./src/js/formFunctions.js",],
7
+ },
8
+ output: {
9
+ filename: "[name].bundle.js",
10
+ path: path.resolve(__dirname, "../dist"),
11
+ library: "fv",
12
+ libraryTarget: "umd",
13
+ globalObject: "this",
14
+ },
15
+ module: {
16
+ rules: [
17
+ {
18
+ test: /\.css/i,
19
+ use: ["style-loader", "css-loader"],
20
+ },
21
+ ],
22
+ },
23
+ }
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ {% load static %}
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>{% block title %}{{ title }}{% endblock %}</title>
7
+
8
+ <!-- Bootstrap CDNs -->
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
10
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
11
+
12
+ {% block extra_head %}{% endblock %}
13
+ </head>
14
+ <body>
15
+ <!-- header -->
16
+ {% include 'includes/header.html' %}
17
+
18
+ <!-- main content -->
19
+ <div class="container my-3">
20
+ {% block content %}{% endblock %}
21
+ </div>
22
+
23
+ {% block extra_js %}{% endblock %}
24
+
25
+ </body>
26
+ </html>
@@ -0,0 +1,51 @@
1
+ {% extends 'base.html' %}
2
+ {% load static %}
3
+
4
+ {% block content %}
5
+ <h3>Sample Form</h3>
6
+ <hr>
7
+ <p>
8
+ Sample form where forms.bundle.js is being added via an HTML <code>&lt;script&gt;</code> tag.
9
+ Then, the object variable, <code>fv</code>, is being exported and used within another <code>&lt;script&gt;</code> tag.
10
+ </p>
11
+ <div id="formDiv">
12
+ <form id="sampleForm" action="{% url 'sample-form' %}" method="post" novalidate>
13
+ {% csrf_token %}
14
+ {% if form.non_field_errors %}
15
+ {% for error in form.non_field_errors %}
16
+ {{ error }}
17
+ {% endfor %}
18
+ {% endif %}
19
+
20
+ {% for field in form %}
21
+ <div class="form-group mb-3">
22
+ <label class="form-label" for="{{ field.name }}">{{ field.label }}</label>
23
+ {{ field }}
24
+ {% if field.errors %}
25
+ {{ field.errors }}
26
+ {% endif %}
27
+ </div>
28
+ {% endfor %}
29
+
30
+ <button class="btn btn-primary btn-lg" type="submit">Submit</button>
31
+ </form>
32
+ </div>
33
+ {% endblock %}
34
+
35
+ {% block extra_js %}
36
+ <script src="{% static 'dist/forms.bundle.js' %}"></script>
37
+ <script>
38
+ // fv is exported from forms.bundle.js
39
+ window.addEventListener("load", () => {
40
+ let ignoredClasses = []; // add more classes that represent forms you want this script to ignore.
41
+ let ignoreValidation = []; // add any form classes that you want to ignore validation
42
+ let validateOnlyOnSubmit = []; // for hitting all forms make index 0 either __all__, all, * or leave blank for false or use false
43
+ let forms = document.getElementsByTagName("form");
44
+ // if (form || userForm) {
45
+ if (forms.length > 0) {
46
+ // calling specific functions on all forms
47
+ fv._InitializeForms(forms, ignoredClasses, ignoreValidation, validateOnlyOnSubmit);
48
+ }
49
+ });
50
+ </script>
51
+ {% endblock %}
@@ -0,0 +1,51 @@
1
+ {% extends 'base.html' %}
2
+ {% load static %}
3
+
4
+ {% block extra_head %}
5
+ <script src="https://assets.tucsonaz.gov/share/web/js/forms.bundle.js" crossorigin="anonymous"></script>
6
+ {% endblock %}
7
+
8
+ {% block content %}
9
+ <h3>Sample Form 2</h3>
10
+ <hr>
11
+ <p>This form only validates when the submit button is clicked.</p>
12
+ <div id="formDiv">
13
+ <form id="sampleForm" class="sample-form2" action="{% url 'sample-form2' %}" method="post" novalidate>
14
+ {% csrf_token %}
15
+ {% if form.non_field_errors %}
16
+ {% for error in form.non_field_errors %}
17
+ {{ error }}
18
+ {% endfor %}
19
+ {% endif %}
20
+
21
+ {% for field in form %}
22
+ <div class="form-group mb-3">
23
+ <label class="form-label" for="{{ field.name }}">{{ field.label }}</label>
24
+ {{ field }}
25
+ {% if field.errors %}
26
+ {{ field.errors }}
27
+ {% endif %}
28
+ </div>
29
+ {% endfor %}
30
+
31
+ <button class="btn btn-primary btn-lg" type="submit">Submit</button>
32
+ </form>
33
+ </div>
34
+ {% endblock %}
35
+
36
+ {% block extra_js %}
37
+ <script>
38
+ // fv (formsvalidator) is exported from forms.bundle.js
39
+ window.addEventListener("load", () => {
40
+ let ignoredClasses = {{ form_validator.ignored_classes|safe }}; // add more classes that represent forms you want this script to ignore.
41
+ let ignoreValidation = {{ form_validator.ignore_validation|safe }}; // add any form classes that you want to ignore validation
42
+ let validateOnlyOnSubmit = {{ form_validator.validate_only_on_submit|safe }}; // for hitting all forms make index 0 either __all__, all, * or leave blank for false or use false
43
+ let forms = document.getElementsByTagName("form");
44
+ // if (form || userForm) {
45
+ if (forms.length > 0) {
46
+ // calling specific functions on all forms
47
+ fv._InitializeForms(forms, ignoredClasses, ignoreValidation, validateOnlyOnSubmit);
48
+ }
49
+ });
50
+ </script>
51
+ {% endblock %}
@@ -0,0 +1,18 @@
1
+ <nav class="navbar navbar-expand-lg bg-body-tertiary">
2
+ <div class="container">
3
+ <a class="navbar-brand" href="{% url 'sample-form' %}">Form Validator</a>
4
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
5
+ <span class="navbar-toggler-icon"></span>
6
+ </button>
7
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
8
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
9
+ <li class="nav-item">
10
+ <a class="nav-link" href="{% url 'sample-form' %}">Sample Form</a>
11
+ </li>
12
+ <li class="nav-item">
13
+ <a class="nav-link" href="{% url 'sample-form2' %}">Sample Form 2</a>
14
+ </li>
15
+ </ul>
16
+ </div>
17
+ </div>
18
+ </nav>
formvalidator/tests.py ADDED
@@ -0,0 +1,21 @@
1
+ from django.test import TestCase
2
+
3
+ from .form_utils.form_utils import FormsValidator
4
+
5
+
6
+ # Create your tests here.
7
+ class GetContextTest(TestCase):
8
+ def setUp(self):
9
+ self.form_validator = FormsValidator()
10
+ self.context = self.form_validator.get_context()
11
+
12
+ def test_get_context(self):
13
+ """Ensuring that the context variable is set correctly"""
14
+ self.assertEqual(type(self.context), dict, msg="The context variable is not a dictionary datatype")
15
+
16
+ def test_context_variables(self):
17
+ """Tests that the context variables are set correctly"""
18
+ context_keys = self.context.keys()
19
+ correct_keys = ["ignored_classes", "ignore_validation", "validate_only_on_submit"]
20
+ for key in context_keys:
21
+ self.assertTrue(key in correct_keys, msg=f"The context variable {key} is missing")
formvalidator/urls.py ADDED
@@ -0,0 +1,12 @@
1
+ from django.urls import path
2
+ from django.conf import settings
3
+
4
+ from . import views
5
+
6
+ urlpatterns = []
7
+
8
+ if settings.DEBUG:
9
+ urlpatterns += [
10
+ path('demo/sample-form/', views.sample, name='sample-form'),
11
+ path('demo/sample-form2/', views.sample2, name='sample-form2'),
12
+ ]
formvalidator/views.py ADDED
@@ -0,0 +1,54 @@
1
+ from django.shortcuts import render
2
+
3
+ from .forms import SampleForm
4
+
5
+ from .form_utils import FormsValidator
6
+
7
+
8
+ # Create your views here.
9
+ def sample(request):
10
+ """
11
+ Sample form view for demonstration purposes only.
12
+ Will provide an example of using the forms.bundle.js file as an imported module.
13
+ Will also not use the settings.py variables.
14
+ """
15
+ title = "Form Validator Sample"
16
+ template = "formvalidator/sample.html"
17
+
18
+ if request.method == 'POST':
19
+ form = SampleForm(request.POST)
20
+ if form.is_valid():
21
+ pass
22
+ else:
23
+ form = SampleForm()
24
+
25
+ context = {
26
+ "form": form,
27
+ "title": title,
28
+ }
29
+ return render(request, template, context)
30
+
31
+
32
+ def sample2(request):
33
+ """
34
+ Another sample form view for demonstration purposes only.
35
+ This demo will show how to use the forms.bundle.js file as a CDN, and using the settings variables
36
+ for the configuration.
37
+ """
38
+ title = "Form Validator Sample 2"
39
+ template = "formvalidator/sample2.html"
40
+ form_validator = FormsValidator()
41
+
42
+ if request.method == 'POST':
43
+ form = SampleForm(request.POST)
44
+ if form.is_valid():
45
+ pass
46
+ else:
47
+ form = SampleForm()
48
+
49
+ context = {
50
+ "form": form,
51
+ "title": title,
52
+ "form_validator": form_validator.get_context(),
53
+ }
54
+ return render(request, template, context)