Загрузка данных


Ошибка **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/.