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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)