Ответы пользователя по тегу Django
  • Как развернуть сайт на django?

    werevolff
    @werevolff
    django запускается как отдельный процесс или демон. Обычно, для запуска используется gunicorn или wsgi. Для запуска gunicorn лучше всего создать systemd конфиг для Debian/Ubuntu систем. Gunicorn работает на IP адресе и открывает порт для IO операций. Например, 127.0.0.1:8000. Далее, в Nginx прописывается:

    server {
        server_name my.site.com;
        listen 80;
        server_tokens off;
        location / {
            include proxy_params;
            proxy_pass http://127.0.0.1:8000/;
        }
    }


    То есть, nginx является лишь прокси-сервером для переадресации запросов на ваш django-wsgi процесс, который непрерывно запущен в системе.

    Второй вариант - поднять систему в докере. Докер предоставляет то же самое: он занимает какой-то порт на localhost, а nginx переадресует запросы извне (от компьютеров клиентов) на docker контейнер.

    Есть и другие варианты запуска, но для начала это то, что вы должны знать.

    Итак, для запуска вам понадобится:
    1. Доступ к systemd конфигам хостинга, либо, возможность запустить docker контейнер с Django
    2. Доступ к конфигу Nginx/Apache2

    Если этих доступов нет, то говорить особо не о чем.

    https://gunicorn.org/
    https://www.docker.com/
    https://docs.docker.com/compose/
    https://habr.com/ru/company/cloverr/blog/247629/
    https://habr.com/ru/post/272811/ - моя очень старая статья. Может не запуститься и имеет не совсем продовский конфиг.
    Ответ написан
  • Как описать хранение рабочих часов в бд?

    werevolff
    @werevolff
    1. Рабочие часы - это тип данных str а не datetime! Почему? Потому, что Datetime в любом случае возвращает конкретную дату. Как надо себе это представлять:

    start_time: str = '12:20'
    end_time: str = '16:00'


    Есть ещё TimeField, но тогда надо будет в поле объявлять format=

    2. Есть несколько вариантов записи этих данных в модель. Самый простой - через Choices:

    class WorkDay(models.Model):
        DAYS_OF_WEEK = (
            (1, _('Monday')),
            (2, _('Tuesday')),
            (3, _('Wednesday')),
            (4, _('Thursday')),
            (5, _('Friday')),
            (6, _('Saturday')),
            (7, _('Sunday')),
        )
        DAY_TYPES = (
            ('weekday', _('weekday')),
            ('holiday', _('holiday')),
        )
        day_of_the_week = models.PositiveIntegerField(
            verbose_name=_('day of the week'),
            choices=DAYS_OF_WEEK
        )
        start_time = models.CharField(
            verbose_name=_('start time'),
            max_length=5
        )
        end_time = models.CharField(
            verbose_name=_('end time'),
            max_length=5
        )
        day_type = models.CharField(
            verbose_name=_('day type'),
            max_length=255,
            choices=DAY_TYPES
        )


    3. Нам может потребоваться записать в рабочий день несколько пар рабочих часов. В этом случае, наиболее верным способом я счёл бы использование ArrayField или ListField

    from django.contrib.postgres.fields import ArrayField
    
    class WorkDay(models.Model):
        DAYS_OF_WEEK = (
            (1, _('Monday')),
            (2, _('Tuesday')),
            (3, _('Wednesday')),
            (4, _('Thursday')),
            (5, _('Friday')),
            (6, _('Saturday')),
            (7, _('Sunday')),
        )
        DAY_TYPES = (
            ('weekday', _('weekday')),
            ('holiday', _('holiday')),
        )
        day_of_the_week = models.PositiveIntegerField(
            verbose_name=_('day of the week'),
            choices=DAYS_OF_WEEK
        )
        working_hours = ArrayField(
            ArrayField(
                models.CharField(
                    max_length=5
                ),
                size=2
            ),
            verbose_name=_('working hours')
        )
        day_type = models.CharField(
            verbose_name=_('day type'),
            max_length=255,
            choices=DAY_TYPES
        )


    4. Также, мы можем использовать JSONField (MySQL или PostgreSQL )

    from django.contrib.postgres.fields import JSONField
    
    class WorkGraph(models.Model):
        working_graph = JSONField(
            verbose_name=_('working graph'),
            default={
                'monday': {
                    'hours': ['12:20', '16:00'],
                    'day_type': 'weekday'
                }
            }
        )


    Я бы предпочёл вариант с ArrayField.

    ArrayField как и JSONField поддерживаются DRF и представляют данные в виде массивов Python, которые можно отработать через validate.

    При таком подходе фронт берёт на себя построение UI, при котором пользователь просто выбирает часы и минуты в форме и добавляет их. JS строит на основе формы массив и передаёт в DRF.

    И бонусом, используем валидацию для полей Django и DRF:

    import re
    from django.core.exceptions import ValidationError
    from typing import NoReturn
    
    def validate_working_hours(
            value: str
    ) -> NoReturn:
        m = re.match(
            r'^(?P<hours>\d{2}):(?P<minutes>\d{2})$',
            value
        )
        if m is None:
            raise ValidationError('please, use format HH:mm')
        else:
            hours = int(
                m.group('hours')
            )
            if hours > 23:
                raise ValidationError('hours is a value from 0 to 23')
            minutes = int(
                m.group('minutes')
            )
            if minutes > 59:
                raise ValidationError('minutes is a value from 0 to 59')
    Ответ написан
  • Насколько frontend часть django медленнее чем работа с чистым frontend, общаясь с джангой с помощью API?

    werevolff
    @werevolff
    Замерьте скорость рендеринга с помощью Инструментов Разработчика Google Chrome. Это очень относительное утверждение.

    Скорость рендеренинга страницы - не совсем верный термин. Вы замеряете время:

    1. За которое получаете ответ от сервера на ваш запрос
    2. За которое браузер скачивает все файлы для построения страницы
    3. За которое браузер показывает первые полезные данные (не loader)
    4. За которое браузер полностью рендерит страницу

    В случае с Django:

    1. Запрос проходит через Nginx - на wsgi
    2. Wsgi ищет правильную привязку URL
    3. Wsgi запускает все Middleware
    4. Wsgi запускает view
    5. После того, как отработал view, HTML код возвращается через все Middleware
    6. Браузер получает код целой страницы со всеми ссылками на файлы и изображения. Допустим, 20 килобайт страница и ещё около сотни ссылок на ней.

    В случае с Vue.js:

    1. Запрос приходит в nginx
    2. Nginx ищет нужный html файл и отдаёт его.
    3. В браузер приходит небольшой файл с одним js. Там ничего нет по сути, кроме скрипта vue.js и пустого html файла.

    Что это всё значит?

    1. Django всегда запускает свои Middleware чтобы получить HTML код. Плюс, сама вьюха
    2. Django возвращает уже готовый HTML страницы. Это много кода и много ссылок в коде
    3. Современные браузеры загружают файлы по ссылкам в коде параллельно. То есть, загрузка и рендеринг 100 файлов параллельно будут происходить быстрее, чем загрузка и рендеринг одного файла.

    Здесь, правда, стоит поспорить: Загрузка 100 файлов для рендеринга страницы происходит быстрее, чем, скажем, 5 файлов потому, что эти файлы сразу начинают отображаться после загрузки. Часть страницы можно вообще не рендерить и подгрузить позже. В случае с классическим рендерингом страницы средствами браузера, эти 5 файлов будут качаться сразу, и мы будем ждать каждый из этих файлов.

    Разумеется, если брать копирование данных или тот же FTP, загрузка 100 мелких файлов никогда не будет быстрее загрузки одного файла, поскольку каждая загрузка будет создавать запрос к серверу и ждать ответ.

    Итак, в браузер пользователя пришёл HTML код с одной ссылкой на Vue.js. Пришёл быстрее, чем код с Джанги. Теперь Vue.js начинает грузить дополнительные данные с сервера (многопоточно) и рендерить их. Рендеринг происходит на стороне клиента, и именно от скорости клиентской машины зависит окончательное время рендеринга. В любом случае, Vue.js уже получил преимущество: он быстрее скачал данные для отображения первичного контента, он может качать страницу частями параллельно.

    Можно посоветовать:

    1. Сделать оптимизацию своего кода Django. Используй кеширование страниц, где это возможно. Используй быстрый Redis Cache.

    from django.views.decorators.cache import cache_page
    
    pages_urls = [
        re_path(
            r'^account/',
            cache_page(
                60 * 60 * 24 * 30
            )(
                TemplateView.as_view(
                    template_name='base.html'
                )
            ),
            name='account'
        ),
    ]


    2. С этим подходом можно включить gzip сжатие страниц перед кешированием, чтобы снизить их вес.
    3. Уменьши число Middleware

    Правда, твой товарищ может сделать то же самое для Vue.js, и у него будет более значительный прирост в скорости рендеринга страницы.

    Выводы:

    Vue.js не то чтобы быстрее Django. Просто сама архитектура позволяет ему за одинаковое с Django время грузить страницы бОльшего объёма. Vue.js готова показать первый контент на странице раньше чем, Django сделает это. Vue.js грузит части страниц и контент параллельно в несколько потоков. Vue.js при получении статических HTML, JS, CSS файлов не ждёт, пока сервер выполнит python или php код, работая напрямую с nginx. Так что, при равных условиях это будет быстрее, чем получать всю страницу от Django.

    Также, следует помнить, что браузер будет ждать от Django полной загрузки основного HTML контента перед тем, как его отрендерить. За это время Vue.js уже покажет первый контент. После чего, Vue.js спокойно догрузит оставшиеся файлы. Если сжать все js в один файл и все css в один файл, то браузер будет ждать их загрузки. Vue.js может сначала отрендерить страницу, а потом уже догрузить недостающий код.
    Ответ написан
  • Django ORM: Как отсортировать по prefetched_set?

    werevolff
    @werevolff
    А то, что ты в prefetch получаешь, нельзя как-нибудь прогнать через annotation? А потом уже с аннотации сортировать?

    А вообще, может использовать аргумент to_attr, чтобы переименовать атрибут?

    class Prefetch(lookup, queryset=None, to_attr=None)
    The to_attr argument sets the result of the prefetch operation to a custom attribute.

    Я так полагаю, numbers_set может ссылаться на существующее поле объекта.

    Решение найдено через FilteredRelation(). Mekalure объяснение: Собственно, Prefetch() метод не создаёт атрибут, который доступен в order_by. Я прогнал у себя: этот атрибут не доступен и в annotate. Только в результате выборки. Поэтому, отсортировать по нему не выйдет. FilteredRelation() объявляется внутри annotate и создаёт QuerySet, доступный в order_by и последующей фильтрации. Но, увы, этот QuerySet не будет доступен в результатах выборки. Подозреваю, что Джанга умеет связывать два QuerySet только через prefetch_related, но не через annotate. В любом случае, эта функция делает нужные данные доступными при сортировке.

    Получается:

    qs1 = Model1.objects.filter(**kwargs)
    qs2 = Model2.objects.annotate(order=FilteredRelation('lookup', condition=Q(**kwarg))).prefetch_related(Prefetch('lookup', queryset=qs1)).order_by('order')
    Ответ написан
  • Django. Как отловить изменение в поле модели?

    werevolff
    @werevolff
    Здесь есть два популярных решения проблемы. Но, сперва, разберёмся в архитектуре.

    1. У нас работает некий таймер, который с некоторой периодичностью запускает python функцию.
    2. Эта функция фильтрует Card по статусу активности. Например:
    qs = Card.objects.filter(
        expired_data__gt = datetime.now()
    )


    3. Для инстансов в Queryset применяются выбранные действия.

    for instance in qs:
        do_something(instance)


    Итак, наша задача - найти этот таймер. Наиболее популярное решение - celery beat с periodic tasks

    Как это выглядит:

    1. Ставим на проект Celery, убедимся, что task.delay() работает для тестового таска.
    2. Создаём my_package/tasks.py.
    3. Создадим внутри таск, который фильтрует наши неактивные карточки и выполняет на них операцию с выставлением счёта, или что там у тебя.
    4. Заносим этот таск в settings.CELERY_BEAT_SCHEDULE

    CELERY_BEAT_SCHEDULE = {
        'my_task_name': {
            'task': 'my_package.tasks.my_task_name',
            'schedule': 24 * 60 * 60,  # 24 hours
            'args': ()
        }
    }


    5. Убедимся, что celery запущена в режиме beat.

    Что если мы не хотим ставить Celery? Есть решение в виде django-crontab.

    1. Ставим django-crontab.
    2. Создаём функцию, которая фильтрует и обрабатывает не активные карточки.
    3. Заносим нашу функцию в settings.CRONJOBS

    CRONJOBS = [
        ('*/5 * * * *', 'myapp.cron.my_scheduled_job')
    ]


    4. Выполняем python manage.py crontab add

    Дополнительно нам может потребоваться сохранять данные о том, какие карты игнорировать. Это можно решить элементарным добавлением поля ignore в модель и изменением правила фильтрации карточки.

    Для тестирования: берём таск или функцию для крона и выполняем её в тестах. Убедимся, что операция выполнена только для не активных карточек. Не забываем использовать CELERY_ALWAYS_EAGER=True в override_settings для теста.
    Ответ написан
  • CustomUser model auth не работает?

    werevolff
    @werevolff
    def login(request):
    
        defaults = {
            'authentication_form': CustomAuthForm,
            'template_name': 'accounts/login.html',
            }
        if request.user.is_anonymous():
            return auth_login(request, **defaults)
        elif request.user.is_owner():
            return redirect('/asdasdasd')
    
        return auth_login(request, **defaults)


    Читаем код:

    1. Функция логина
    2. Если пользователь анонимный - залогинить.
    3. Если пользователь не анонимен и метод is_owner() возвращает True - редиректнуть.
    4. Во всех остальных случаях - залогинить.

    Соответственно, проверка is_owner() происходит только в том случае, если пользователь авторизован и пытается зайти на страницу login. Теперь об ошибках:

    1. Непонятно зачем дёргается стандартная ф-ия аутентификации.
    2. Не думаю, что смысл в проверке is_owner имеет смысл вообще.
    3. Её можно упростить до вида:

    def login(request):
        defaults = {
            'authentication_form': CustomAuthForm,
            'template_name': 'accounts/login.html',
            }
        if not request.user.is_authenticated() and request.user.is_owner():
            return redirect('/asdasdasd')
        return auth_login(request, **defaults)


    Теперь по поводу вопроса:

    Пользователь зашёл и ввёл логин/пароль. Его перекинуло на стандартную View авторизации джанги (иными словами, эта вьюха вообще не нужна: всё что она делает - редиректит ), вот он и вошёл безо всяких проверок на owner.

    Для решения задачи необходимо написать собственное представление для входа в систему.

    P.S. Какие странные пользователи появились у моей любимой Django в последнее время. Аж дух захватывает.
    Ответ написан
  • Как реализовать древовидные комментарии на django с использование tinymce/ckeditor?

    werevolff
    @werevolff
    Django не является CMS. Это не джумла, и не вордпресс. Здесь нет отговорок типа "мне лень это писать". Ставим treebeard или mptt, реализуем модель комментариев. При необходимости, делаем REST интерфейс для вывода дерева, в JS добавляем возможность при клике на "ответить" выставлять id комментария в переменную. Открываем tinymce/ckeditor, при сабмите отправляем id родителя и текст сообщения.

    django-threadedcomments реализует только модель и кучу templatetags. Абсолютно не django way. Как вы собрались решать проблему, если вам банально лень открыть анализатор запросов и посмотреть что отправляется на сервер? Почему не уходит parent_id?
    Ответ написан