Django tips & tricks

    Приветствую!

    В этом посте — небольшие советы по работе с Django, которые могут пригодиться начинающим разработчикам. Как я хотел бы знать это в начале моего пути освоения Django!..
    Рассматривать эти советы следует с долей критицизма. Буду рад, если вы найдёте неточности / лучшее решение, или предложите свои «фишки» для django, которых нет в документации.

    Итак, начнём издалека, а уж потом перейдём к деталям.



    venv

    Виртуальное окружение


    Если вы не используете virtualenv для вашего django-приложения — то обязательно попробуйте.

    Если вы уже используете virtualenv, то ответьте, нужен ли вам --no-site-packages. Этот флаг по умолчанию включён и используется при создании виртуального окружения. При включённом флаге программы «внутри» окружения не увидят программы «снаружи». Если вы поставите вашим пакетным менеджером какой-нибудь пакет глобально, например, python2-django, то «внутри» окружения всё равно придётся делать pip install django.
    Зачем могут понадобиться глобально установленные пакеты?
    Я столкнулся с этим, когда настраивал поисковый движок на xapian. Xapian идёт в поставке xapian-core (написан на C++) и xapian-bindings (обвязка для разных ЯП, в том числе python). Логично их обновлять одновременно — если изменился движок, то и обвязки надо обновить. Поэтому ставить xapian-core глобально пакетным менеджером, а обвязки через pip не устравивает (к тому же, их нет в pip). Выхода 2:
    1. Создать помойку внутри virtualenv: ./configure --prefix=/path/to/virtualenv && make && make install
    2. Сделать глобальные пакеты видимыми извне и обновлять их пакетным менеджером дистрибутива, что я и выбрал

    Вообще, когда модуль написан на чистом питоне, проблем не возникает — устанавливаем через pip в virtualenv. Если модуль — это смесь, скажем, c++ и питона — начинается магия.

    Видимость / невидимость глобальных программ из virtualenv устанавливается отсутствуем / наличием файла [virtualenv]/lib/python*.*/no-global-site-packages.txt. Вот так просто.

    Кстати, рекомендую всем статью про «изолированность» virtualenv: Why I hate virtualenv and pip (сайт тормозит, смог открыть только через web.archive.org). В ней рассматривается, насколько virtualenv действительно изолирован от «внешней» среды — если кратко, то это лишь частичная изоляция.


    ipython


    Pip install ipython заменит стандартный питоновский шелл на продвинутый, с раскрашиванием, автодополнением, интроспекцией, удобным многострочным вводом, копипейстом и т.д. Django автоматически подцепляет ipython, если он установлен.
    Кстати, все перечисленные достоинства можно использовать не только в ./manage.py shell, но и в дебаге, вызывая отладку с помощью import ipdb; ipdb.set_trace().

    Структура проекта


    Django по умолчанию при создании проекта или приложения создаёт необходимые каталоги. Но и самим нужно думать.

    Как проект назовёшь, так и будешь импортировать

    Называйте ваш проект project (django-admin.py startproject project) — ну или другим, но одинаковым именем для всех проектов. Раньше я называл проекты соответственно домену, но при повторном использовании приложений в других проектах приходилось менять пути импорта — то from supersite import utils, то from newsite import utils. Это путает и отвлекает. Если расширить этот совет — зафиксируйте (унифицируйте) для себя структуру каталогов всех ваших проектов и строго её придерживайтесь.

    Живой пример:
    --site.ru
      |--static
      |--media
      |--project (папка с проектом)
         |--manage.py
         |--project (папка с основным приложением)
         |  |--settings.py
         |  |--urls.py
         |  |-- ...
         |--app1
         |--app2
         |--...
    


    Куда сохранять html-шаблоны

    Никогда, никогда не кидайте шаблоны (.html) в папку templates вашего приложения. Всегда создавайте дополнительный каталог с названием, совпадающим с именем приложения.
    Вот это плохо, т.к. создаёт коллизию шаблонов, например, при {% include 'main.html' %}:
    /gallery/templates/main.html
    /reviews/templates/main.html
    

    Вот это — хорошо, можно использовать {% include 'reviews/main.html' %}:
    /gallery/templates/gallery/main.html
    /reviews/templates/reviews/main.html
    


    {% include %}

    К слову, если вы используете {% include 'some_template.html' %}, то велика вероятность, что что-то не так. Почему?
    Пример:
    def view(request):
        return render(
            request,
            'master.html',
            {'var': 'Some text'}
        }
    

    <!-- master.html -->
    Value of variable var: {{ var }}.
    {% include 'slave.html' %}
    
    <!-- slave.html -->
    Again, value of variable var: {{ var }}.
    


    1) KISS едет лесом. С одной стороны, код страницы разбит на несколько — master.html и подключаемый slave.html, и это удобно для разделения больших html-страниц на части. Но в данном случае переменная var передаётся в шаблон slave.html неявно — var передатся в master.html, а slave.html просто «цепляет» контекст master'а. Таким образом, мы видим, что шаблон внутри {% include %} зависит от контекста основного шаблона. Мы вынуждены следить за контекстом родительского шаблона, иначе в дочерний может попасть что-нибудь не то.
    2) По моим наблюдениям, {% include %} дорогой в плане рендеринга. Лучше его избегать.

    Что делать? Если очень хочется одни шаблоны включать в другие — используйте inclusion tags (о них читать ниже). Но проще — просто пишите всё в одном файле:
    <!-- master.html -->
    Value of variable var: {{ var }}.
    Again, value of variable var: {{ var }}.
    


    settings.py

    Вы же не имеете два разных settings.py на тестовом и деплой серверах, да?
    Создайте дополнительные local_settings.py и deployment_settings.py, куда скиньте всё, что относится только к соответствующему серверу.
    Вот, например, что логично задавать в local_settings.py
    DEBUG = True
    
    DOMAIN = '127.0.0.1:8000'
    ALLOWED_HOSTS = ['127.0.0.1', DOMAIN]
    
    SERVER_EMAIL = 'mail@test.ru'
    EMAIL_HOST = 'localhost'
    EMAIL_PORT = 1025
    EMAIL_HOST_USER = ''
    EMAIL_HOST_PASSWORD = ''
    EMAIL_USE_TLS = False
    EMAIL_SUBJECT_PREFIX = '[' + DOMAIN + '] '
    
    DEFAULT_FROM_EMAIL = 'mail@test.ru'
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': 'test',
            'USER': 'test',
            'PASSWORD': 'test',
            'HOST': 'localhost',
            'PORT': '',
            'ATOMIC_REQUESTS': True,
        }
    }
    
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
        }
    }
    



    В settings.py пишем в начале:
    # Load local settings if available
    try:
        from local_settings import *
    except ImportError:
        from deployment_settings import *
    

    Соответственно, на деплое удаляем local_settings.py. Чтобы он не мешался, его можно добавить в .gitignore.

    Корень проекта

    Задайте корень проекта в settings.py — это облегчит жизнь потом:
    from os import path
    BASE = path.dirname(path.dirname(path.dirname(path.abspath(__file__))))
    MEDIA_ROOT = BASE + '/media/'
    STATIC_ROOT = BASE + '/static/'
    


    Контекстные процессоры (context_processors.py), {% include %} и inclusion tags


    Используйте контекстные процессоры только если вам нужно добавить переменные в контекст каждой страницы сайта — ведь контекстные процессоры будут вызываться для любой страницы, даже если вы не воспользуйтесь их результатами. Лично я использую их для передачи номера телефона в контекст шаблона — этот номер реально на каждой странице выводится, и не единожды. Ещё пример — меню сайта. Я прописал заголовки и ссылки в контекстном процессоре, и если мне нужно будет добавить новый раздел в меню — я просто добавлю его в контекстный процессор, и он автоматически добавится везде на сайте.

    Есть одна ошибка — использование контекстных процессоров для виджетов. Например, у вас на сайте есть колонка новостей, которая выводится всегда, т.е. на каждой страничке. Казалось бы, создать news/context_processors.py, и в контекст добавлять переменную news с новостями, а в шаблоне {% include 'news/news_widget.html' %}, или даже {% load news_widget %} {% news_widget news %}…

    Это работает, но это замусоривает контекст и, кроме того, кто знает, всегда ли у вас будет эта колонка. Выход есть — используйте inclusion tag. Вы просто пишете в шаблоне {% news %}, а уже этот templatetag ищет новости и вставляет колонку новостей. И работает он только тогда, когда вы его реально запускаете — т.е. пишете {% news %} в шаблоне.


    Батарейки


    django-debug-toolbar-template-timings

    Все его знают и, наверно, используют. Но есть django-debug-toolbar-template-timings — плагин к debug toolbar, который замеряет время рендеринга шаблонов. А учитывая, что шаблоны django довольно «дорогие» (рендерятся долго), то для ускорения сайта этот плагин — то что доктор прописал.

    adv_cache_tag

    django-adv-cache-tag позволяет очень гибко управлять кешированием в шаблонах — версионность, сжатие, частичное кэширование. Просто оцените:
    {% load adv_cache %}
    {% cache 0 object_cache_name object.pk obj.date_last_updated %}  <!-- Закэшировать без таймаута, обновить кэш при обновлении obj.date_last_updated -->
      {{ obj }}
      {% nocache %}
         {{ now }}  <!-- А это никогда не кэшируем -->
       {% endnocache %}
       {{ obj.date_last_updated }}
    {% endcache %}
    


    django-mail-templated

    Шаблоны email писем — это то, чего не хватает django. django-mail-templated

    django-ipware

    django-ipware определит ip пользователя за вас, и сделает это лучше.
    Вы же знаете, откуда брать ip пользователя?
    'HTTP_X_FORWARDED_FOR', # client, proxy1, proxy2
    'HTTP_CLIENT_IP',
    'HTTP_X_REAL_IP',
    'HTTP_X_FORWARDED',
    'HTTP_X_CLUSTER_CLIENT_IP',
    'HTTP_FORWARDED_FOR',
    'HTTP_FORWARDED',
    'HTTP_VIA',
    'REMOTE_ADDR',
    



    Beautiful Soup

    Не пишите свой парсер html. Не парсите html сами. Всё уже есть.

    Templatetags, которые могут пригодиться


    add_class

    Если вы создаёте форму и хотите для каждого input-а задать стиль, класс или placeholder, то django заставит вас нарушить принципы и прописать все стили прямо в forms.py:
    class SomeForm(ModelForm):
        class Meta:
            model = SomeModel
            fields = ('field1', 'field2')
            widgets = {
                'field1': Textarea(attrs={'rows': '2', 'class': 'field1_class'}),
            }
    

    Меня каждый раз коробит при виде html текста не в .html файлах. Это нарушает MVT архитектуру. Поэтому я создал для себя фильтр:
    {% load add_class %}
    {{ form.field1|add_class:'field1_class' }}
    

    Данный фильтр добавляет класс к тегам, но можно переписать и добавлять любое свойство.
    Код add_class.py
    from django import template
    from django.utils.safestring import mark_safe
    from bs4 import BeautifulSoup
    
    register = template.Library()
    @register.filter
    def add_class(html, css_class):
        soup = BeautifulSoup(unicode(html), 'html.parser')
    
        for tag in soup.children:
            if tag.name != 'script':
                if 'class' in tag:
                    tag['class'].append(css_class)
                else:
                    tag['class'] = [css_class]
    
        return mark_safe(soup.renderContents())
    



    is_current_page

    Иногда нужно что-то выводить в шаблоне, если открыта определённая страница. Например, подсветить кнопку «магазин» в меню, если пользователь сейчас в разделе магазина. Предлагаю следующий вариант:
    from django import template
    from django.core.urlresolvers import resolve
    from project.utils import parse_args
    
    register = template.Library()
    @register.filter
    def is_current_page(request, param):
        return resolve(request.path).view_name == param
    

    Это фильтр, а не тэг, и причина тут одна: можно строить совершенно дичайшие конструкции с {% if %}. Например, если текущая страница — карточка товара, и при этом пользователь авторизован:
    {% if request|is_current_page:'shop/product' and user.is_authenticated %}
    

    Есть и альтернативная, более точная, реализация, в которой используются аргументы (args или kwargs) для определения точной страницы (т.е. не просто «страница какого-либо товара», а «страница товара с id=36»):
    {% if request|is_current_page:'shop/product,id=36' %}
    

    @register.filter
    def is_current_page(request, param):
        params = param.split(',')
        name = params[0]
        args, kwargs = parse_args(params[1:])
        # Do not mix args and kwargs in reverse() - it is forbidden!
        if args:
            return request.path == reverse(name, args=args)
        elif kwargs:
            return request.path == reverse(name, kwargs=kwargs)
        else:
            return request.path == reverse(name)
    



    Модели


    Пустые

    Модели могут быть пустыми. Вот так:
    class Phrase(models.Model):
        pass
    
    class PhraseRu(models.Model):
        phrase = models.ForeignKey(Phrase, verbose_name='фраза', related_name='ru')
    
    class PhraseEn(models.Model):
        phrase = models.ForeignKey(Phrase, verbose_name='фраза', related_name='en')
    

    В данном случае Phrase является связующим звеном между PhraseEn и PhraseRu, хотя сама в себе ничего не содержит. Полезно, когда две модели равнозначны, и их необходимо связать в единое целое.

    Generic relation mixin

    Объекты GenericRelation всегда возвращаются QuerySet'ом, даже есть мы точно знаем, что объект один:
    class Token(models.Model):
        content_type = models.ForeignKey(ContentType)
        object_id = models.PositiveIntegerField()
        content_object = generic.GenericForeignKey()
    
    class Registration(models.Model):
        tokens = generic.GenericRelation(Token)
    

    Если нужно получить доступ к токену, мы пишем registration.tokens.first(). Но мы-то знаем, что токен один, и хотим писать просто registration.token и получить сразу заветный токен. Это возможно при помощи mixin:
    class Token(models.Model):
        content_type = models.ForeignKey(ContentType)
        object_id = models.PositiveIntegerField()
        content_object = generic.GenericForeignKey()
    
    class TokenMixin(object):
        @property
        def token(self):
            content_type = ContentType.objects.get_for_model(self.__class__)
            try:
                return Token.objects.get(content_type__pk=content_type.pk, object_id=self.id)
            except Token.DoesNotExist:
                return None
    
    class Registration(models.Model, TokenMixin):
        tokens = generic.GenericRelation(Token)
    


    Теперь registration.token работает!

    get_absolute_url


    Старайтесь не писать {% url 'shop/product' id=product.id %}.
    Лучше для каждой модели задайте метод get_absolute_url(), и используйте {{ object.get_absolute_url }}. Заодно и ссылка «смотреть на сайте» появится в админке.

    pre_save

    В pre_save можно узнать, изменится ли модель после сохранения или нет. Цена — запрос к БД для получения старой записи из базы.
    @receiver(pre_save, sender=SomeModel)
    def process_signal(sender, instance, **kwargs):
        old_model = get_object_or_None(SomeModel, pk=instance.pk)
        if not old_model:
            # Created
            old_value = None
            ...
        else:
            old_value = old_model.field
        new_value = instance.field
    
        if new_value != old_value:
            # field changed!
    


    Формы


    Этот паттерн уже был на хабре, но он слишком хорош, чтобы не упомянуть его.
    form = SomeForm(request.POST or None)
    if form.is_valid():
        # ... actions ...
        return HttpResponseRedirect(...)
    return render(
        request,
        {'form': form}
    )
    


    На этом всё. Спасибо за внимание.

    UPD. Как обычно на Хабре, в комментариях хабражители высказали свои мнения и предложили кучу замечательных идей, дополнений и замечаний к статье. Я не стал их вносить в статью, но вместо этого настоятельно рекомендую ознакомиться с комментариями к статье.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 65
    • +5
      Радует что Django в последнее время набирает обороты. Причем вполне заслужено.
      Сейчас, правда, все немного замерли в ожидании релиза 1.7.

      А статью в закладки и перечитать еще несколько раз.
      • +2
        Я настройки храню в отдельных файлах в подмодуле `settings`. Просто чтобы было чуть больше порядка.

        Общие настройки — в settings/base.py, а в деве-стейджинге-продакшне делаю немного некошерно, но для настроек — самое оно:

        from .base import *
        
        # отличающиеся настройки
        
        • 0
          То есть у вас в settings.py хранятся только отличающиеся настройки? Как тогда решается проблема с системой контроля версий? (На продакшне одни settings.py, на деве — другие)
          • +1
            Мы делаем похожее, поэтому отвечу за 404.

            В settings.py определены все настройки для боевого сервера.
            Затем есть settings_local.py, который импортирует все с settings.py (from settings import *) и переопределяет необходимое, чтобы работало на local.
            А сам сервер на локалке, например запускается с дополнительным параметром --settings=settings_local

            Тоже самое с settings_dev.py (и другими версиями).

            Таким образом на боевом все с одного файла без наследований, переопределений и импорта. На dev и local — наследование и переопределение. При этом все есть в нашем git, не нужно писать исключений и удалять настроечные файлы.

            Для нас наиболее оптимальный вариант.
            • 0
              У меня вообще нет settings.py (при наличии модуля settings и settings.py у питона просто сорвёт крышу.

              При старте сервера (runserver, wsgi — не важно) просто указываются нужное значение в $DJANGO_SETTINGS_MODULE (my_project.settings.productiuon, например).
              • 0
                А в чём профит?
                • 0
                  Профит в том, что получается минимум копипасты.
                  • 0
                    Ну подождите. У вас же всё равно есть эти файлы my_project.settings.production и my_project.settings.development. Какая разница, включать из «запускатора» или из settings.py?
                    • +3
                      |-project
                      |--settings
                      |---__init__.py 
                      |---base.py
                      |---ci.py
                      |---dev.py
                      |---production.py
                      |---staging.py
                      


                      Во-первых, окружений может быть больше чем два, и тогда трюк с ImportError не прокатит, ну или надо будет делать многовложенные try-except.
                      Во-вторых, разработчиков может быть больше, чем один, и желательно иметь возможность синхронизировать их dev-окружения. Тут .gitignore нам всё поломает.
                      В-третьих, бОльшая часть настроек (INSTALLED_APPS, менеджеры контекста, мидлвари) — общие для всех окружений. Поэтому имеет смысл держать их в одном месте, коим и является base.py.
                      • 0
                        Спасибо, осознал, понравилось :)
            • +4
              В two scoops of django читал про другой подход: версионируются абсолютно все настройки (settings/prod.py, settings/dev.py и т.д). А всякие SECRET_KEY, пароли от БД и т.д выносятся в переменные окружения.
            • +10
              Для «add_class»-функциональности (и прочих полезных штук для работы с формами) рекомендую django-widget-tweaks.
            • 0
              но при повторном использовании приложений в других проектах приходилось менять пути импорта

              можно например использовать относительные импорты
              • 0
                Вы немного не поняли. Создайте django-admin.py startproject one, django-admin startproject two, и попытайтесь импортировать settings.py. Для первого проекта будет from one import settings, для второго from two import settings. Относительные пути не спасут.

                P.s. Пример надуманный. Разумеется, по-хорошему from django.conf import settings.
                • 0
                  Просто не нужно такие(переносимые) приложения «складывать» в ту же директорию где и settings.py. Кстати емнип «дефолтную» структуру проекта обновили в одной из крайних версий.
                  Тогда в проекте вместо такого импорта с именем проекта
                  from project_one.news.models import Model
                  

                  будет возможен например такой импорт
                  from news.models import Model
                  


                  вот еще как пример можно посмотреть github.com/django/djangoproject.com

                  p.s. по крайней мере у меня это нормально работает
                  p.p.s. хотя в статье у Вас указана нормальная структура(с приложениями в «корневой» директории, рядом с manage.py), тогда не понятно откуда проблема с импортами
                  • 0
                    Я вас понял :) У меня эти мысли с унификацией названий проектов появились по нескольким причинам:
                    1. Я складываю все глобальные утилиты туда же, где и settings.py, в project/project. Например, project/project/templatetags/add_class.py. По мне, так это логично — всё, что применимо ко всему проекту — в основную папку проекта. Вы сейчас предложете вынести всё это в новое приложение utils — и будете тоже правы. Дело вкуса, имхо.
                    2. Когда на разных проектах одинаковые пути — это сильно облегчает жизнь, проверено. Лично я теперь не зависаю на cd, вспоминая имя проекта.
              • –2
                <irony>У вас слишком расистская статья, слишком много упоминаний рабства.</irony>
                • +3
                  Называйте ваш проект project (django-admin.py startproject project) — ну или другим, но одинаковым именем для всех проектов. Раньше я называл проекты соответственно домену, но при повторном использовании приложений в других проектах приходилось менять пути импорта — то from supersite import utils, то from newsite import utils. Это путает и отвлекает.
                  Это вообще странно. Та структура каталогов, что у вас ниже нарисована вообще не подразумевает, что в импорты попадёт «название проекта». Их туда вообще не нужно включать, т.к. в пути надо включать именно внутренность этой папки проекта (которая вообще неважно как называется), потому непонятно как и зачем оно в импорты попадёт. Названия «основных приложений» как раз, имхо, надо делать разными. Это удобно, например, если несколько таких «основных приложений» рядом надо где-то положить. Для этого к такому виду и было приведено, емнип, в версии 1.4. До этого внутренности были снаружи и там действительно бывали проблемы с повторным использованием и импортами.

                  Лично я внутренности того что вы называете «папки с проектом» вытаскиваю в корень внешней вашей «папки сайта». Потому что эта лишняя вложенность там не особо нужна.
                  • 0
                    Добавлю пару замечаний:
                    — Приложения достаточно удобно хранить в отдельной директории, например, apps.
                    — Шаблоны имеет смысл хранить все вместе, но отдельно от приложений (т.е. выделить под все шаблоны отдельную директорию). Такая структура подталкивает хранить шаблоны в структуре app_name/tmpl_name и коллизии сразу видны на глаз. Кроме того шаблоны обычно уникальны для каждого проекта (в отличие от приложений, которые полностью или частично реюзабельны), а если шаблон универсальный то вообще имеет смысл задуматься о создании пакета.

                    • –2
                      Есть такой замечательный продукт django-cms.
                      Расписывать все прелести не буду, вкратце — CMS на Django =)
                      • –2
                        Тогда уже лучше – Wagtail.
                        • +1
                          Простите, не удержался
                          image
                          • 0
                            Так, господа, я тут не для холивара или для рекламы ссылочку оставил. А для того, чтобы незнающие смогли узнать о наличии таковой CMS.
                            • +2
                              Недавно смотрел Django-CMS и видяшку на ютубе где аффтар утверждает «Friends don't let friends use Drupal». Но вот как раз в плане CMS-нутости, ИМХО Drupal даст Django-CMS огромную фору, т.к. механизм CCK+Views+Panels это реально мощный конструктор. В Django-CMS ничего такого не нашел. Как унифицированная простейшая CMS-ка пойдет, но не имеет ИМХО большого смысла. Заказчик чаще всего не хочет изучать все эти менюшки и настройки, он хочет зайти в админку и увидеть большую красную кнопку — «PROFIT». А для этого админку требуется сделать под заказчика и Django-CMS тут совсем не помогает +))

                              P.S: Сам долго юзал Drupal, но последний год использую Django, ибо в работе черезчур уникальные для Drupal-а вещи +))
                              • –1
                                Drupal — PHP, Django (-cms) — Python, и вообще речь в топике не об этом)
                                Клиенту, естественно, хочется иметь такую кнопку, кто ж такую не хочет)) как только такая `работающая` появится — закажу себе парочку.
                                • +2
                                  А причем тут язык на котором написана CMS? Я говорю про функционал, что я не видел среди CMS для питона аналогов CCK+Views+Panels в Drupal.
                          • +2
                            {% include 'slave.html' %}
                            {% include 'slave.html' with var=var %}
                            

                            И никакие они не медленные. При частом обращении ФС поместит файл в память и будет все нормально. Вдвойне неактуально во времена ssd vps. У меня основной шаблон так весь в инклудах, потому как редактировать простыни, где нужный код находится в 40-х отступах от левого края совсем неудобно. И в cvs красота — видно, что правили footer.html, а не ничего говорящее base.html.

                            pre_save
                            

                            Есть еще Django Dirty Fields — неизменная специя к большинству моих моделей (автор идеи сам Armin!). Умеет так:

                            class TestModel(DirtyFieldsMixin, models.Model):
                                boolean = models.BooleanField(default=True)
                            
                            >>> tm = TestModel(boolean=True,characters="testing")
                            >>> tm.save()
                            >>> tm.boolean = False
                            >>> tm.is_dirty()
                            True
                            >>> tm.get_dirty_fields()
                            {'boolean': True}
                            
                            • 0
                              а для чего такое можно использовать?
                              • +2
                                Чаще всего я его использую в связке с mptt, т.к. предпочитаю хранить урлы категорий в базе:

                                class TreeModel(MPTTModel, DirtyFieldsMixin):
                                    parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
                                    title = models.CharField(max_length=100)
                                    slug = models.SlugField(max_length=70, db_index=True, unique=True)
                                    path = models.CharField(max_length=255, editable=False, unique=True, db_index=True)
                                

                                Ключевое тут поле path — оно хранит адрес предков: /parent/parent/child/, и при сохранении предка надо это поле обновить у всех детей вниз по иерархии. А чтобы не дергать базу просто так, при сохранении модели я проверяю, изменилось ли поле slug и если да, обновляю его у всех детей.

                                def save(self, *args, **kwargs):
                                    self.path = self.slug
                                    if self.parent:
                                        self.path = '{}/{}'.format(self.parent.path, self.slug)
                                    # Надо сохранить модель прежде, чем браться за детей, чтобы записать path,
                                    # но после вызова super.save память о старых полях потрется,
                                    # поэтому надо запомнить состояние
                                    is_dirty = 'path' in self.get_dirty_fields()
                                    super(TreeModel, self).save(*args, **kwargs)
                                    # Если изменение поля было, пускаем рекурсию — каждый child запустит свою проверку
                                    if is_dirty:
                                        for child in self.get_children():
                                            child.save()
                                
                                • 0
                                  ах вот оно как работает, показывает изменение поля, тогда понятно и полезно :)
                                  Просто из вашего первого примера, не совсем понятно было, что ж оно делает
                              • 0
                                Спасибо.

                                Я называю инклюды медленными, потому что в своё время делал
                                {% for item in items %}
                                    {% include 'item_description.html' %}
                                {% endfor %}
                                

                                Замена include сделала приложение быстрее.

                                А ещё коварный include не ругнётся, если вы забудете передать какую-либо переменную в его контекст, а inclusion tag ругнётся. Так что лично я не любитель инклюдов, хотя и пользуюсь ими иногда для разбивки полотна html на несколько мелких кусочков, не нуждающихся в контексте, например, {% include 'google_analytics.html' %}.
                                • +2
                                  Я и сейчас так делаю. Это от проекта зависит: если кусочек не маленький и это противоречит DRY (в разумных рамках: большой шаблон, много условий и т.д.)
                                  Вот как это легко решается:

                                  {% for item in items %}
                                      {% cache 1800 list_item item.id item.modified %}
                                          {% include 'item_description.html' %}
                                      {% endcache %}
                                  {% endfor %}
                                  

                                  Если item изменится, он тут же обновит кеш, а старый сам протухнет со временем.

                                  {% include 'google_analytics.html' %}
                                  

                                  Да прибудет с вами django-chunks!
                              • +3
                                Но в данном случае переменная var передаётся в шаблон slave.html неявно — var передатся в master.html, а slave.html просто «цепляет» контекст master'а. Таким образом, мы видим, что шаблон внутри {% include %} зависит от контекста основного шаблона. Мы вынуждены следить за контекстом родительского шаблона, иначе в дочерний может попасть что-нибудь не то.

                                Решается параметром only:

                                If you want to render the context only with the variables provided (or even no variables at all), use the only option. No other variables are available to the included template:
                                {% include "name_snippet.html" with greeting="Hi" only %}
                                • 0
                                  Неплохой скелет проекта — django-skel.readthedocs.org/en/latest/, github.com/rdegges/django-skel
                                  • 0
                                    Еще можно описывать настройки для всех окружений в settings.py с помощью модуля django-configurations. Очень удобно если больше двух окружений.
                                  • 0
                                    Вместо virtualenv удобней использовать virtualenvwrapper.
                                    Также в Python 3.4 есть встроенное виртуальное окружение.
                                    • 0
                                      Может не «вместо», а «вместе с»?
                                      • 0
                                        Пробовал и специально не упомянул в статье. Из всего функционала пригодилось только workon. На вкус и цвет, как говорится.
                                        • +1
                                          Дело привычки конечно. Мне лично удобно когда все «виртуалки» в одном месте и еще некоторые полезные «плюшки».
                                          • 0
                                            А меня вот как раз «все виртуалки в одном месте» не устроило :) Плюс стараюсь по минимому ставить софта. Жизнь без virtualenvwrapper возможна! Но он популярен, это да.
                                      • 0
                                        Не раз встречал импорт local_settings в начале settings.py, как описано в вашей статье. Честно говоря, не понимаю почему в начале, а не в конце. Суть в том, что вам может понадобится что-либо переопределить. Ну предположим, что в settings.py DEBUG=False и вот как ни крути local_settings, если он подключен в начале, дебаг вы не включите. А вот если делаете импорт в конце, то можете переопределять в local_settings всё, что душе угодно не трогая никак settings.py.
                                        • 0
                                          А DEBUG в settings.py не нужен. Не забудете потом, что и где вы переопределили?
                                          • 0
                                            Ну про DEBUG, наверное, плохой пример, но, например, можно к INSTALLED_APPS добавлять debug_toolbar или ещё чего там может пригодиться для тестирования. Можно просто в local_settings сделать INSTALLED_APPS += ('debug_toolbar', ) и всё. Мне кажется это удобным.
                                            • 0
                                              А можно в settings.py сделать
                                              if DEBUG:
                                                  INSTALLED_APPS += ...
                                              

                                              :)
                                              Можно и переопределять, конечно. Но тогда у вас одна опция одновременно хранится в разных файлах… А ведь явное лучше неявного.
                                              • 0
                                                Можно, да. Но костыль же. Тем более, если не определить в local_settings INSTALLED_APPS, будет фейл. Но, тут, конечно, можно ещё проверять существование свойства. :) Я на самом деле интересовался не спора ради, а просто, мало ли — есть какие-то причины на то. Но пока я останусь при своём мнении.
                                                • 0
                                                  Да тут в основном дело вкуса, никакой великой тайны нет :) Что в начале, что в конце — лишь бы нравилось
                                        • 0
                                          Старайтесь не писать {% url 'shop/product' id=product.id %}.

                                          Почему?
                                          • 0
                                            Промазал, ответил ниже
                                          • 0
                                            Если вы решите потом изменить способ адресации к объекту, то придётся везде заменять теги {% url %}. Пример из моей практики: раньше адреса были вида /shop/product/<id товара>/, но потом пришли сеошники и заставили привести адреса к такому: /shop/product/<slug товара>/. Пришлось во всём проекте заменять {% url 'shop/product' id=product.id %} на {% url 'shop/product' slug=product.slug %}. Если бы я использовал везде get_absolute_url, то было бы достаточно изменить лишь этот метод.
                                            • 0
                                              Но ведь это работает только с урлами, однозначно сопоставленными с моделями. Что с остальными делать?
                                              • 0
                                                Ничего не делать с ними. Хардкодить {% url 'contacts' %}, по-другому никак.
                                                Подробнее, зачем нужен get_absolute_url, хорошо написано на djangoproject.com.
                                                • 0
                                                  Ну вот и получается, что в одном месте url, в другом get_absolute_url. Нехорошо. Я за единообразие. Урлы надо менять не так часто, а читается лучше, когда написано везде одинаково.
                                            • +3
                                              1) Действительно ли inclusion tags работают быстрее {% include %}?
                                              2) Beautiful soup адово течет, лучше использовать lxml

                                              Дополню пост: есть замечательный апп django-devserver
                                              • 0
                                                1) К сожалению, со 100% уверенностью сказать не могу. В моей практике рендериться с inclusion tag быстрее.
                                                2) BS позволяет указать в качестве бэкенда lxml. По умолначию он использет дефолтный html.parser, который до версии питона 2.7.3 был корявым. Если вы имеете в виду, что течёт сам BS, то буду признателен за пруфлинк.
                                              • +1
                                                1. Обязательно включать кеширование шаблонов — оно доступно из коробки
                                                2. Для разбития сеттингсов есть хорошая батарейка github.com/2general/django-split-settings
                                                • 0
                                                  Кэширование — зверь, которого надо уметь готовить. Лучше так: в разработке кэш отключать вообще, но обязательно соблюдать завершающую стадию «а теперь включим кэш», в тестах проверять-проверять-проверять!, на продакте — включать с уверенностью что все оттестировано.
                                                  Часто видел трудноуловимые и трудно понимаемые ошибки при наличии кэша, включенного а ля «нам надо ускорить рендеринг! а давай кэширование включим».
                                                • 0
                                                  Вопрос тем кто уже освоился с джангой, гуру или те кто чувствую себя с джангой вполне комфортно.
                                                  Я вот только начал, или даже только начинаю ее изучать.
                                                  Меня сбивает столку туториалы для разные версий джанги, одни на 1.5, чуть меньше на 1.6, two scoops которые зацепил тоже 1.6, а тут еще все ждут мега-эпичный релиз 1.7 который позиционируется такимже основательным как 1.0.

                                                  Я то понимаю что надо пережовывать все, пахать и пахать. Сталкивался с подобным и в других отрослях.

                                                  Но может всетки поделитесь какимито советами, подбдрите, успокоите — вдруг че умного надумали со времен когда сами были новисами :))))
                                                  • 0
                                                    Я начинал с djangobook, которая уже в то время устарела :) Но именно она позволила понять, с чего вообще начать. Думаю, первые 7 глав позволят «въехать» в основные принципы и вполне себе сойдут за туториал, ну а дальше docs.djangoproject.com от начала и до конца.
                                                    У джанги есть и собственный туториал, но он в своё время мне не понравился.
                                                    • 0
                                                      даааа, от джангобука многие открещиваются как от нечистой — так что я как бы решил вабще не обращать на нее внимание. но конечно все же просмотрю :)

                                                      джанго туториал мне более или менее понравился, сам по себе он далек от полноценного, но через него думаю что я как минимум половину доков прочитал.

                                                      меня больше всего напрягает переход между фишками версий...1.5 1.6 1.7, что то добавлено, что то уже похоронено.
                                                  • 0
                                                    Просто начни писать что-нибудь простое. Там блог или чатик, или чонеть для сбора/обработки/отображения какой-неть простой статистики. Или что-то вроде To-Do списка, или сервис который считает сколько ты в день куришь. Главное — четко определи 1 единственную задачу для сервиса, и сделай его как получится. Когда после этого начнешь делать что-то второе, уже будешь ориентироваться в том что ты умеешь, что можно сделать лучше, что для этого надо изучить.

                                                    Тут как с английским языком — бессмысленно надеяться что ты походишь на курсы, поучишь словарь и внезапно заговоришь. Чтобы чему-то научиться, нужно это делать) Да, ты будешь косячить, будешь ошибаться, сделаешь кривую херню… а как тут иначе то?))

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.