Ответы пользователя по тегу Django
  • Как динамически фильтровать field в форме?

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

    Без js фильтрацию значений зависимых полей можно реализовать только с помощью многошаговой формы: форма с единственным полем Partner -> Submit -> форма со всеми остальными полями.
    Ответ написан
  • 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()
    Ответ написан
  • Как запустить сборку 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
    Ответ написан
  • Как преобразовать 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'
    Ответ написан
  • Как интегрировать Django Channels в Django используя докер?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Есть два варианта:
    1. Если нагрузка небольшая, можно использовать Daphne и для WSGI, и для ASGI запросов;
    2. Передавать контейнерам режим работы через environment variables (использовать entrypoint script, который будет запускать manage.py runserver, uwsgi, gunicorn, daphne, или manage.py runworker в зависимости от выбранного режима), запросы распределять между контейнерами в зависимости от пути или доменного имени.

    "По классике" статику отдаёт не Nginx, а S3 - AWS или его аналоги в проде, Minio в dev окружениях.

    Вместо Nginx советую использовать Traefik - он умеет находить контейнеры и роутить запросы к ним в зависимости от параметров, указанных в docker-compose.yml. Автоматический load balancing, letsencrypt без лишних телодвижений, очень просто конфигурируется, может обслуживать несколько разных проектов без дополнительных настроек (актуально для фрилансеров).

    #!/bin/sh
    
    set -o errexit
    set -o pipefail
    set -o nounset
    
    
    if [ $DJANGO_DEBUG == "on" ]; then
        ./manage.py runserver 0.0.0.0:8000
    elif [ $DJANGO_MODE == "worker" ]; then
        ./manage.py runworker
    else
        daphne -b 0.0.0.0 -p 8000 config.asgi:application
    fi
    Ответ написан
  • Как прочитать словарь из текстового файла?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Это не json, т.к. в json используются двойные кавычки. Прочитать такие данные можно с помощью модуля ast:

    #!/usr/bin/env python3
    import ast
    
    
    filename = 'file.txt'
    
    # open file
    with open(filename) as f:
        # read lines one by one
        for line in f:
            # remove semicolon and new line characters from the end of the line
            line = line.rstrip('\n;')
            # parse string
            data = ast.literal_eval(line)
            # print representation of the data
            print(repr(data))
    Ответ написан
  • Как в Django создать атрибут модели без добавления поля в БД?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Для этого можно использовать либо property:
    import os
    
    from django.db import models
    
    
    class Article(models.Model):
        title = models.CharField(
            max_length=200,
        )
        image = models.ImageField(
            blank=True,
            upload_to='images',
        )
    
        @property
        def thumbnail(self):
            if self.image:
                path, filename = os.path.split(self.image.url)
                name, ext = os.path.splitext(filename)
                filename = 'prefix_' + name + '_suffix' + ext
                return os.path.join(path, filename)


    либо функцию:
    import os
    
    from django.contrib import admin
    from django.utils.html import format_html
    
    
    @admin.register(Article)
    class ArticleAdmin(admin.ModelAdmin):
        list_display = (
            'title',
            'get_thumbnail',
        )
        fields = (
            'title',
            'get_thumbnail',
        )
        readonly_fields = (
            'get_thumbnail',
        )
    
        def get_thumbnail(self, obj):
            if obj.image:
                path, filename = os.path.split(self.image.url)
                name, ext = os.path.splitext(filename)
                filename = 'prefix_' + name + '_suffix' + ext
                return format_html('<img src="{}">', os.path.join(path, filename))
        get_thumbnail.short_description = 'Thumbnail'
        get_logo.admin_order_field = 'image'


    либо и то, и другое:
    import os
    
    from django.contrib import admin
    from django.utils.html import format_html
    
    
    @admin.register(Article)
    class ArticleAdmin(admin.ModelAdmin):
        list_display = (
            'title',
            'get_thumbnail',
        )
        fields = (
            'title',
            'get_thumbnail',
        )
        readonly_fields = (
            'get_thumbnail',
        )
    
        def get_thumbnail(self, obj):
            url = obj.thumbnail
            return format_html('<img src="{}">', url) if url else ''
        get_thumbnail.short_description = 'Thumbnail'
        get_logo.admin_order_field = 'image'
    Ответ написан
  • Как ускорить импорт из csv в бд?

    neatsoft
    @neatsoft
    Life is too short for bad software
    1. Итерировать строки по несколько сотен штук, и создавать соответствующие им объекты единым вызовом к bulk_create:
    import itertools
    
    def grouper(iterable, n, fillvalue=None):
        args = [iter(iterable)] * n
        return itertools.zip_longest(*args, fillvalue=fillvalue)
    
    ...
    
    limit = 250
    
    for index, items in enumerate(grouper(rows, limit)):
        items = (i for i in items if i) # remove empty rows added by grouper
    
        products = []
    
        for item in items:
            product = Product(
                price=item[0],
                shipping=item[1],
            )
            products.append(product)
    
        Product.objects.bulk_create(products)

    Все 15 миллионов объектов разом создавать не стоит - ничего хорошего из этого не выйдет.

    2. Если необходимо создавать зависимые объекты, использовать транзакции:
    from django.db import transaction
    
    ...
    
    limit = 250
    
    for index, items in enumerate(grouper(rows, limit)):
        with transaction.atomic():
            for item in (i for i in items if i):
                product = Product.objects.create(
                    price=item[0],
                    shipping=item[1],
                )
                # product can be used there to create another objects, e.g.:
                for color in item[2].split(','):
                    ProductColor.objects.create(
                        product=product,
                        color=color,
                    )

    Транзакции, в данном случае, обеспечивают существенный прирост производительности.

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

    4. Скомбинировать 3 и 1 или 3 и 2.

    5. Модели в джанго - это просто таблицы в базе данных. Можно добавлять строки с помощью SQL запросов из отдельного скрипта на Python или Go.
    Ответ написан
  • Как изменять имя файла при загрузки в Django?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Для именования файлов лучше использовать хэш, а не uuid. Это гарантирует уникальность имён, и обеспечивает дедупликацию данных: сколько бы раз пользователи не заливали файл с одинаковым содержимым, он будет храниться в единственном экземпляре (исключение - файлы с одинаковым содержимым и разными расширениями, но это разумный компромисс - работать с файлами без расширений менее удобно на многих этапах).

    Можно реализовать соответствующую функциональность самостоятельно:

    app_name/models.py:
    import hashlib
    import os
    
    from django.conf import settings
    from django.core.files.storage import FileSystemStorage
    from django.db import models
    
    
    def upload_to(instance, filename, fieldname):
        ext = os.path.splitext(filename)[1].lower()
        class_name = instance.__class__.__name__.lower()
    
        h = hashlib.sha256()
        field = getattr(instance, fieldname)
        for chunk in field.chunks():
            h.update(chunk)
        name = h.hexdigest()
    
        return os.path.join(
            class_name,
            name + ext,
        )
    
    
    class OverwriteStorage(FileSystemStorage):
        def get_available_name(self, name, max_length=None):
            if self.exists(name):
                os.remove(os.path.join(settings.MEDIA_ROOT, name))
            return name
    
    
    class Article(models.Model):
        image = models.ImageField(
            storage=OverwriteStorage(),
            upload_to=lambda inst, fn: upload_to(inst, fn, 'image'),
        )


    Но гораздо разумнее воспользоваться готовой библиотекой:

    Установить hashedfilenamestorage:
    pip install django-hashedfilenamestorage

    и добавить следующую строку в settings.py:
    DEFAULT_FILE_STORAGE = 'django_hashedfilenamestorage.storage.HashedFilenameFileSystemStorage'
    Ответ написан
  • Как использовать Django аутентификацию с Amazon Gateway API?

    neatsoft
    @neatsoft
    Life is too short for bad software
    OAuth + Amazon Lambda function для проверки токенов
    API Gateway Custom Authorizers
    Использую в крупном проекте с большим количеством клиентов, несколько микросервисов с единым центром авторизации, всё работает как надо.
    Ответ написан
  • Как хранить глобальные объекты в Django?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Для выполнения фоновых задач и поддержки вебсокетов в Django нужно использовать Django Channels.
    Если требуется сохранять большие объёмы данных между вызовами, то либо БД, либо Redis.
    Ответ написан
  • Есть идеи, как реализовать создание/хранение приходной/расходной накладной на Django?

    neatsoft
    @neatsoft
    Life is too short for bad software
    В одной таблице должны храниться документы, во второй - строки табличной части (всех документов).

    models.py:
    from django.db import models
    from django.db.models import Max, Sum
    from django.utils.translation import ugettext_lazy as _
    
    
    class Product(models.Model):
        name = models.CharField(
            _('Name'),
            max_length=200,
            db_index=True,
        )
    
        def __str__(self):
            return self.name
    
    
    class Document(models.Model):
        INVOICE = 'I'
        WAYBILL = 'W'
        TYPE_CHOICES = (
            (INVOICE, _('Invoice')),
            (WAYBILL, _('Waybill')),
        )
        type = models.CharField(
            _('Type'),
            max_length=1,
            choices=TYPE_CHOICES,
        )
        number = models.CharField(
            _('Number'),
            blank=True,
            max_length=50,
        )
        created_at = models.DateTimeField(
            _('Created'),
            auto_now_add=True,
            db_index=True,
        )
    
        @property
        def total(self):
            return self.items.aggregate(sum=Sum('total'))['sum']
    
        class Meta:
            ordering = ['-created_at']
    
    
    class DocumentItem(models.Model):
        document = models.ForeignKey(
            Document,
            models.CASCADE,
            related_name='items',
        )
        position = models.PositiveIntegerField(
            verbose_name=_('Position'),
            editable=False,
            db_index=True,
        )
        product = models.ForeignKey(
            Product,
            models.PROTECT,
        )
        price = models.DecimalField(
            _('Price'),
            max_digits=12,
            decimal_places=2,
        )
        quantity = models.DecimalField(
            _('Quantity'),
            max_digits=10,
            decimal_places=3,
        )
        total = models.DecimalField(
            _('Total'),
            max_digits=12,
            decimal_places=2,
        )
    
        def save(self, *args, **kwargs):
            if not self.position:
                position = self.document.items.aggregate(Max('position'))['position__max'] or 0
                self.position = position + 1
            super(DocumentItem, self).save(*args, **kwargs)


    admin.py:
    from django.contrib import admin
    
    from .models import Product, Document, DocumentItem
    
    
    @admin.register(Product)
    class ProductAdmin(admin.ModelAdmin):
        pass
    
    
    class DocumentItemInline(admin.TabularInline):
        model = DocumentItem
        fields = (
            'position',
            'product',
            'price',
            'quantity',
            'total',
        )
        readonly_fields = (
            'position',
        )
        ordering = ['position']
    
    
    @admin.register(Document)
    class DocumentAdmin(admin.ModelAdmin):
        inlines = [
            DocumentItemInline,
        ]
        list_display = (
            'type',
            'number',
            'created_at',
            'total',
        )
        list_filter = (
            'type',
        )
        search_fields = (
            '=number',
        )


    Стандартная админка - для примера, вообще она не для этого (обычным пользователям она не должна быть доступна). Но реальные формы строятся по тому же принципу.

    Код накидал прям здесь, не проверял, поэтому возможны незначительные ошибки.
    Ответ написан
  • Как синхронизировать базу и миграции Django?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Информация о примененных миграциях хранится в самой базе данных (таблица django_migrations). Поэтому если скрипты миграций вручную не менялись и не удалялись, то никаких проблем при деплое не возникнет. Более того, дальнейшие изменения структуры БД можно без проблем накатывать с помощью python manage.py migrate - в этом и заключается прелесть миграций.
    Ответ написан
  • Как в Django 1.11 создать дополнительные поля к связи многие-ко-многим?

    neatsoft
    @neatsoft
    Life is too short for bad software
    т.к. класс Ingredients на момент использования еще не объявлен, его название нужно взять в кавычки:
    ...
        ingridients = models.ManyToManyField(
            Food,
            through='Ingredients',
            through_fields=('recipe', 'food')
        )
    ...

    lazy relationships
    Ответ написан
  • Как в одном проекте Django оптимально реализовать сайты на поддоменах в разных городах?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Отдельные базы данных и DATABASE_ROUTERS это жуткая головная боль - поломанные миграции, неочевидные глюки, существенное замедление разработки, и т.д. Не надо так делать. Есть один подобный проект на сопровождении, поэтому знаю о чем говорю.

    При правильно расставленных индексах количество строк в таблице слабо влияет на производительность базы данных (если лимитировать выдачу и задумываться о том какие именно запросы формирует ORM), поэтому о размере бд особо переживать не стоит. Лучше озадачиться грамотным кэшированием.
    Чтобы исключить взаимные блокировки при создании новых объектов советую присмотреться к uuid в качестве первичных ключей:
    class MyModel(models.Model):
        uuid = models.UUIDField(
            verbose_name=_('Uuid'),
            primary_key=True,
            default=uuid.uuid4,
            editable=False,
        )

    Файл settings.py должен быть только один, особенные настройки для каждого из сайтов нужно хранить в бд. Выбор сайта на основании имени домена можно производить с помощью middleware. Некоторые модели будут общими для всех сайтов, некоторые нужно будет фильтровать.
    Если предполагается использование стандартной админки, то для пользователей без superuser статуса нужно будет фильтровать queryset и автоматически добавлять site_id при создании новых объектов.
    Ответ написан
  • Как получить всех людей начинающихся с перечня букв?

    neatsoft
    @neatsoft
    Life is too short for bad software
    import operator
    import functools
    
    from django.db.models import Q
    
    from .models import Item
    
    
    def get_items(letters=''):
        items = Item.objects.filter(active=True)
        items = items.exclude(id__lt=100).exclude(id__gt=200)
        if letters:
            q = functools.reduce(operator.or_, (Q(name__istartswith=l) for l in letters))
            items = items.filter(q)
        return items
    
    get_items('abc')


    from django.db.models import Q
    
    from .models import Item
    
    
    def get_items(letters='', active=None):
        q = Q()
        if len(letters) == 3 and letters[1] == '-':
            letters = (unichr(c) for c in range(ord(letters[0]), ord(letters[2]) + 1))
        for letter in letters:
            q |= Q(name__istartswith=letter)
        if active is not None:
            q &= Q(active=active)
        return Item.objects.filter(q)
    
    get_items(u'абвгд')
    get_items(u'в-д')
    get_items(u'а-я', True)
    Ответ написан
  • Нужно ли дополнительно настраивать сервер под django?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Выполнить шаги, описанные в Django deployment checklist

    Настроить ssh:
    • запретить доступ для пользователя root
    • настроить авторизацию с помощью ключа
    • запретить авторизацию с помощью пароля


    Настроить firewall:
    • разрешить входящие соединения на 22, 80, и 443 порты (ssh, http, https)
    • запретить все остальные входящие соединения


    Настроить ssl:
    • приобрести ssl сертификат
    • сделать переадресацию с http на https


    Если у пользователей есть возможность загрузки файлов на сайт, то:
    • приобрести отдельный домен для хостинга этих файлов
    • настроить nginx для раздачи media_root с этого домена
    • проверить недоступность media с основного домена
    Security in Django - User-uploaded content

    ps. В будущих проектах попробуйте вместо Gunicorn использовать uWSGI, а вместо CentOS 7 - Ubuntu 16.04 server.
    Ответ написан
  • Как перенести юзера в другую группу через n-дней?

    neatsoft
    @neatsoft
    Life is too short for bad software
    Канонический способ сделать это в джанго - добавление django-admin команды, и периодический запуск этой команды с помощью cron:
    Writing custom django-admin commands
    CronHowto
    Ответ написан
  • Как правильно настроить media-root?

    neatsoft
    @neatsoft
    Life is too short for bad software
    MYSITE_URL = '<abs_path>/domains/mysite.ru'
    STATIC_ROOT = os.path.join(MYSITE_URL, 'static')
    MEDIA_ROOT = os.path.join(MYSITE_URL, 'media')

    Но для production это не подходит. Каталог static нужно раздавать с помощью nginx (предварительно скомпоновав его с помощью python manage.py collectstatic), a media выносить на отдельный домен (для предотвращения XSS уязвимостей).
    Ответ написан