This commit is contained in:
@@ -1,16 +1,36 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
PHONE_MIN_DIGITS = 9
|
||||||
|
PHONE_MAX_DIGITS = 15
|
||||||
|
|
||||||
|
|
||||||
class FaqQuestionForm(forms.Form):
|
class FaqQuestionForm(forms.Form):
|
||||||
name = forms.CharField(
|
name = forms.CharField(
|
||||||
label="Имя",
|
label="Имя",
|
||||||
max_length=150,
|
max_length=150,
|
||||||
widget=forms.TextInput(attrs={"placeholder": "Имя", "autocomplete": "name"}),
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Имя",
|
||||||
|
"autocomplete": "name",
|
||||||
|
"title": "Имя не должно содержать цифры",
|
||||||
|
"data-faq-name-input": "",
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
phone = forms.CharField(
|
phone = forms.CharField(
|
||||||
label="Номер телефона",
|
label="Номер телефона",
|
||||||
max_length=64,
|
max_length=64,
|
||||||
widget=forms.TextInput(attrs={"placeholder": "Номер телефона", "autocomplete": "tel"}),
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Номер телефона",
|
||||||
|
"autocomplete": "tel",
|
||||||
|
"inputmode": "tel",
|
||||||
|
"pattern": r"^\+?\d+(?:-\d+)*$",
|
||||||
|
"title": "Телефон должен начинаться с + или цифры и содержать только цифры и дефисы",
|
||||||
|
"data-faq-phone-input": "",
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
question = forms.CharField(
|
question = forms.CharField(
|
||||||
label="Ваш вопрос",
|
label="Ваш вопрос",
|
||||||
@@ -23,10 +43,33 @@ class FaqQuestionForm(forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
return self.cleaned_data["name"].strip()
|
name = self.cleaned_data["name"].strip()
|
||||||
|
if any(char.isdigit() for char in name):
|
||||||
|
raise forms.ValidationError("Имя не должно содержать цифры.")
|
||||||
|
return name
|
||||||
|
|
||||||
def clean_phone(self):
|
def clean_phone(self):
|
||||||
return self.cleaned_data["phone"].strip()
|
phone = self.cleaned_data["phone"].strip()
|
||||||
|
digits_count = sum(char.isdigit() for char in phone)
|
||||||
|
|
||||||
|
if not phone or not (phone[0].isdigit() or phone[0] == "+"):
|
||||||
|
raise forms.ValidationError("Телефон должен начинаться с + или цифры.")
|
||||||
|
|
||||||
|
if phone.startswith("+"):
|
||||||
|
phone_body = phone[1:]
|
||||||
|
else:
|
||||||
|
phone_body = phone
|
||||||
|
|
||||||
|
if not phone_body or any(not (char.isdigit() or char == "-") for char in phone_body):
|
||||||
|
raise forms.ValidationError("Телефон может содержать только цифры и дефисы.")
|
||||||
|
|
||||||
|
if "--" in phone_body or phone_body.startswith("-") or phone_body.endswith("-"):
|
||||||
|
raise forms.ValidationError("Дефис в телефоне должен быть между цифрами.")
|
||||||
|
|
||||||
|
if not (PHONE_MIN_DIGITS <= digits_count <= PHONE_MAX_DIGITS):
|
||||||
|
raise forms.ValidationError("Телефон должен содержать от 9 до 15 цифр.")
|
||||||
|
|
||||||
|
return phone
|
||||||
|
|
||||||
def clean_question(self):
|
def clean_question(self):
|
||||||
return self.cleaned_data["question"].strip()
|
return self.cleaned_data["question"].strip()
|
||||||
|
|||||||
@@ -40,6 +40,79 @@ class FaqQuestionTests(TestCase):
|
|||||||
self.assertEqual(len(mail.outbox), 0)
|
self.assertEqual(len(mail.outbox), 0)
|
||||||
self.assertContains(response, "Необходимо согласие на обработку персональных данных.")
|
self.assertContains(response, "Необходимо согласие на обработку персональных данных.")
|
||||||
|
|
||||||
|
def test_faq_question_form_rejects_digits_in_name(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("home"),
|
||||||
|
data={
|
||||||
|
"name": "Иван123",
|
||||||
|
"phone": "+375291112233",
|
||||||
|
"question": "Сколько стоит доставка?",
|
||||||
|
"accept": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(mail.outbox), 0)
|
||||||
|
self.assertContains(response, "Имя не должно содержать цифры.")
|
||||||
|
|
||||||
|
def test_faq_question_form_rejects_invalid_phone_characters(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("home"),
|
||||||
|
data={
|
||||||
|
"name": "Иван Петров",
|
||||||
|
"phone": "+375 (29) abc",
|
||||||
|
"question": "Сколько стоит доставка?",
|
||||||
|
"accept": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(mail.outbox), 0)
|
||||||
|
self.assertContains(response, "Телефон может содержать только цифры и дефисы.")
|
||||||
|
|
||||||
|
def test_faq_question_form_accepts_phone_with_plus_and_dashes(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("home"),
|
||||||
|
data={
|
||||||
|
"name": "Иван Петров",
|
||||||
|
"phone": "+375-29-111-22-33",
|
||||||
|
"question": "Сколько стоит доставка?",
|
||||||
|
"accept": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
|
||||||
|
def test_faq_question_form_accepts_phone_starting_with_digit(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("home"),
|
||||||
|
data={
|
||||||
|
"name": "Иван Петров",
|
||||||
|
"phone": "375291112233",
|
||||||
|
"question": "Сколько стоит доставка?",
|
||||||
|
"accept": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
|
||||||
|
def test_faq_question_form_rejects_phone_with_invalid_digits_count(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("home"),
|
||||||
|
data={
|
||||||
|
"name": "Иван Петров",
|
||||||
|
"phone": "+12345",
|
||||||
|
"question": "Сколько стоит доставка?",
|
||||||
|
"accept": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(mail.outbox), 0)
|
||||||
|
self.assertContains(response, "Телефон должен содержать от 9 до 15 цифр.")
|
||||||
|
|
||||||
|
|
||||||
class HomePopularProductsTests(TestCase):
|
class HomePopularProductsTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -490,6 +490,12 @@ header {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.phone-link svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex: 0 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.breadcrumbs-container {
|
.breadcrumbs-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
53
static/js/faq-form-validation.js
Normal file
53
static/js/faq-form-validation.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const nameInput = document.querySelector('[data-faq-name-input]')
|
||||||
|
const phoneInput = document.querySelector('[data-faq-phone-input]')
|
||||||
|
|
||||||
|
const sanitizeName = function (value) {
|
||||||
|
return value.replace(/\d/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizePhone = function (value) {
|
||||||
|
let result = ''
|
||||||
|
|
||||||
|
Array.from(value).forEach(function (char) {
|
||||||
|
if (/\d/.test(char)) {
|
||||||
|
result += char
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char === '+' && result === '') {
|
||||||
|
result = '+'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char === '-' && /\d$/.test(result) && !result.endsWith('-')) {
|
||||||
|
result += '-'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizeInput = function (input, sanitizer) {
|
||||||
|
if (!input) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('input', function () {
|
||||||
|
const sanitizedValue = sanitizer(input.value)
|
||||||
|
|
||||||
|
if (input.value !== sanitizedValue) {
|
||||||
|
const position = input.selectionStart
|
||||||
|
input.value = sanitizedValue
|
||||||
|
|
||||||
|
if (typeof position === 'number') {
|
||||||
|
const nextPosition = Math.min(position, input.value.length)
|
||||||
|
input.setSelectionRange(nextPosition, nextPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizeInput(nameInput, sanitizeName)
|
||||||
|
sanitizeInput(phoneInput, sanitizePhone)
|
||||||
|
})
|
||||||
@@ -24,4 +24,8 @@
|
|||||||
{% include 'home/_fragments/faq.html' %}
|
{% include 'home/_fragments/faq.html' %}
|
||||||
|
|
||||||
{% include "_fragments/footer.html" %}
|
{% include "_fragments/footer.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block add_js %}
|
||||||
|
<script src="{% static 'js/faq-form-validation.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user