Структура модуля
Обновлено: 07 Декабрь 2025
Версия: 2.2
Модуль должен быть упакован в ZIP архив и содержать следующие компоненты:
module_name/
├── module.json # Конфигурационный файл (обязателен)
├── models.py # Модели Django
├── views.py # Представления
├── urls.py # URL маршруты
├── admin.py # Админка
├── apps.py # Конфигурация приложения (обязателен)
├── migrations/ # Миграции базы данных
│ └── 0001_initial.py # Начальная миграция
├── templates/ # Шаблоны
│ └── module_name/ # Папка с именем модуля
├── static/ # Статические файлы
└── ... # Другие файлы приложения
Важные требования к файлам модуля
1. apps.py
Файл apps.py должен содержать класс AppConfig с явно указанным label:
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class YourModuleConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'your_module_name' # Имя модуля (будет автоматически обновлено при установке)
label = 'your_module_name' # ⚠️ ОБЯЗАТЕЛЬНО: явно указываем label для правильного app_label
verbose_name = _('Your Module')
def ready(self):
"""Инициализация модуля при загрузке"""
pass
Важно: Поле label должно совпадать с app_name из module.json -> settings.app_name. Это необходимо для правильного определения app_label моделей.
2. models.py
В Meta классах всех моделей обязательно указывайте app_label:
from django.db import models
from django.utils.translation import gettext_lazy as _
class YourModel(models.Model):
name = models.CharField(_('Name'), max_length=255)
class Meta:
app_label = 'your_module_name' # ⚠️ ОБЯЗАТЕЛЬНО: явно указываем app_label
verbose_name = _('Your Model')
verbose_name_plural = _('Your Models')
Важно: app_label должен совпадать с app_name из module.json -> settings.app_name. Без этого Django может неправильно определить имя таблицы в базе данных.
3. migrations/0001_initial.py
В классе миграции обязательно указывайте app_label:
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
# ⚠️ ОБЯЗАТЕЛЬНО: явно указываем app_label для миграции
app_label = 'your_module_name'
dependencies = [
# ...
]
operations = [
# ...
]
Важно: app_label должен совпадать с app_name из module.json -> settings.app_name. Это необходимо для правильного применения миграций.
4. templates/
Шаблоны должны находиться в папке с именем модуля:
templates/
└── your_module_name/ # Папка с именем модуля
├── list.html
├── detail.html
└── form.html
В views.py используйте полный путь к шаблону:
class YourListView(ListView):
template_name = 'your_module_name/list.html' # Полный путь с именем модуля
Важно: Система автоматически добавляет пути к шаблонам модулей в TEMPLATES['DIRS'] при старте приложения, поэтому шаблоны будут найдены автоматически.
Создание шаблонов для модулей
Все шаблоны модулей должны наследоваться от базового шаблона partials/base.html. Это обеспечивает единообразный внешний вид и правильную загрузку всех необходимых библиотек.
Базовый шаблон
Обязательно используйте:
{% extends 'partials/base.html' %}
{% load static %}
{% load i18n %}
Структура блоков базового шаблона
Базовый шаблон partials/base.html предоставляет следующие блоки для переопределения:
**\{% block title %}**- Заголовок страницы (отображается в<title>)**\{% block extra_css %}**- Дополнительные CSS файлы (загружаются перед основными)**\{% block css %}**- Основные CSS (Bootstrap, иконки, app.min.css) - НЕ ПЕРЕОПРЕДЕЛЯЙТЕ, используйтеextra_css**\{% block header %}**- Шапка страницы (опционально)**\{% block sidebar %}**- Боковая панель (опционально)**\{% block contents %}**- Основной контент страницы (обязательно)**\{% block extra_content %}**- Дополнительный контент после основного**\{% block javascript %}**- Основные JavaScript библиотеки (jQuery, Bootstrap, toastr, HTMX) - НЕ ПЕРЕОПРЕДЕЛЯЙТЕ!**\{% block extra_javascript %}**- Дополнительные JavaScript файлы и скрипты (внутри блокаjavascript)
⚠️ Важно: Правильное использование блоков JavaScript
НЕПРАВИЛЬНО - переопределение блока javascript:
{% block javascript %}
<script src="{% static 'libs/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
// код
});
</script>
{% endblock %}
ПРАВИЛЬНО - использование блока extra_javascript:
{% block extra_javascript %}
<script src="{% static 'libs/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
// код
});
</script>
{% endblock %}
Почему это важно:
- Блок
javascriptв базовом шаблоне загружает jQuery, Bootstrap, toastr и другие необходимые библиотеки - При переопределении блока
javascriptэти библиотеки не загружаются, что приводит к ошибкам типа "jQuery is not defined" или "toastr is not defined" - Блок
extra_javascriptнаходится внутри блокаjavascript, поэтому все базовые библиотеки уже загружены
Пример минимального шаблона
{% extends 'partials/base.html' %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "My Module" %}{% endblock title %}
{% block contents %}
<div class="row">
<div class="col-12">
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
<h4 class="mb-sm-0 font-size-18">{% trans "My Module" %}</h4>
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}">{% trans "Home" %}</a></li>
<li class="breadcrumb-item active">{% trans "My Module" %}</li>
</ol>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">{% trans "Content" %}</h4>
<!-- Ваш контент здесь -->
</div>
</div>
</div>
</div>
{% endblock %}
Добавление дополнительных CSS
Используйте блок extra_css для подключения дополнительных стилей:
{% block extra_css %}
<link href="{% static 'libs/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet">
<link href="{% static 'libs/select2/dist/css/select2.min.css' %}" rel="stylesheet" type="text/css" />
{% endblock %}
Важно: Если вам нужно включить базовые стили (Bootstrap и т.д.), используйте:
{% block css %}
{% include 'partials/css.html' %}
<!-- Ваши дополнительные стили -->
{% endblock %}
Добавление JavaScript
Используйте блок extra_javascript для подключения дополнительных скриптов:
{% block extra_javascript %}
<script src="{% static 'libs/select2/dist/js/select2.min.js' %}"></script>
<script src="{% static 'libs/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
<script>
$(document).ready(function() {
// jQuery доступен, так как он загружен в базовом шаблоне
$('#my-select').select2({
theme: 'bootstrap-5',
width: '100%'
});
$('#my-datepicker').datepicker({
format: 'dd.mm.yyyy',
autoclose: true
});
});
</script>
{% endblock %}
Bootstrap разметка
Система использует Bootstrap 5. Используйте стандартные классы Bootstrap:
Контейнеры:
<div class="container-fluid"> <!-- Полная ширина -->
<!-- Контент -->
</div>
Сетка:
<div class="row">
<div class="col-12 col-md-6 col-lg-4">
<!-- Колонка -->
</div>
</div>
Карточки:
<div class="card">
<div class="card-body">
<h4 class="card-title">Заголовок</h4>
<p class="card-text">Текст</p>
</div>
</div>
Кнопки:
<a href="{% url 'module:create' %}" class="btn btn-primary">
<i class="bx bx-plus"></i> {% trans "Add" %}
</a>
<button type="submit" class="btn btn-sm btn-outline-primary">
{% trans "Save" %}
</button>
Таблицы:
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans "Column 1" %}</th>
<th>{% trans "Column 2" %}</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>{{ item.field1 }}</td>
<td>{{ item.field2 }}</td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center">
<div class="alert alert-info">
{% trans "No items found" %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
Формы:
<form method="post" class="mb-3">
{% csrf_token %}
<div class="row g-3">
<div class="col-md-6">
<label for="id_name" class="form-label">{% trans "Name" %}</label>
<input type="text" class="form-control" id="id_name" name="name"
value="{{ form.name.value|default:'' }}" required>
</div>
<div class="col-md-6">
<label for="id_status" class="form-label">{% trans "Status" %}</label>
<select class="form-select" id="id_status" name="status">
<option value="">{% trans "Select status" %}</option>
<option value="active">{% trans "Active" %}</option>
<option value="inactive">{% trans "Inactive" %}</option>
</select>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">
{% trans "Save" %}
</button>
</div>
</div>
</form>
Badges (значки статусов):
{% if item.status == 'active' %}
<span class="badge bg-success">{% trans "Active" %}</span>
{% elif item.status == 'inactive' %}
<span class="badge bg-secondary">{% trans "Inactive" %}</span>
{% else %}
<span class="badge bg-warning">{% trans "Pending" %}</span>
{% endif %}
Пагинация:
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
{% trans "Previous" %}
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">
{% trans "Next" %}
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
Иконки
Система использует Boxicons (класс bx) и Bootstrap Icons (класс bi). Примеры:
<i class="bx bx-plus"></i> <!-- Добавить -->
<i class="bx bx-edit"></i> <!-- Редактировать -->
<i class="bx bx-show"></i> <!-- Просмотр -->
<i class="bx bx-info-circle"></i> <!-- Информация -->
<i class="bx bx-search"></i> <!-- Поиск -->
<i class="bx bx-trash"></i> <!-- Удалить -->
Переводы (i18n)
В шаблонах:
- Используйте
{% load i18n %}в начале шаблона - Используйте
{% trans "Text" %}для всех строк, отображаемых пользователю - Используйте
{% blocktrans %}для сложных строк с переменными
Примеры:
{% load i18n %}
<h4>{% trans "Inventory Issues" %}</h4>
<button>{% trans "Add Issue" %}</button>
{% blocktrans with name=item.name %}
Item "{{ name }}" was created successfully.
{% endblocktrans %}
В Python коде:
- Используйте
from django.utils.translation import gettext_lazy as _ - Используйте
_("Text")для всех строк, отображаемых пользователю
from django.utils.translation import gettext_lazy as _
class MyModel(models.Model):
name = models.CharField(_("Name"), max_length=255)
status = models.CharField(_("Status"), max_length=50)
Статические файлы
В шаблонах:
{% load static %}
<link href="{% static 'libs/select2/dist/css/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'libs/select2/dist/js/select2.min.js' %}"></script>
<img src="{% static 'images/logo.png' %}" alt="Logo">
Структура статических файлов модуля:
static/
└── your_module_name/ # Папка с именем модуля
├── css/
│ └── custom.css
├── js/
│ └── custom.js
└── images/
└── logo.png
Использование:
{% static 'your_module_name/css/custom.css' %}
{% static 'your_module_name/js/custom.js' %}
{% static 'your_module_name/images/logo.png' %}
Использование HTMX
Система включает HTMX для динамических обновлений без JavaScript. Используйте HTMX вместо чистого JavaScript там, где это возможно:
<!-- Загрузка контента по клику -->
<a href="{% url 'module:detail' item.pk %}"
hx-get="{% url 'module:detail' item.pk %}"
hx-target="#content"
hx-swap="innerHTML">
{% trans "View Details" %}
</a>
<!-- Отправка формы без перезагрузки -->
<form hx-post="{% url 'module:create' %}"
hx-target="#result"
hx-swap="innerHTML">
{% csrf_token %}
<!-- поля формы -->
<button type="submit">{% trans "Submit" %}</button>
</form>
Полный пример шаблона списка
{% extends 'partials/base.html' %}
{% load static %}
{% load i18n %}
{% block extra_css %}
<link href="{% static 'libs/select2/dist/css/select2.min.css' %}" rel="stylesheet" type="text/css" />
{% endblock %}
{% block title %}{% trans "Inventory Issues" %}{% endblock title %}
{% block contents %}
<div class="row">
<div class="col-12">
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
<h4 class="mb-sm-0 font-size-18">{% trans "Inventory Issues" %}</h4>
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}">{% trans "Home" %}</a></li>
<li class="breadcrumb-item active">{% trans "Inventory Issues" %}</li>
</ol>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="card-title mb-0">{% trans "Inventory Issues List" %}</h4>
<a href="{% url 'inventory:issue_create' %}" class="btn btn-primary">
<i class="bx bx-plus"></i> {% trans "Add Issue" %}
</a>
</div>
<!-- Фильтры -->
<form method="get" class="mb-3">
<div class="row g-3">
<div class="col-md-3">
<label for="search" class="form-label">{% trans "Search" %}</label>
<input type="text" class="form-control" id="search" name="search"
value="{{ search_query }}" placeholder="{% trans 'Search...' %}">
</div>
<div class="col-md-2">
<label for="status" class="form-label">{% trans "Status" %}</label>
<select class="form-select" id="status" name="status">
<option value="">{% trans "All" %}</option>
<option value="active" {% if status_filter == 'active' %}selected{% endif %}>
{% trans "Active" %}
</option>
</select>
</div>
<div class="col-md-1">
<label class="form-label"> </label>
<button type="submit" class="btn btn-primary w-100">
<i class="bx bx-search"></i>
</button>
</div>
</div>
</form>
<!-- Таблица -->
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>{{ item.name }}</td>
<td>
<span class="badge bg-success">{% trans "Active" %}</span>
</td>
<td>
<a href="{% url 'inventory:issue_detail' item.pk %}"
class="btn btn-sm btn-outline-info">
<i class="bx bx-info-circle"></i>
</a>
<a href="{% url 'inventory:issue_edit' item.pk %}"
class="btn btn-sm btn-outline-primary">
<i class="bx bx-edit"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center">
<div class="alert alert-info">
{% trans "No items found" %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Пагинация -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
{% trans "Previous" %}
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">
{% trans "Next" %}
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_javascript %}
<script src="{% static 'libs/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
$('#status').select2({
theme: 'bootstrap-5',
width: '100%'
});
});
</script>
{% endblock %}
Рекомендации
- Всегда используйте
**\{% extends 'partials/base.html' %}**- это обеспечивает единообразный внешний вид - Используйте
**extra_javascript**вместо**javascript**- это гарантирует загрузку всех базовых библиотек - Используйте переводы - все строки должны быть переводимыми через
{% trans %} - Используйте Bootstrap классы - не создавайте кастомные стили без необходимости
- Используйте HTMX - предпочитайте HTMX чистому JavaScript для динамических обновлений
- Следуйте структуре - используйте стандартную структуру с
page-title-box,card,card-body - Используйте иконки - используйте Boxicons (
bx) или Bootstrap Icons (bi) для визуального оформления