28 дек. 2009 г.

Django compressor и фильтры.

В прошлом посте я писал о django compressor. Не ясно было почему компрессор ничего не делает (по умолчанию) с CSS-файлами (кроме очевидного – сборки в один файл и кеширования).

Присмотревшись внимательней компрессору, удалось выяснить, что есть в нем возможность использования csstidy, jsmin, yuicompressor и google closure для сжатия css и js файлов. Однако, процесс установки и настройки этих инструментов, не так уж и прост (по крайней мере yuicompressor и google closure).

jsmin ставить отдельно не нужно. Он входит в “стандартный набор” и даже включен по умолчанию в настройках django compressor опцией


COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter']


С csstidy у меня ничего не вышло. После установки csstidy на наш с Дениской debian и подключения фильтра:


COMPRESS_CSS_FILTERS = ['compressor.filters.csstidy.CSSTidyFilter']


словил 500 ошибку - фильтр не знал глобальную переменную css.


# django_compressor/compressor/filters/csstidy.py

...

17.         tmp_file.write(css)

...


YUI Compressor.

Сходил и забрал по ссылке http://yuilibrary.com/downloads/#yuicompressor.

Поставил openjdk-6-jre (советуют ставить последний, aptitude search "?provides(java-runtime)").


apt-get install openjdk-6-jre


Положил файл yuicompressor'а в /var/www/. Не спрашивайте зачем и почему – так в голову взбрело.

Добавил в settings.py моего django-проекта:


COMPRESS_JS_FILTERS = ['compressor.filters.yui.YUIJSFilter',]

COMPRESS_CSS_FILTERS = ['compressor.filters.yui.YUICSSFilter',]

COMPRESS_YUI_BINARY = 'java -jar /var/www/yuicompressor-2.4.2.jar'


Google closure пока не осилил. При, казалось бы правильных настройках:


COMPRESS_JS_FILTERS = ['compressor.filters.closure.ClosureCompilerFilter',]

COMPRESS_CSS_FILTERS = ['compressor.filters.closure.ClosureCompilerFilter',]

COMPRESS_CLOSURE_COMPILER_BINARY = 'java -jar /var/www/compiler.jar'


Попытка сжать css и js отпадает по таймауту. Возможно Вам известна причина? Напишите, я хочу сравнить производительность и результаты работы фильтров.

Возможно, кстати, кому-то удалось заставить работать csstidy?

Django compressor.

Давненько заглядывался на django-compress, но всякий раз пугался групп для css и js файлов. Дело в том, что процессе непрекращающегося deadline’а я постоянно дописываю стили в новые файлы или прям в шаблоны. Вечно потом пытаюсь разобраться в этом беспорядке, но как правило, прихожу к выводу, что некоторым стилям, или “приглашенным” js-скриптам в общем наборе делать нечего.

Пример: на сайте http://72arenda.ru/ есть форма заказа. Сейчас jquery + ui грузятся для всего сайта, но раньше было правильней – набор грузился только на странице бронирования, где эти скрипты и были востребованы. Подобных примеров в моих проектах много.

Альтернатива django-compress’у - Django compressor, использует другой подход. После подключения приложения ‘compressor’ в INSTALLED_APPS нужно прям в шаблонах заворачивать конструкции вызова или описания css или js файлов в специальные templatetag’и.

Пример с сайта:


{% load compress %}



{% compress css %}

<link rel="stylesheet" href="/media/css/one.css" type="text/css" charset="utf-8">

<style type="text/css">p { border:5px solid green;}</style>

<link rel="stylesheet" href="/media/css/two.css" type="text/css" charset="utf-8">

{% endcompress %}



{% compress js %}

<script src="/media/js/one.js" type="text/javascript" charset="utf-8"></script>

<script type="text/javascript" charset="utf-8">obj.value = "value";</script>

{% endcompress %}


Django compressor заменит стили на

<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" media="all" charset="utf-8">


а js на



<script type="text/javascript" src="/media/CACHE/js/3f33b9146e12.js" charset="utf-8"></script>


То есть, на кешированные, собранные в один файл, почищенные наборы стилей и скриптов.



UPD. Как выяснилось поздней, фильтры по умолчанию ничего не делают с содержимым файлов - не жмут, не чистят, не обфусцируют. Для активации фильтров нужно ставить yui-compressor или google closure, т.к. csstidy у меня работать отказался – жалуется, что не знает переменную css.



Compressor не делает ничего, если включен дебаг режим (DEBUG = True).



Компрессору нужен BeautifulSoup, так что проследите, чтоб он у вас был установлен.



К сожалению, пока не удалось разобраться в том, что является критерием “перестройки” кеш-файлов. В скриптах есть строка “Rebuilds the cache once a day if nothing has changed.” – Очевидно, перестройка происходит чаше, если что-то меняется (я проверял. каждый раз, когда я что-то менял, происходила “перестройка”).



Подобные инструменты нужно использовать в паре с методикам и описанными на странице http://code.google.com/p/django-compress/wiki/FarFutureExpires для достижения большего эффекта.



Успехов в освоении Django compressor и с наступающим!

Снипет для пользовательского контента в шаблонах

Очень простой и, наверняка, не слишком правильный способ вставлять html-хуки в шаблоны, содержимым которых может управлять пользователь:


# models.py:



class Snippet(models.Model):

    id          = models.AutoField(primary_key=True)

    title       = models.CharField(u'название', max_length=50)

    text        = models.TextField(u'текст', max_length=5000)

    template    = models.CharField(u'шаблон',   max_length=250, blank=True, null=True)

    def __unicode__(self): return u'%s (%s)'%(self.title, self.template)

    class Meta:

        verbose_name = u'снипет'

        verbose_name_plural = u'снипеты'



# tampletetags/realtor_tags.py:



@register.simple_tag

def get_snippet(title, **kwargs):

    from realtor.models import Snippet

    try:

        return Snippet.objects.get(title=title).text

    except:

        return u''

get_snippet.is_safe = True



# в шаблонах:



{% load realtor_tags %}



{% get_snippet 'realtor-top-left' %}


22 дек. 2009 г.

Django multiple database support - теперь поддерживает множественные соединения с базами данных

Весь твиттер буквально кипит от это новости – django теперь поддерживает множественные соединения с базами данных. Фича влита в транк и описана в документации.

В документации (по ссылке выше) описан процесс настройки и использования нескольких баз данных в джанго:


# in settings.py:



DATABASES = {

    'default': {

        'NAME''app_data',

        'BACKEND''django.db.backends.postgres_psycopg2',

        'USER''postgres_user',

        'PASSWORD''s3krit'

    },

    'users': {

        'NAME''user_data',

        'BACKEND''django.db.backends.mysql',

        'USER''mysql_user',

        'PASSWORD''priv4te'

    }

}



# in model lookups:



Author.objects.using('default').all()



# using('default') - явное указание БД для запроса. Если не указывать будет браться default


Подробней смотрите в документации! Поздравляю всех с этой новой фичей, мы давно ждали такой функциональности. Ура!

UPD. Этот пост как-то “сам собой” залез на хабр, и теперь собирает заслуженную (и не только) критику.

20 дек. 2009 г.

Django CMS – show_menu

UPD: Начиная с версии 2.1.0 beta загрузка меню тэгов происходит по новому (см. http://www.django-cms.org/news/2010/03/24/django-cms-210-beta-released/).

Сегодня попытаюсь рассказать о template tag’е show_menu в django-cms. Сам разбирался с ним методом научного тыка.

{% show_menu %}


Тэг генерирует меню навигации для текущей (выбранной, открытой) страницы.

Параметры

Show_menu принимает следующие необязательные параметры:
start_level - начальный уровень навигации,
end_level конечный уровень навигации,
extra_inactive – начальный уровень “дополнительной” навигации,
extra_active - конечный уровень “дополнительной” навигации
“Дополнительная” - это навигация по страницам, которые не являются прямыми потомками и предками текущей страницы.
Для этого тэга можно переопределить шаблон вывода, создав в шаблонах своего проекта свой шаблон cms/menu.html или передав в ещё один дополнительный параметр template. Пример:

{% show_menu 0 100 0 100 "my_new_cool_menu.html" %}


Играемся с уровнями навигации

Для тестирования работы тэга возьмем сайт-пример demo.concepter.ru, о котором я уже писал раньше. (UPD: сайта demo.concepter.ru больше нет). Итак, имеет исходное дерево сайта:
  • root 1
    • 1 level child (p, n)
      • 2 level child (p, n) of 1
      • 2 level child (p) of 1
    • 1 level child (p)
      • 2 level child (p, n) of 1 level child (p)
    • 1 level child
    • 1 level child (n)
  • root 2
    • 1 level child (p, n) of root 2
      • 2 level child (p, n) of 1 of root 2
    • 1 level child (p) of root 2
буквы в скобках – статусы. p – страница опубликована, n – “в навигации”.
На главной странице вызваны show_menu со следующими параметрами:

1. {% show_menu 0 100 100 100 %} - Вся навигация



2. {% show_menu 0 100 0 100 %} - Навигация по активному дереву



3. {% show_menu 0 100 0 1 %} - Навигация с одним дополнительным уровнем



4. {% show_menu 0 0 0 0 %} - Корневая навигация



5. {% show_menu 1 1 100 100 %} - Навигация первого уровня



6. {% show_menu 1 1 0 100 %} - Навигация по первому уровню (только активное дерево)



7. {% show_menu 0 1 100 100 %} - Навигация по перевому и второму уровню


Под активным деревом в данном случае понимаем дерево, в которое входит выбранная страница. Например, потомков root 1 активным деревом будет все, что ниже root 1 и root 1, включительно.

Независимые от активной страницы блоки навигации

Естественно, что вся навигация (1), корневая навигация (4), навигация первого уровня (5) и навигация 0-1 уровней (7) не будут меняться с изменением активной страницы (т.е. при переходе на новую страницу). Потому сразу покажу, как они выглядят для всех страниц:

Complete navigation

Level 0 navigation

Level 1 navigation

Level 0-1 navigation

Навигацию по активному дереву


{% show_menu 0 100 0 100 %}


Далее посмотрим какие страницы попадут в навигацию по активному дереву в зависимости от смены страницы.
1. root 1
2. root 2
Активное дерево развернуто, а не активное – нет. В навигации все страницы с параметрами страница “опубликована” и “в навигации”.

Навигация с одним дополнительным уровнем


{% show_menu 0 100 0 1 %}


1. root 1
2. root 2
3. 1 level child (p, n)
4. 1 level child (p, n) of root 2
В навигации есть все, что опубликовано и одновременно присутствует в навигации. Тэг честно вываливает 1 дополнительный уровень к текущей странице (потомки).

Навигация по первому уровню (только активное дерево)


{% show_menu 1 1 0 100 %}


Такой тип навигации можно использовать в паре с навигацией 0-го уровня для генерации двухуровневого меню на главной странице, как тут:
image
image
Естественно, там сделано не так, но для примера подойдет.
Далее пары “Активная страница” + содержимое навигации:
root 1 + 1 level child (p, n)
root 2 + 1 level child (p, n) of root 2
1 level child (p, n) + 1 level child (p, n)
2 level child (p, n) of 1 + 1 level child (p, n)
1 level child (p, n) of root 2 + 1 level child (p, n) of root 2
2 level child (p, n) of 1 of root 2 + 1 level child (p, n) of root 2
Очевидно, что в навигации данного типа меню присутствует первый уровень активного дерева. Как всегда, в неё попали только опубликованные страницы со статусом “в навигации”.

Резюме

С помощью данного тэга можно выстраивать навигацию любой сложности и вложенности, главное не запутаться в уровнях и активных деревьях.
Без параметров тэг инициализируется с 0 100 и 0 100, так что в некоторых случаях можно использовать просто {% show_menu %}.
Помимо show_menu в django-cms есть ещё show_menu_below_id, show_sub_menu и show_breadcrumb, использование которых может существенно сэкономить время и нервы. Хотя, какие там нервы? Django-cms – полнеищий fun.
Читайте все тоже самое, но на английском на странице http://www.django-cms.org/en/documentation/2.0/navigation/.

14 дек. 2009 г.

При не одном заполнении всех полей и соответственно правильном

Как правильно писать в тех. поддержку? Как правильно задавать вопросы? – все это вопросы не для обычного пользователя. В сущности, обычному пользователю все равно как это делать правильно. Такие знания нужны людям так, или иначе связанным с производством информационных систем, АРМ и многих других страшных слови аббревиатур. Обычный пользователь привык к мысли о том, что когнитивный разрыв между его пониманием систем, и пониманием систем программистом на столько велик, что программист скорей бог и экстрасенс, чем обычный человек. Зачем с богами разговаривать обычным языком? Ан нет - не боги горшки обжигают.

Сегодня на адрес тех. поддержки одного из наших проектов пришло письмо следующего содержания:

“День добрый,при не одном заполнении всех полей и соответственно правильном,невозможно пройти регистрацию,возможно с Вашем сайтом какие-то неполадки?”

У меня по русскому тройка, поэтому я не буду (не в праве) судить человека написавшего это. Да и не люблю я, когда люди начинают друг другу объяснять где запятые ставит, если суть ясна. Но в данном случае суть ускользает (от меня, по крайней мере).

Версия Дениса:

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

Вы что думаете? Случалось Вам получать подобные письма?

11 дек. 2009 г.

Django RSS для Яндекс новостей

Не секрет, что контриб syndication в django умеет делать RSS ленты, и делает это очень правильно. Но вот беда, Яндексу для его новостей нужны не просто RSS 2.0 ленты, а RSS 2.0 с добавлением тэга yandex:full-text, да ещё и в кодировке windows-1251.

Завелосипедить эту досадную оплошность можно следующим образом (естественно, не только им, но именно в споре рождается истина, так что жду комментарии):


# in urls:

   (r'^yandex.xml$''text.views.yandex_rss'),





# in views:



def yandex_rss(request):

    from news.models import New

    output = {

        'news': New.objects.filter(rubrics__pk=11).order_by('-published').select_related()[:15],

    }

    from django.template.loader import render_to_string

    rendered = render_to_string('yandex.html', output).encode('cp1251')

    return HttpResponse(rendered, mimetype='application/rss+xml; Charset=windows-1251')



# yandex.html:



<?xml version="1.0" encoding="windows-1251"?>

<rss version="2.0"

     xmlns="http://backend.userland.com/rss2"

     xmlns:yandex="http://news.yandex.ru">

    <channel>

        <title>вИшиме.ру</title>

        <link>http://vishime.ru/</link>

        <description>Последние новости с вИшиме.ру</description>

        <lastBuildDate>{% now "r" %}</lastBuildDate>

        <image>

            <url>http://vishime.ru/media/favicon.ico</url>

            <title>вИшиме.ру</title>

            <link>http://vishime.ru/</link>

        </image>

        {% for new in news %}

        <item>

            <title>{{ new.title }}</title>

            <link>http://vishime.ru{{ new.get_absolute_url }}</link>

            <description>{% if new.lead %}{{ new.lead|striptags|safe }}{% else %}{{ new.text.text|striptags|truncatewords:"20"|safe}}{% endif %}</description>

            <author>{% if new.author %}{{ new.author }}{% else %}вИшиме.ру{% endif %}</author>

            <category>{{ new.rubrics.get }}</category>

            <pubDate>{{ new.published|date:'r' }}</pubDate>

            <yandex:full-text>{{ new.text.text|striptags|safe }}</yandex:full-text>

        </item>

        {% endfor %}

    </channel>

</rss>


Пример с вИшиме.ру, но внимание (!), эту ленту ещё не приняли в яндекс новости, так что приведенные куски кода ничего не гарантируют.

Ещё раз, я понимаю, что нужно такие вещи писать по другому (раз уж есть syndication).

7 дек. 2009 г.

Мороз. Можно в школу не ходить?

Вчера сын не учился - из-за низкой температуры занятия для младших классов отменили. Я не слушаю радио, а в интернете информацию по данному вопросу мне найти не удалось. Дмитрий говорил, что ему нужна подобная функциональность на вИшиме.ру.
В итоге, из за всех этих факторов, а также нездорового любопытства, за пол часа наклепал ерунду, которой можно пользоваться для проверки сабжа.
Данные берутся из гисметео (только Тюмень), а правила отмены занятий взяты из статьи http://www.t-i.ru/article/8745/. Скрипт простой как мычание:

# -*- coding: utf-8 -*-

from django.shortcuts import render_to_response

from django.template import RequestContext

from django.utils import http

from datetime import datetime

import xml.etree.ElementTree as ET



def average(numbers):

    return sum(numbers) / len(numbers)



class Forecast(object):

    datetime = datetime(2009, 12, 7, 20)

    pressure_max = pressure_min = temperature_max = temperature_min = wind_max = \

    wind_direction = wind_min = relwet_max = relwet_min = heat_max = \

    heat_min = 0

    def avg(self):

        return average([self.temperature_max, self.temperature_min]), \

            average([self.wind_max, self.wind_min])

    def npu_1_4(self):

        '''

        Занятия для учеников 1-4-х классов отменяются, если температура воздуха

        опускается до -30 С и ниже при скорости ветра менее 2 метров в секунду.

        При скорости ветра 2 метра в секунду и более занятия отменяют уже при

        -25 градусах.

        '''


        avg_temp, avg_wind = self.avg()

        return (avg_wind >= 2 and avg_temp <= -25) or (avg_wind < 2 and avg_temp <= -30)



    def npu_1_9(self):

        '''

        Школьники 1-9-х классов могут остаться дома, если столбик термометра

        показывает -35 градусов и ниже, при этом скорость ветра должна быть

        менее 2 метров в секунду. А при большей его скорости занятия для

        учеников отменяются и при температуре -30 и ниже.

        '''


        avg_temp, avg_wind = self.avg()

        return (avg_wind >= 2 and avg_temp <= -30) or (avg_wind < 2 and avg_temp <= -35)

    def npu(self):

        '''

        Занятия отменяются по всей школе (1-11-е классы) при температуре

        наружного воздуха -40 градусов и ниже (при этом скорость ветра не должна

        превышать 2 метров в секунду).

        ??? превышать ??? - наверное, должна быть не меньше.

        '''


        avg_temp, avg_wind = self.avg()

        return (avg_wind >= 2 and avg_temp <= -40)



def index(request):

    h = http.urllib.urlopen('http://informer.gismeteo.ru/xml/28367_1.xml')

    xml = h.read()

    tree = ET.XML(xml)

    res = []

    for f in tree.getiterator('FORECAST'):

        obj = Forecast()

        obj.date = datetime(

            int(f.get('year')), int(f.get('month')), int(f.get('day')),

            int(f.get('hour'))

        )

        for x in f.getchildren():

            for y in x.items():

                setattr(obj, str(x.tag.lower())+'_'+y[0], int(y[1]))

        res.append(obj)

    output = {

        'res': res,

    }

    return render_to_response('index.html', output, context_instance=RequestContext(request))



Посмотреть результат можно тут: school.concepter.ru. Он не причесан и не информативен, но с функцией справляется – если занятий нет, он напишет.

UPD. Спасибо Земе за ликбез.

UPD2: Нужно смотреть не температуру (temperature_min и temperature_max), а температуру комфорта (heat_min и heat_max) – отменяют по ней.

Заочная встреча с создателем вИшиме.ru

Позавчера (в субботу) прокладывали ethernet-кабели в новом офисе ИГС. Из-за разных неожиданных мелочей провозились на 1.5 часа больше, чем я планировал. Не страшно, но в 15 часов ко мней домой приезжал Дмитрий Орлецкий - автор перспективного проекта о городе Ишиме - вИшиме.ru. Так уж вышло, что я в этом проекте тоже участвую.
Встретится у нас так и не получилось, но на мою жену он произвел положительное впечатление, что само по себе уже очень хорошо. Новое время стирает привычные границы. Раньше я не мог себе представить, что буду работать с каким-нибудь человеком 2 месяца, получать от него деньги, и не видеть его "в живую". Теперь - обычное дело.
вИшиме.ru написан на django и использует несколько весьма популярных pluggble апликух. Если хотите, я могу описать его архитектуру подробней, может быть даже выложу некоторые куски кода. Хотите?

Щедрый-мудрый Google или "1400 руб. на рекламу Вашего сайта в Google" от Google Apps

Всем привет!
Так уж вышло, что я пользуюсь Google Apps очень активно. Почти всем новым клиентам/друзьям и сотрудникам с собственными доменными именами я рекомендую завести аккаунт в Google Apps. При том я понимаю, что Google будет "владеть" информацией в почте и документах моих друзей, но по факту, стоимость владения таким решением перекрывает все эти призрачные недостатки.

Щедрый
Несколько дней назад Google решил вознаградить меня за пропаганду - прислал целых 6 купонов (видимо, по кол-ву зарегистрированных лично на меня Google Apps доменов) на рекламу сайта в Google AdWords.

Мудрый
Рекламные купоны Google рассылает почти всем, кто потенциально может быть связан с веб-мастерингом и рекламой в интернете. Естественно, Вы не можете просто взять 1400, которые Вам подарили, более того, их нельзя даже просто пустить в рекламу - нужно вложить немного своих денег, чтоб начать использовать купоны. Аккаунт, на котором будет использоваться купон должен быть младше 15 дней, что намекает на механизмы в Google, похожие на приемы работы советских чиновников - поднять кол-во рекламодателей за счет явно не новых пользователей сервиса. Но определенно, польза Googl'у от этого мероприятия есть - до письма я вообще не планировал рекламироваться, а теперь озабочен на столько, что ищу способы пристроить купоны даже тут, у себя в блоге. Видимо, сработали глубинные подсознательные русские механизмы поиска халявы.

Минус в том, что я не успею использовать их все до 31.12.2009. Может их продать? Или подарить кому-нибудь? Пишите в комменты, как мне поступить!


5 дек. 2009 г.

Завидная лаконичность


На сайте компании-клиента только что посетитель оставил удивительно лаконичный отзыв. Попробуйте произнести в слух на одном выдохе написанное, и Вам сразу станет весело и легко.

1 дек. 2009 г.

Тест для фриланс-работодателя

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

Мини-тест для работодателя:

Мне нужна:

  • душа работника (его таланты),
  • ум работника (его умения),
  • сердце работника (его заинтересованность в моем бизнесе),
  • результаты его работы (тупо результаты его работы).

Работа должна выполнятся:

  • в срок,
  • качественно,
  • так, как договорились.

Связь с работником:

  • работник должен быть всегда на связи (не пропадать),
  • должен быть на связи тогда, когда договорились,
  • никому ничего не должен, кроме того, о чем договорились.

* Между последними двумя ответами есть разница.

Итак, думаю если работодатель адекватен, или хотя бы претендует на адекватность, будет выбирать последние пункты. Почему? Потому что между работником и работодателем в здоровой обстановке должны быть трудовые/товарно-денежные отношения. Работодатель покупает часть жизни работника и платить ему за это компенсацию. При это он не покупает человека целиком, т.к. реальная стоимость даже ничего не умеющего нищеброда чрезвычайно высока.

Конечно спорно, должен ли работодатель платить за весь опыт работника накопленный им за всю жизнь. Однако, при покупке дивана Вы, как правило, не просите продавцов убрать из цены дивана стоимость работы грузчиков, доставивших диван в магазин (или стоимость работы завода. сделавшего диван).

Любовь за деньги не купишь, согласны? Тогда почему работодатель уверен, что если он будет хорошо платить своему работнику, то последний будет его за это любить? Бред. Дистанция, уважение, возможность реализовать свои таланты, помогут работодателю “влюбить” в себя работника (премии путь тоже остаются :) ).