Загрузка данных
Ошибка **TemplateDoesNotExist**, которую вы видите на экране, означает, что Django не может найти ваши HTML-файлы.
Обычно это происходит по трем причинам:
1. Приложение main не добавлено в список INSTALLED_APPS в файле settings.py.
2. Папка с шаблонами названа с опечаткой (например, template вместо template**s**).
3. Шаблоны лежат не в той директории, где их ожидает увидеть Django.
Чтобы гарантировать, что всё заработает с первого раза, мы немного изменим подход: **создадим общую папку templates в корне проекта** (там же, где лежит manage.py). Это самый надежный способ.
Также я улучшил ваш код, добавил красивый CSS-дизайн (под мобильное устройство, как в задании) и исправил мелкие недочеты.
Ниже представлен **полный и исправленный пошаговый алгоритм**.
### Правильная структура папок
Перед началом убедитесь, что ваша структура выглядит так (папку templates создайте вручную рядом с manage.py):
```text
project/
├── manage.py
├── config/ <-- Папка с настройками
│ ├── settings.py
│ ├── urls.py
│ └── ...
├── main/ <-- Ваше приложение
│ ├── models.py
│ ├── views.py
│ ├── urls.py <-- Создать этот файл
│ └── ...
└── templates/ <-- ВАЖНО! Создайте эту папку здесь
├── form.html
└── dash.html
```
### Шаг 1: Настройка config/settings.py
Откройте config/settings.py. Внесите 3 важных изменения:
1. Добавьте 'main' в INSTALLED_APPS.
2. В блоке TEMPLATES добавьте BASE_DIR / 'templates' в массив DIRS.
3. Добавьте настройки авторизации в самый низ файла.
```python
# ... где-то в начале файла ...
import os
# ... блок INSTALLED_APPS ...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'main', # <-- ОБЯЗАТЕЛЬНО ДОБАВИТЬ!
]
# ... блок TEMPLATES ...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# Указываем Django искать шаблоны в папке templates в корне проекта:
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# ... В САМЫЙ НИЗ ФАЙЛА ДОБАВИТЬ ЭТО ...
AUTH_USER_MODEL = 'main.CustomUser'
LOGIN_REDIRECT_URL = '/dash/'
LOGOUT_REDIRECT_URL = '/'
LOGIN_URL = '/' # Куда перенаправлять неавторизованных
```
### Шаг 2: База данных (main/models.py)
Код отличный, оставляем его, чуть-чуть поправив читаемость.
```python
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
fio = models.CharField('ФИО', max_length=200)
phone = models.CharField('Телефон', max_length=20)
class Application(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name='Заказчик')
address = models.CharField('Адрес', max_length=250)
contacts = models.CharField('Контактные данные', max_length=250)
date = models.DateField('Дата')
time = models.TimeField('Время')
service = models.CharField('Услуга', max_length=50, choices=[
('Общий клининг', 'Общий клининг'),
('Генеральная уборка', 'Генеральная уборка'),
('Послестроительная уборка', 'Послестроительная уборка'),
('Химчистка ковров и мебели', 'Химчистка ковров и мебели')
])
payment = models.CharField('Оплата', max_length=20, choices=[
('Наличные', 'Наличные'),
('Карта', 'Банковская карта')
])
status = models.CharField('Статус', max_length=20, default='Новая', choices=[
('Новая', 'Новая'),
('В работе', 'В работе'),
('Выполнено', 'Выполнено'),
('Отменена', 'Отменена')
])
reason = models.TextField('Причина отмены (если отменена)', blank=True, null=True)
class Meta:
verbose_name = 'Заявка'
verbose_name_plural = 'Заявки'
```
### Шаг 3: Админка (main/admin.py)
```python
from django.contrib import admin
from .models import CustomUser, Application
admin.site.register(CustomUser)
@admin.register(Application)
class ApplicationAdmin(admin.ModelAdmin):
# Добавил FIO заказчика для удобства админа
list_display = ('id', 'get_user_fio', 'service', 'status', 'date')
list_editable = ('status',) # Меняем статус прям в списке
list_filter = ('status', 'service') # Фильтры справа
def get_user_fio(self, obj):
return obj.user.fio
get_user_fio.short_description = 'ФИО Клиента'
```
### Шаг 4: Логика (main/views.py)
```python
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.decorators import login_required
from django import forms
from .models import CustomUser, Application
class RegForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ('username', 'fio', 'phone', 'email')
class AppForm(forms.ModelForm):
class Meta:
model = Application
fields = ['address', 'contacts', 'date', 'time', 'service', 'payment']
widgets = {
'date': forms.DateInput(attrs={'type': 'date'}),
'time': forms.TimeInput(attrs={'type': 'time'})
}
def register(request):
form = RegForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
user = form.save()
login(request, user)
return redirect('dash')
return render(request, 'form.html', {'form': form, 'title': 'Регистрация'})
@login_required
def dash(request):
form = AppForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
app = form.save(commit=False)
app.user = request.user
app.save()
return redirect('dash')
apps = Application.objects.filter(user=request.user).order_by('-id')
return render(request, 'dash.html', {'form': form, 'apps': apps})
```
### Шаг 5: Маршруты
Создайте файл **main/urls.py** (если его нет) и вставьте:
```python
from django.urls import path
from django.contrib.auth.views import LoginView, LogoutView
from . import views
urlpatterns = [
path('', LoginView.as_view(template_name='form.html'), name='login'),
path('reg/', views.register, name='register'),
path('logout/', LogoutView.as_view(), name='logout'),
path('dash/', views.dash, name='dash'),
]
```
Откройте главный **config/urls.py** и убедитесь, что он выглядит так:
```python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('main.urls')),
]
```
### Шаг 6: Дизайн (HTML Шаблоны)
Создайте папку templates **в корне проекта** (рядом с manage.py).
Внутри создайте два файла. Я улучшил дизайн, чтобы он был современным и точно подходил под размеры телефона.
**1. templates/form.html (Авторизация и Регистрация)**
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title|default:"Вход" }}</title>
<style>
body { background: #f0f2f5; display: flex; justify-content: center; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px 0;}
.app-container { width: 390px; min-height: 844px; background: white; padding: 30px; box-sizing: border-box; border-radius: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); overflow-y: auto;}
h2 { text-align: center; color: #333; margin-bottom: 25px;}
input, select { width: 100%; padding: 12px; margin-bottom: 15px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 10px; font-size: 15px; background: #f9f9f9;}
button { width: 100%; padding: 14px; background: #28a745; color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 16px; font-weight: bold; margin-top: 10px;}
button:hover { background: #218838; }
.link { display: block; text-align: center; margin-top: 20px; color: #007bff; text-decoration: none; font-size: 14px;}
.helptext, label { display: none; } /* Прячем лишние подписи Django для чистоты UI */
.errorlist { color: red; font-size: 13px; list-style: none; padding: 0;}
</style>
</head>
<body>
<div class="app-container">
<h2>{% if title %}{{ title }}{% else %}Вход в систему{% endif %}</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">{% if title %}Зарегистрироваться{% else %}Войти{% endif %}</button>
</form>
{% if not title %}
<a class="link" href="{% url 'register' %}">Нет аккаунта? Зарегистрироваться</a>
{% else %}
<a class="link" href="{% url 'login' %}">Уже есть аккаунт? Войти</a>
{% endif %}
</div>
</body>
</html>
```
**2. templates/dash.html (Личный кабинет и заявки)**
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Личный кабинет</title>
<style>
body { background: #f0f2f5; display: flex; justify-content: center; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px 0;}
.app-container { width: 390px; min-height: 844px; background: white; padding: 25px; box-sizing: border-box; border-radius: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); overflow-y: auto;}
h2 { font-size: 20px; color: #333; text-align: center;}
h3 { color: #007bff; border-bottom: 2px solid #007bff; padding-bottom: 5px; margin-top: 25px;}
input, select { width: 100%; padding: 12px; margin-bottom: 12px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 10px; background: #f9f9f9;}
label { font-size: 14px; font-weight: bold; color: #555;}
button { width: 100%; padding: 14px; background: #007bff; color: white; border: none; border-radius: 10px; font-size: 16px; font-weight: bold; cursor: pointer; margin-top: 5px;}
.card { border: 1px solid #eaeaea; padding: 15px; margin-bottom: 15px; border-radius: 15px; background: #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.05);}
.card b { color: #333; }
.status-Новая { color: #17a2b8; font-weight: bold;}
.status-Вработе { color: #ffc107; font-weight: bold;}
.status-Выполнено { color: #28a745; font-weight: bold;}
.status-Отменена { color: #dc3545; font-weight: bold;}
.btn-logout { background: #dc3545; margin-top: 30px;}
</style>
</head>
<body>
<div class="app-container">
<h2>Привет, {{ user.fio|default:user.username }}!</h2>
<h3>Оставить новую заявку</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Создать заявку</button>
</form>
<h3>Мои заявки</h3>
{% for app in apps %}
<div class="card">
<b>Услуга:</b> {{ app.service }}<br>
<b>Дата:</b> {{ app.date|date:"d.m.Y" }} в {{ app.time|time:"H:i" }}<br>
<b>Статус:</b> <span class="status-{{ app.status|cut:' ' }}">{{ app.status }}</span><br>
{% if app.reason and app.status == 'Отменена' %}
<b style="color: red;">Причина:</b> {{ app.reason }}
{% endif %}
</div>
{% empty %}
<p style="text-align:center; color:#777;">У вас пока нет заявок.</p>
{% endfor %}
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<button type="submit" class="btn-logout">Выйти из аккаунта</button>
</form>
</div>
</body>
</html>
```
### Шаг 7: Запуск и проверка
Поскольку мы изменили модель базы данных (сделали поле reason необязательным), нужно обновить БД.
Остановите сервер (Ctrl+C) и выполните команды по очереди:
```bash
python manage.py makemigrations
python manage.py migrate
```
Создаем суперпользователя (если еще не создали или БД очистилась):
```bash
python manage.py createsuperuser
# Логин: adminka
# Пароль: password
```
Запускаем сервер:
```bash
python manage.py runserver
```
Теперь перейдите по адресу http://127.0.0.1:8000/. Ошибка шаблона исчезнет, дизайн будет выглядеть отлично (имитация экрана телефона). Админка будет по адресу http://127.0.0.1:8000/admin/.