Поиск

Релиз

Содержание

Нужна помощь?

Не нашли ответ на свой вопрос? Свяжитесь с нашей службой поддержки.

Обратиться в поддержку

Структура модуля

Обновлено: 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 предоставляет следующие блоки для переопределения:

  1. **\{% block title %}** - Заголовок страницы (отображается в <title>)
  2. **\{% block extra_css %}** - Дополнительные CSS файлы (загружаются перед основными)
  3. **\{% block css %}** - Основные CSS (Bootstrap, иконки, app.min.css) - НЕ ПЕРЕОПРЕДЕЛЯЙТЕ, используйте extra_css
  4. **\{% block header %}** - Шапка страницы (опционально)
  5. **\{% block sidebar %}** - Боковая панель (опционально)
  6. **\{% block contents %}** - Основной контент страницы (обязательно)
  7. **\{% block extra_content %}** - Дополнительный контент после основного
  8. **\{% block javascript %}** - Основные JavaScript библиотеки (jQuery, Bootstrap, toastr, HTMX) - НЕ ПЕРЕОПРЕДЕЛЯЙТЕ!
  9. **\{% 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">&nbsp;</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 %}

Рекомендации

  1. Всегда используйте **\{% extends 'partials/base.html' %}** - это обеспечивает единообразный внешний вид
  2. Используйте **extra_javascript** вместо **javascript** - это гарантирует загрузку всех базовых библиотек
  3. Используйте переводы - все строки должны быть переводимыми через {% trans %}
  4. Используйте Bootstrap классы - не создавайте кастомные стили без необходимости
  5. Используйте HTMX - предпочитайте HTMX чистому JavaScript для динамических обновлений
  6. Следуйте структуре - используйте стандартную структуру с page-title-box, card, card-body
  7. Используйте иконки - используйте Boxicons (bx) или Bootstrap Icons (bi) для визуального оформления