Last active
May 12, 2026 09:24
-
-
Save sunmeat/b886e7511fc28ae00facf743409bcdb7 to your computer and use it in GitHub Desktop.
типи полів форми для завантаження файлів та зображень (поки що БЕЗ збереження на сервері)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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