• Как установить let's encrypt сертификат в docker совместно с nginx и certbot?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Проблема в неправильном выборе инструмента. Для решения этой задачи гораздо лучше подходит Traefik: он умеет динамически подхватывать контейнеры, которым необходим доступ извне, автоматически получать let's encrypt сертификаты, роутить запросы на основании имени домена / пути, и выступать в качестве load balancer-а. Настраивается гораздо проще, чем Nginx: вся конфигурация - несколько строк.
    Ответ написан
    7 комментариев
  • Как удалить/обновить программу, которая была создана из исходников?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Такой софт можно собирать и запускать с помощью Docker, а при удалении просто грохать соответствующий контейнер или образ. Помимо упрощения очистки хост-системы это даёт ещё одно преимущество - возможность запускать несколько версий одной и той же программы одновременно
    Ответ написан
    Комментировать
  • С помощью каких приложений объяснить ребенку (9 лет) python?

    neatsoft
    @neatsoft
    Life is too short for bad software
    1. Установить на планшет ScratchJr, объяснить базовые принципы
    2. Показать Scratch, предложить выполнить в нем несколько заданий умеренной сложности
    3. Вместе написать простую игру на Pygame, например, Whack a Mole
    4. Вместе освоить управление Minecraft с помощью Python на Raspberry Pi
    5. Показать JavaScript-HTML-CSS и Developer Tools в браузере
    6. Рассказать про нативные приложения для мобильных платформ
    7. Рассказать про фреймворки (например, Django и Vue), базы данных, сетевое взаимодействие
    8. Построить и запрограммировать модели из LEGO Mindstorms EV3 (он "огороженный", тормознутый, устаревший, но детям по прежнему нравится)
    9. Рассказать про микроконтроллеры, вместе собрать что-нибудь с помощью Амперка Матрешка или Амперка Йодо
    При потере интереса на любом из этапов оставить ребенка в покое
    Ответ написан
    Комментировать
  • Как динамически фильтровать field в форме?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Эту функциональность можно реализовать с помощью перезагрузки формы с передачей выбранных значений через GET параметры, но гораздо проще и удобнее использовать Select2 / autocomplete_fields. Кроме фильтрации это решает ещё две проблемы: загрузка длинных списков и поиск элементов в таких списках.

    Без js фильтрацию значений зависимых полей можно реализовать только с помощью многошаговой формы: форма с единственным полем Partner -> Submit -> форма со всеми остальными полями.
    Ответ написан
    Комментировать
  • Как сконфигурировать виртуальные хосты для NGINX для ajax?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Существует три варианта решения этой проблемы:
    1. После сборки фронтенда можно помещать его в static files / assets бэкенда, и сервить так же, как и все остальные файлы. Этот метод плох тем, что утрачивается возможность применения cache forever / cache busting (добавления хэш суммы в имена файлов).
    2. Распределять запросы между сервисами используя договоренность о путях, например, /api/* и /admin/* отправлять в бэк, /static/* отдавать из каталога с VueJS статикой, а для всех остальных запросов возвращать dist/index.html (для избавления от # в путях и обеспечения поддержки history mode). Минус - об этой договоренности нужно помнить, и следить за её соблюдением. Появляется неявная зависимость между сервисами.
    3. Использовать разные поддомены, например, api.example.com для бэка, www.example.com - для фронта. Роутинг запросов в этом случае происходит наиболее естественным образом - на основании имени домена. Становятся доступны дополнительные способы масштабирования (размещение на разных серверах, dns round robin, использование разных reverse proxy), и появляется возможность тестирования / отладки фронта с апи из разных окружений (dev, staging, qa, prod).


    ps. Cоветую упаковать ваши сервисы в Docker контейнеры, а в качестве reverse proxy / tls proxy / load balancer использовать Traefik вместо Nginx. Его киллер-фичи - auto discovery и auto load balancing. Кроме того, его гораздо проще правильно настроить. Сам Traefik тоже в контейнере работает, поэтому на сервере не требуется ничего кроме docker и docker-compose

    traefik-architecture.svg
    Ответ написан
    Комментировать
  • Программа в фоне на Python?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Для таких задач есть специальный инструмент - Celery.

    Под windows запустить можно, но не нужно. Если нет возможности использовать Linux в качестве основной рабочей системы, то стоит хотя бы виртуальную машину создать, т.к. паритет разработки/работы приложения - это важно.

    upd. Почему Celery лучше чем планировщик? Фоновое выполнение - это не такая тривиальная задача, как может показаться на первый взгляд, т.к. существуют граничные условия:
    - Что необходимо делать, если процесс по каким-то причинам не был запущен по расписанию, или завершился аварийно? Нужно ли запускать его повторно? (at least once)
    - Важна ли очередность выполнения?
    - Можно ли параллельно запускать ещё одну задачу, если предыдущая не завершилась?
    - Как мониторить выполнение фоновых задач?
    - Как собирать и хранить логи?
    Celery - хорошо протестированный, используемый во множестве проектов, production ready инструмент. Единожды изучив, его можно использовать для задач любой сложности.
    Ответ написан
    Комментировать
  • Как сравнить два файла в python?

    neatsoft
    @neatsoft
    Life is too short for bad software
    INPUT_FILENAME = 'file1'
    BLACKLIST_FILENAME = 'file2'
    OUTPUT_FILENAME = 'file3'
    
    with open(BLACKLIST_FILENAME) as f:
        blacklist = f.readlines()
    
    prev_line = None
    with open(OUTPUT_FILENAME, 'w') as output_file:
        with open(INPUT_FILENAME) as input_file:
            for line in input_file:
                if line in blacklist:
                    prev_line = None
                else:
                    if prev_line is not None:
                        output_file.write(prev_line)
                    prev_line = line
            if prev_line is not None:
                output_file.write(prev_line)
    Ответ написан
    3 комментария
  • Django request.build_absolute_uri?

    neatsoft
    @neatsoft
    Life is too short for bad software
    1. В settings.py нужно добавить USE_X_FORWARDED_HOST == True
    2. В конфигурации Nginx установить хэдер X-Forwarded-Host
    Документация: Django documentation > Settings > USE_X_FORWARDED_HOST
    Функция, которая возвращает имя хоста: request.py:_get_raw_host()
    Ответ написан
    2 комментария
  • Где брать дисковое пространство для сайта?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Стандарт де-факто для хранения медиа файлов (user uploaded files) - S3. Он отлично поддерживается большинством фреймворков, и это не обязательно Amazon: поменяв в настройках проекта адрес хранилища, можно легко переключиться на использование любого другого облачного провайдера. Собственное S3 хранилище можно создать установив на сервер MinIO.

    Преимущества S3:
    - отдельный стабильный сервис, не зависящий от ошибок в коде проекта
    - простота масштабирования и оптимизации затрат
    - основной сайт и хранилище файлов могут быть расположены на разных серверах: быстрый с небольшим ssd для бэкенда, слабый но с объемными дисками - для файлов
    - нативная защита данных - криптографически подписанные ссылки с ограничением по времени
    - упрощение проекта: никаких больше причудливых конфигураций nginx для роутинга запросов
    - соответствие The Twelve-Factor App

    Если 1 TB достаточно, рекомендую online.net. За €8.99 евро в месяц они предлагают выделенный сервер с SATA диском и неограниченным трафиком - отличный вариант для MinIO при умеренных нагрузках.

    MinIO удобно запускать с помощью Docker, указав что для хранения данных должен использоваться каталог на хост системе. На роль reverse proxy / tls proxy в этом случае лучше всего подходит Traefik
    Ответ написан
    1 комментарий
  • Как запустить сборку python + django?

    neatsoft
    @neatsoft
    Life is too short for bad software
    По этой ссылке находится заброшенный проект, при этом очень низкого качества - нарушено всё что только можно, включая PEP8, django best practices, etc.

    Это приговор:
    Django==1.3
    South==0.7.3

    Поддержка Django 1.3 закончилась 26 февраля 2013 года - шесть лет назад. С тех пор очень многое изменилось не только в Django, но и в Python.
    Практической ценности этот код не представляет, в качестве основы для нового проекта тоже не годится - очень грязный.

    5cffd0e81c9fe887261265.png
    Ответ написан
    Комментировать
  • Потянет ли intel core i5-4670 CPU монитор 3840Х2160?

    neatsoft
    @neatsoft
    Life is too short for bad software
    i7-7700 + Z270, интеграшка спокойно тянет 11520 x 2160 - это три 4K монитора. Единственная проблема - все разъемы на материнской плате разные: DisplayPort, HDMI 2.0, и USB-C, следовательно на мониторах должны быть соответствующие входы.

    Что касается i5-4670, лучше искать ответы на такие вопросы не по названию процессора, а по названию gpu - Intel HD Graphics 630. Через DP - будет работать, через HDMI - нет (30 Гц - это не работа), поэтому зависит от материнки.

    к чему еще требователен

    К поддержке HiDPI со стороны операционной системы и программного обеспечения. Если разработчики, создавая свой софт, не задумывались о наличии мониторов с высоким разрешением, то будет либо мыльно, либо мелко. Большинство программ работают хорошо, но "динозавры" до сих пор иногда встречаются
    Ответ написан
    Комментировать
  • Нехватка места на мониторах. Как решаете?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Пару лет назад купил три 27" 4K монитора, суммарное разрешение - 11520 x 2160, общая диагональ - 72", соотношение сторон - 48/9. В сочетании с тайловым оконным менеджером (i3) места почти всегда хватает: слева обычно открыта документация или исходники одного из предыдущих проектов, по центру редактор (чуть больше половины экрана вертикально), терминал, и файловый менеджер, справа целевой сайт в режиме отладки

    Несколько наблюдений, которые могут оказаться полезны для тех, кто думает о подобном решении:
    1. В такой конфигурации мониторы приходится располагать полукругом - так, чтобы расстояние от головы до центра каждого из экранов было одинаковым, иначе пользоваться ими неудобно. Это совсем не похоже на картинки красивых рабочих мест из интернета, зато можно использовать стол меньшей ширины. Например, три 27" монитора по 62 см каждый, отлично встают на 150 см стол
    5cfdc7ef2aab8908505831.jpeg
    2. Следствие предыдущего пункта - мониторы лишаются регулировки наклона, поэтому важно, чтобы присутствовала регулировка по высоте
    3. Работать стало значительно удобнее, но на производительность труда это почти не повлияло: как выяснилось, переключение виртуальных экранов - это далеко не главная причина срыва контекста
    4. Как ни странно, с тремя 4K мониторами отлично справляется интеграшка от Intel, правда приходится использовать три разных подключения - DisplayPort, HDMI 2.0, и USB-C
    5. Многомониторная конфигурация не позволяет использовать качественные колонки - их попросту некуда ставить. Планирую сделать стол с горизонтально расположенными вуферами, твиттеры поместить в отдельные корпуса, которые можно направить на слушателя расположив под мониторами, и использовать активный фильтр (MiniDSP) для корректировки фазы и АЧХ отдельно для каждого из динамиков

    В общем, один 4K монитор со всех точек зрения проще, но три - веселее )

    Двухмониторная конфигурация - не вариант, т.к. либо рамка будет располагаться посередине, либо с одной стороны света будет существенно больше, чем с другой (если один из мониторов расположить по центру, а второй сбоку, в качестве вспомогательного). Пробовал на время убирать один из мониторов - не понравилось

    При этом я с радостью поменял бы свой сет на один монитор с соотношением 32/9, диагональю 58", разрешением 7680 x 2160 (аналог двух 32" 4K), и подключением через один USB-C (TB3 позволяет передавать два 4K потока с частотой 60 Гц), но, к сожалению, такие пока не выпускают. Учитывая недавнее появление нескольких моделей 49" 5120 x 1440, надеюсь что и до 58" Dual 4k рано или поздно дело дойдёт

    upd. 40 см по вертикали (32" 16/9) - это предел для комфортной работы, иначе устает шея. За 43" и тем более 50" телевизором работать будет крайне не удобно. К тому же надо понимать, что во многих случаях разрешение гораздо важнее размера: если что-то не получается разглядеть, то можно придвинуться поближе, но если не хватает разрешения, то приходится пользоваться виртуальными экранами
    Ответ написан
    Комментировать
  • Не удается отправить файл на ftp сервер, в чем может быть причина?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Видно что ошибка возникает при вызове FTP.connect. Учитывая что библиотека FTP используется во многих проектах, и c подобными проблемами больше никто не сталкивается, рискну предположить, что неправильно заданы параметры - адрес и порт. Для их проверки удобно использовать интерактивный режим Python.

    Кроме того, исходя из личного опыта, советую явно указывать таймаут:
    FTP_TIMEOUT = 10 # seconds
    ftp.connect(host, port, FTP_TIMEOUT)


    задать рабочий каталог:
    ftp.cwd(path)

    и обернуть вызов библиотечных функций в try-except блоки:
    try:
        ftp.connect(...)
        ftp.login(...)
        ftp.cwd(...)
    except Exception as e:
        ...
    
    ...
    
    try:
        ftp.storbinary(f'STOR {filename}', f)
    except Exception as e:
        ...
    Ответ написан
  • Где правильно хранить загружаемые изображения Vuejs cli + NodeJs (MariaDB)?

    neatsoft
    @neatsoft
    Life is too short for bad software
    С точки зрения The Twelve Factor App, для хранения загружаемых файлов (user-uploaded media files) должна использоваться сторонняя служба (backing service). В большинстве крупных проектов это Amazon S3, но если необходимо размещать все данные локально, можно установить MinIO.

    В базу данных записываются относительные пути. Url хранилища и ключи задаются через переменные окружения.

    Важно! Статические файлы (static assets) в S3 помещать не нужно: это неправильно с архитектурной точки зрения, т.к. они являются частью приложения.
    Ответ написан
    Комментировать
  • Как запустить Nuxt+Express(встроенный) на VPS под Ubuntu - нужен ли Nginx или еще чего?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Express поместить в Docker контейнер, в качестве reverse proxy / tls termination proxy / load balancer использовать Traefik. На VPS ничего кроме докера и docker-compose в этом случае устанавливать не придется.
    Ответ написан
    Комментировать
  • Как разместить vue-ssr или nuxt приложение и laravel api на одном vps сервере/домене?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Решение в лоб - Nginx в качестве reverse proxy, но есть более современный и удобный вариант:
    - Laravel упаковать в один Docker контейнер, Vue во второй
    - запускать контейнеры с помощью Docker Compose
    - запросы роутить с помощью Traefik

    traefik-architecture.svg
    Ответ написан
    Комментировать
  • Как поднять несколько сайтов на одном сервере?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Категорически не согласен с ранее данными ответами. Докер отлично подходит для этой задачи, причём решает целый ряд проблем. Имеющиеся сайты могут оказаться привязаны к разным версиям одних и тех же программ, докеризация позволит изолировать их окружения. Упростится тестирование, деплой, и настройка dev окружений.

    В качестве tls proxy / load balancer рекомендую использовать Traefik - он автоматически подхватывает Docker контейнеры в соответствии с указанными в docker-compose.yml правилами, получает tls сертификаты для всех адресов с помощью letsencrypt, эффективно роутит запросы между контейнерами, позволяет балансировать нагрузку между несколькими контейнерами (docker-compose --scale).

    Время, потраченное на изучение Docker и Traefik, с лихвой окупится колоссальной экономией времени в будущих проектах.
    Ответ написан
    7 комментариев
  • Какие есть приемы удобной вставки данных в MySQL с помощью Python?

    neatsoft
    @neatsoft
    Life is too short for bad software
    1. Django ORM
    2. SQLAlchemy
    ORM не избавляет от необходимости изучения принципов работы реляционных баз данных, и понимания того, в какие SQL запросы разворачиваются те или иные конструкции, но позволяет избежать множества типичных ошибок начинающих разработчиков (типа валидации данных), и резко сокращает количество рутины.
    Советую присмотреться к Django, т.к. вместе с неплохой ORM, в ней "из коробки" есть очень удобные миграции данных.
    Ответ написан
    Комментировать
  • Как преобразовать datetime в django?

    neatsoft
    @neatsoft
    Life is too short for bad software
    pip install python-dateutil

    >>> from datetime import datetime, timedelta
    >>> from dateutil import relativedelta
    >>> d1 = datetime.now()
    >>> d2 = d1 + timedelta(days=285, hours=14, minutes=32, seconds=19)
    >>> r = relativedelta.relativedelta(d2, d1)
    >>> r.years, r.months, r.days, r.hours, r.minutes, r.seconds
    (0, 9, 10, 14, 32, 19)


    >>> from datetime import datetime, timedelta
    >>> from dateutil import relativedelta
    >>> r = relativedelta.relativedelta(datetime(2019, 12, 25), datetime.now())
    >>> f'{r.months} мес {r.days} дн {r.hours:02}:{r.minutes:02}:{r.seconds:02}'
    '9 мес 10 дн 04:32:01'
    Ответ написан
    2 комментария
  • Как реализовать на сайте таблицу мер продуктов?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Для каждого продукта питания необходимо хранить калорийность в пересчете на грамм, а для единиц измерения указывать вес в граммах:

    5c8901b18f654618880623.png

    Единицы измерения не имеют смысла безотносительно продуктов питания, т.к. между десятком яиц, столовой ложкой муки, и одним бананом среднего размера (200 гр с кожурой, 150 гр мякоти) нет ничего общего.

    Пример реализации на Django:

    models.py
    import decimal
    
    from django.core.validators import MaxValueValidator, MinValueValidator
    from django.db import models
    
    
    class Foodstuff(models.Model):
        name = models.CharField(max_length=200)
        kcal_per_gram = models.DecimalField(
            # the most calorie-dense food is fat cattle - 925 kcal per 100 gram (9.25 kcal/gram)
            max_digits=3,
            decimal_places=2,
            validators=[
                MaxValueValidator(decimal.Decimal('9.99')),
                MinValueValidator(decimal.Decimal('0.00')),
            ],
        )
    
        @property
        def kcal_per_100_gram(self):
            return int(self.kcal_per_gram * 100)
    
        def __str__(self):
            return self.name
    
        class Meta:
            ordering = ['name']
    
    
    class FoodstuffUnit(models.Model):
        foodstuff = models.ForeignKey(
            Foodstuff,
            models.CASCADE,
        )
        name = models.CharField(max_length=200)
        net_weight = models.DecimalField(
            max_digits=6,
            decimal_places=2,
            validators=[
                MinValueValidator(decimal.Decimal('0.01')),
            ],
        )
    
        @property
        def kcal(self):
            return int(self.foodstuff.kcal_per_gram * self.net_weight)
    
        def __str__(self):
            return self.name
    
        class Meta:
            ordering = ['name']
            unique_together = ('foodstuff', 'name')
    
    
    class Dish(models.Model):
        name = models.CharField(max_length=200)
    
        @property
        def kcal(self):
            return sum(i.kcal for i in self.dishingredient_set.all())
    
        def __str__(self):
            return self.name
    
        class Meta:
            ordering = ['name']
            verbose_name_plural = 'dishes'
    
    
    class DishIngredient(models.Model):
        dish = models.ForeignKey(
            Dish,
            models.CASCADE,
        )
        foodstuff = models.ForeignKey(
            Foodstuff,
            models.PROTECT,
        )
        foodstuff_unit = models.ForeignKey(
            FoodstuffUnit,
            models.PROTECT,
            blank=True,
            null=True,
        )
        amount = models.DecimalField(
            max_digits=6,
            decimal_places=2,
            validators=[
                MinValueValidator(decimal.Decimal('0.00')),
            ],
        )
    
        @property
        def kcal(self):
            weight = self.amount
            if self.foodstuff_unit is not None:
                weight *= self.foodstuff_unit.net_weight
            return int(self.foodstuff.kcal_per_gram * weight)
    
        def __str__(self):
            unit = 'гр' if self.foodstuff_unit is None else f'{self.foodstuff_unit}'
            return f'{self.dish}: {self.foodstuff}, {unit} - {self.amount}'
    
        class Meta:
            ordering = ['id']


    admin.py
    from django.contrib import admin
    
    from . import models as app_models
    
    
    class FoodstuffUnitInline(admin.TabularInline):
        model = app_models.FoodstuffUnit
        extra = 0
        readonly_fields = (
            'kcal',
        )
    
    
    @admin.register(app_models.Foodstuff)
    class FoodstuffAdmin(admin.ModelAdmin):
        list_display = (
            'name',
            'kcal_per_100_gram',
        )
        inlines = (
            FoodstuffUnitInline,
        )
        readonly_fields = (
            'kcal_per_100_gram',
        )
    
    
    class DishIngredientInline(admin.TabularInline):
        model = app_models.DishIngredient
        extra = 0
        readonly_fields = (
            'kcal',
        )
    
    
    @admin.register(app_models.Dish)
    class DishAdmin(admin.ModelAdmin):
        list_display = (
            'name',
            'kcal',
        )
        inlines = (
            DishIngredientInline,
        )
        readonly_fields = (
            'kcal',
        )
    Ответ написан