Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active May 12, 2026 09:24
Show Gist options
  • Select an option

  • Save sunmeat/b886e7511fc28ae00facf743409bcdb7 to your computer and use it in GitHub Desktop.

Select an option

Save sunmeat/b886e7511fc28ae00facf743409bcdb7 to your computer and use it in GitHub Desktop.
типи полів форми для завантаження файлів та зображень (поки що БЕЗ збереження на сервері)
contact.html:
{% extends "app/layout.html" %}
{% load static %}
{% block content %}
<link rel="stylesheet" href="{% static 'app/css/styles.css' %}">
<div class="container mt-5">
<h2>{{ title|default:"Відправка резюме" }}</h2>
<p class="text-muted">Будь ласка, заповніть дані та прикріпіть файли</p>
<hr>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
<!-- !!! enctype="multipart/form-data" !!! -->
<form method="post" enctype="multipart/form-data" class="row g-3">
{% csrf_token %}
<div class="col-12">
{{ form.as_div }}
</div>
<div class="col-12 mt-4">
<button type="submit" class="btn btn-primary btn-lg px-5">
Надіслати резюме
</button>
</div>
</form>
</div>
{% endblock %}
================================================================================================
forms.py:
from django import forms
from django.core.validators import MinLengthValidator
import os
# pip install Pillow !!! Django для роботи з зображеннями використовує бібліотеку Pillow, тому її потрібно встановити окремо
# якщо не встановити, то при спробі завантажити зображення виникне помилка
# кастомний валідатор для телефону
def validate_phone(value):
"""Перевірка номеру телефону"""
import re
if value:
cleaned = value.replace(' ', '').replace('(', '').replace(')', '').replace('-', '')
if not re.match(r'^\+?380\d{9}$', cleaned):
raise forms.ValidationError("Введіть коректний номер у форматі +380XXXXXXXXX")
# валідатори для файлів
def validate_resume_file(value):
"""Перевірка файлу резюме"""
ext = os.path.splitext(value.name)[1].lower()
valid_extensions = ['.pdf', '.doc', '.docx']
if ext not in valid_extensions:
raise forms.ValidationError("Дозволені тільки формати: PDF, DOC, DOCX")
def validate_avatar_image(value):
"""Перевірка аватарки"""
ext = os.path.splitext(value.name)[1].lower()
valid_extensions = ['.jpg', '.jpeg', '.png']
if ext not in valid_extensions:
raise forms.ValidationError("Дозволені тільки формати: JPG, JPEG, PNG")
class ContactForm(forms.Form):
"""Форма для відправки резюме"""
full_name = forms.CharField(
label="ПІБ",
max_length=150,
validators=[MinLengthValidator(3)],
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Шевченко Тарас Григорович'
})
)
email = forms.EmailField(
label="Email адреса",
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'taras.shevchenko@gmail.com'
})
)
phone = forms.CharField(
label="Номер телефону",
max_length=20,
validators=[validate_phone],
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '+380 XX XXX XX XX',
'type': 'tel'
})
)
# !!!!! робота з файлами
resume_file = forms.FileField(
label="Файл з резюме",
validators=[validate_resume_file],
help_text="Дозволено: PDF, DOC, DOCX (максимум 10 МБ)",
widget=forms.ClearableFileInput(attrs={'class': 'form-control'})
)
avatar = forms.ImageField(
label="Фото (аватар)",
required=False,
validators=[validate_avatar_image],
help_text="Дозволено: JPG, JPEG, PNG (максимум 5 МБ)",
widget=forms.ClearableFileInput(attrs={'class': 'form-control'})
)
# додаткова валідація розмірів файлів
def clean_resume_file(self):
"""Перевірка розміру файлу резюме"""
resume = self.cleaned_data.get('resume_file')
if resume and resume.size > 10 * 1024 * 1024: # 10 MB
raise forms.ValidationError("Файл резюме занадто великий. Максимальний розмір — 10 МБ.")
return resume
def clean_avatar(self):
"""Перевірка розміру аватара"""
avatar = self.cleaned_data.get('avatar')
if avatar and avatar.size > 5 * 1024 * 1024: # 5 MB
raise forms.ValidationError("Фото занадто велике. Максимальний розмір — 5 МБ.")
return avatar
def clean(self):
cleaned_data = super().clean()
# приклад додаткової логіки
phone = cleaned_data.get('phone')
email = cleaned_data.get('email')
if not phone and not email:
raise forms.ValidationError("Вкажіть хоча б один контактний спосіб (телефон або email).")
return cleaned_data
================================================================================================
views.py:
from datetime import datetime
from django.http import HttpRequest
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm
def contact(request):
"""Обробка форми зворотнього зв'язку"""
if request.method == 'POST':
form = ContactForm(request.POST, request.FILES) # !!!!! request.FILES для обробки файлів
if form.is_valid():
# ====================== ВИВІД У КОНСОЛЬ ======================
print("\n" + "="*60)
print("НОВЕ ПОВІДОМЛЕННЯ З ФОРМИ ОТРИМАНО!")
print("="*60)
print(f"Дата та час: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("-" * 60)
for field_name, value in form.cleaned_data.items():
if value is None or value == '':
value = "—"
label = form.fields[field_name].label or field_name
print(f"{label:35} : {value}")
print("="*60 + "\n")
# повідомлення користувачу
messages.success(request, 'Ваша заявка успішно надіслана! Дякуємо.')
return redirect('contact')
else:
# якщо є помилки валідації
messages.error(request, 'Будь ласка, виправте помилки у формі.')
else:
# GET-запит - порожня форма
form = ContactForm()
return render(request, 'app/contact.html', {
'form': form,
'title': 'Надішліть ваше повідомлення',
'year': datetime.now().year,
})
def home(request):
"""Renders the home page."""
assert isinstance(request, HttpRequest)
return render(
request,
'app/index.html',
{
'title': 'Home Page',
'year': datetime.now().year,
}
)
def about(request):
"""Renders the about page."""
assert isinstance(request, HttpRequest)
return render(
request,
'app/about.html',
{
'title': 'About',
'message': 'Your application description page.',
'year': datetime.now().year,
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment