[

суббота, 24 декабря 2011 г.

Django 1.4 alpha release

На днях писал о кастомных фильтрах в админке: http://readability-counts.blogspot.com/2011/12/django-admin-listfilter.html. Все оказалось не совсем так - я смотрел trunk репозитория. И вот вышла Django 1.4 alpha с новой фичей, описанной в моем предыдущем посте. Там еще много всего интересного - изучайте!

четверг, 22 декабря 2011 г.

Django Admin и кастомный фильтр для list_filter

Это только набросок - нужно все опробовать на живом коде.

"Вдруг" родилась задача выводить кастомный список значений для фильтра в админке. Поясню, имеем следующую модель:
class NewsPost(models.Model):
    """Новость"""

    authors = models.ManyToManyField(User, blank = True, null = True)

В админке она представлена так:
class NewsPostAdmin(admin.ModelAdmin):
    """Представление Новости в админке"""

    # фильтрация новостей по автору
    list_filter = ('authors',)

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        """Специальная фильтрация для списка допустимых авторов

        Данная фильтрация распространяется только на поле authors в
        форме редактирования новости в админке
        """

        if db_field.name == 'authors':
            kwargs['queryset'] = User.objects.filter(is_staff=True)
        return super(PostAdmin, self).formfield_for_manytomany(
            db_field, request, **kwargs)

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

Из документации по админке Django сказано, что в list_filter можно указывать только название поля:
Set list_filter to activate filters in the right sidebar of the change list page of the admin. This should be a list of field names, and each specified field should be either a BooleanFieldCharFieldDateFieldDateTimeFieldIntegerField or ForeignKey.
Fields in list_filter can also span relations using the __ lookup:
Но если посмотреть код в django.contrib.admin.views.main.ChangeList, метод get_filters() то увидим следующее:
...
filter_specs = []
if self.list_filter:
    for list_filter in self.list_filter:
        if callable(list_filter):
            # This is simply a custom list filter class.
            spec = list_filter(request, lookup_params,
                self.model, self.model_admin)
        else:
            ...

Оказывается в list_filter можно указать "какой-то" класс, экземпляр, которого будет создан для фильтрации. Классы эти можно увидеть вот тут:  django.contrib.admin.filters.

To be continued...

среда, 7 декабря 2011 г.

Скрипт-лоттерея

Для чего его использовать - ваша воля. Мы же его использовали для распределения смен технической поддержки на НГ.


# coding: utf-8
import random
import readline


def get_random_workers():
    """Start lottery"""

    # Список людей
    people = []

    # Заполним список участников
    while 1 == 1:
        name = raw_input("Введите имя сотрудника (пустая строка для завершения): ")
        name = name.strip()
        if name == '':
            break
        people.append(name)

    # проверим, что участники введены
    if not people:
        print("Нет участников для нашей лоттереи :( жаль.")
        sys.exit()

    # Случайный выбор из списка
    print("\nА сейчас... победители!")
    print("Нажмите Enter для получения следующего \"счастливчика\" или q для завершения")
    while 1 == 1:
        s = raw_input()
        if s == 'q':
            break
        random.seed()
        print(random.choice(people))

    # конец
    print("\nДо встречи перед следующими праздниками!")

if __name__ == '__main__':
    get_random_workers()

Баги:

  1. raw_input() отказывается читать кирилицу, хотя везде все UTF-8. можно переопределить sys.stdin - экземпляр StreamReader.

воскресенье, 21 августа 2011 г.

Утечка памяти в Django

Взято от сюда: http://stackoverflow.com/questions/1339293/python-memory-leak-debugging

Если вы пишите скрипт или management command для обслуживания сайта на Django, то можете наткнуться на то, что у вас "утекает" память. На самом деле это не совсем так - добавляется debug информация и логируются запросы. Чтобы этого избежать, сделайте следующее:

  1. выключите DEBUG в настройках;
  2. где-то в коде делайте db.reset_queries().

Если это не помогло, тогда да, ищите утечку.

понедельник, 1 августа 2011 г.

Поиск в Django (Haystack)

В большинстве проектов так или иначе требуется поиск. И тут, на мой взгляд, есть варианта действий:
  • написать свой поиск;
  • скормить сайт какой-нибудь поисковой системе;
  • воспользоваться готовым решением.
Первый вариант я бы отмел сразу - относительно просто до тех пор пока трафик на вашем ресурсе не пойдет вверх, тогда вам придется думать о хешировании, кешировании и прочих прелестях.

Скормить сайт поисковику, на мой взгляд, самый правильный вариант, но что делать если вы пишите внутреннее приложение без доступа извне?

Немного погуглив на эту тему, наткнулся на весьма интересный проект для Django: Haystack. То, что меня сразу "зацепило" а.к.а. возможности из коробки:
  • индексирование;
  • настраиваемые поисковые формы;
  • настраиваемые поисковые представления.
Еще одна вещь мне была поначалу непонятна: настраиваемые шаблоны для поиска. Как оказалось именно в этих шаблонах вся "фишка" haystack. Эти шаблоны никому никогда не показываются, но именно они индексируются.

Итак, давайте поподробнее посмотрим как это работает. Пусть имеется приложение blogs и следующие модели:

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __unicode__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    def __unicode__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateTimeField()
    mod_date = models.DateTimeField()
    author = models.ForeignKey(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __unicode__(self):
        return self.headline

Искать надо, скажем, записи (Entry), но пусть также находятся записи где поисковый запрос соответствует имени или email'у автора, а также названию или тегу блога. Т.е. поиск должен происходить не только по модели Entry, но и по Blog и Author. Вот тут нам и помогут те самые настраиваемые шаблоны для поиска.

Для этого создаем файл search_indexes.py в папке с приложением следующего содержания:
from haystack.indexes import *
from haystack import site

from blogs import models

class EntryIndex(RealTimeSearchIndex):
    # специальное поле - должно быть только одно в модели
    text = CharField(document=True, use_template=True)
    
    # поля из модели Entry
    headline = CharField(model_attr='headline')
    body_text = TextField(model_attr='body_text')  

    # поля модели Blog
    blog_name = CharField(model_attr='blog__name')
    blog_tagline = CharField(model_attr='blog__tagline')

    # поля модели Author
    author_name = CharField(model_attr='author__name')
    author_email = CharField(model_attr='author__email')

site.register(models.Entry, EntryIndex)

Теперь необходимо создать сам шаблон

вторник, 19 июля 2011 г.

Upd: Ajax отправка формы с файлом на jQuery

Вчерашний пост по поводу отправки формы с файлом через AJAX не завелся с IE. Беглый взгляд в консоль разработчика показал, что IE отправляет форму не через POST, а через GET. Не долго думая решил попробовать 2ю версию работы с jQuery Form.

Итак, форма:

<form id="ajax_form" method="post" action="/path/to/form/processing/" enctype="multipart/form-data">
<input type="text" name="some_text" />
<input type="file" name="some_file" />
<input type="submit" value="Post" />
</form>

Форма как форма. Отправка будет происходить, как с обычной формой, по нажатию на кнопку Post, которая теперь обычный submit. Для того, чтобы обернуть ее в iframe, надо в $(document).ready() инициализировать ajaxForm:

$(document).ready(function(){
    
    $('#ajax_form').ajaxForm({
        success: function(data) {
            alert(data);
        }
    });
});

Программно отправить форму можно так: $('#ajax_form').submit().

Проблема решена, но осталось 2 вопроса из-за чего не работает первый вариант в IE:

  1. неправильно работает плагин?
  2. проблемы с IE и какие? (в других браузерах все на ура)
Если глючит код плагина - невелика беда. Если же дело в IE, то может быть кто-нибудь уже с этим сталкивался и осознал в чем тут дело?

понедельник, 18 июля 2011 г.

Ajax отправка формы с файлом на jQuery

Не все так хорошо, как казалось - данный код не работает в IE! К сожалению, своего IE у меня нет - обычно "отбираю" ноут у кого-нибудь из родственников, но я таки переписал код по-другому (и не раз), и форма заработала. Новый пост тут.

Ни для кого не секрет, что через AJAX (XMLHttpRequest) нельзя отправить форму с файлом... в том смысле в каком все привыкли это делать обычным POST'ом. Весьма распространенный ход в этом случае - создать iframe, в нем форму, и отправить ее.

Безусловно это не так сложно, как кажется на первый взгляд, однако, зачем изобретать велосипед? Погуглив вчера нашел весьма удобный в обращении плагин jQuery Form.

Допустим, вы создаете динамически (в оверлее) форму следующего содержания:

<form id="ajax_form" enctype="multipart/form-data" onsubmit="return false;">
<input type="text" name="some_text" /><br />
<input type="file" name="some_file" /><br />
<button onclick="ajaxPostForm()">Post</button>
</form>

и хотите, чтобы по нажатию на Post форма отправлялась через AJAX. Используя jQuery Form это можно сделать несколькими способами, я покажу наиболее близкий к $.post():

function ajaxPostForm() {
    // получим форму
    var form = $('#ajax_form');
    // обернем ее в IFrame - поступаем как при использовании практически любого jQuery плагина
    form.ajaxForm();
    // отправим форму - большинство параметров как у $.ajax()
    form.ajaxSubmit({
        url: '/path/to/form/processing/',
        success: function(data) {
            alert(data);
        }
    });
}

И some_text и some_file ушли на сервер. Ура!

суббота, 16 июля 2011 г.

Множественный вход Google

Пару дней назад, придя на работу и открыв браузер, увидел, что gmail.com аккаунт разлогинен... Ничего страшного, бывает! Ввожу логин и пароль - вошел. Ок, теперь корпоративная почта (она тоже на gmail.com), ввожу логин/пароль и... я уже вошел, сообщил мне Google! Да не просто, а еще и предложил продолжить или отменить новый вход. Непочуяв подвоха я нажал продолжить - вошел.

Дело пошло, можно продолжать утренний моцион - прочтение почты. И тут я, что меня "обманули" - выбросили из первого аккаунта! Понимая, что так не может быть и где-то должна быть настройка, я полез ее искать в Google, куда же еще!?:) Вот она заветная ссылка на справку.

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

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

PS: хочется сказать, что это первая фишка от Google, которая вывела меня из себя!

пятница, 15 июля 2011 г.

SyntaxHighlighter 3 и с чем его едят

Вот все хорошо с Blogger'ом, за исключением подсветки кода:) Немного погуглив нашел такую вещь как SyntaxHighlighter, а там и статью о интеграции его в Blogger (первая в списке;)).

Вот что из этого вышло:
parents, babies = (1, 1)
while babies < 100:
    print 'This generation has %d babies' % babies
    parents, babies = (babies, parents + babies)

К сожалению, не получилось "завести" SH с автозагрузкой кистей. если будет время еще разберусь.

понедельник, 11 июля 2011 г.

Программное создание Dojo формы из JSON

Понедельник утро... самое время собрать мозги в кучу!

На этот раз расскажу об одной интересной наработке - как я программно создаю форму Dojo из JSON. Задумав сие я стал бродить по интернету и наткнулся на весьма интересный пост. Он то и послужил основой для моего класса.

Прежде всего, для затравки, покажу как выводится форма:

dojo.xhrGet({
    url: '/path/to/form/json/',
    handleAs: 'json',
    load: function(data) {
        var form = new JSONForm(data);
        dojo.byId('container').innerHTML = form.generate();
    }
});

Сгенерированный HTML код формы будет размещен в контейнере c ID container. Просто и быстро.

JSON код (основной) формы в моем случае выглядит так:
{ 

  "id" : "form_id", /* ID формы */
  "params" :  /* Параметры формы */
    { 
      "action" : "/path/to/form/post/",
      "encType" : "multipart/form-data",
      "method" : ""
    },
  "fields" : /* Массив полей */
    [ 
      { 
        "name" : "field_name", /* Название поля в формы */
        "widget" : "TextInput", /* Тип виджета */
        "params" : /* Параметры, передаются конструктору виджета Dojo */
          { 
            "id" : "field1_id", /* ID виджета Dojo (domeNode) */
            "label" : "Подпись:", /* Подпись поля */
            "name" : "widget_name" /* Название HTML элемента формы */
            /* любые другие параметры для виджета */
          }
      }
    ]
}

Тут стоит рассказать о нескольких моментах:

  1. в отличии поста на stackoverflow.org, я передаю не класс Dojo виджета в параметре widget, а, вообще говоря, ключ по которому мой класс JSONForm делает отображение на класс Dojo виджета
  2. у поля есть два свойства name: одно непосредственно в объекте field, а другое в объекте params. Дело в том, что форма на сервере (а я использую Django forms) помимо названия полей имеет еще и префикс для того, чтобы можно было скомбинировать несколько форм с одинаковыми именами полей под одним тегом формы. Так что field.name - это имя поля в форме, а field.params.name - имя поля в HTML коде.

Теперь, когда понятно в каком виде приходит форма с сервера и как она выводится, перейдем к классу JSONForm. Безусловно он написан с помощью классов Dojo.

dojo.declare('JSONForm', null, {
    
    /*
     * Отображение типов виджетов, пришедших с сервера на Dojo воджеты
     */
    _widget2dijit: {
        'Textarea': dijit.form.Textarea,
        'PasswordInput': dijit.form.ValidationTextBox,
        ...
    },

    /*
     * Конструктор
     */
    constructor: function(/* JSON Form Object */json) {
        // параметры формы
        this.id = json.id;
        this.params = json.params;
        this.params['id'] = this.id; // надо для создания виджета

        // создадим форму
        this.form = new dijit.form.Form(this.params);
        
        // поля формы
        this.fields = [];
        // поля формы с ключем по имени поля
        this.fields_dict = {};

        // создадим поля
        dojo.forEach(json.fields, function(item, index){
            this.fields[index] = this._get_dijit(item);
            this.fields_dict[item.name] = this.fields[index];
        }, this);
    },

    /*
     * Получим класс Dijit'a
     */
    _get_dijit: function(field) {
        var widget = this._widgit2dijit[field.widget];
        return widget(field.params);
    },

    /*
     * Генерация формы
     */
    generate: function() {
        // получим тег формы
        var form = this.form.domNode;

        // добавим поля
        dojo.forEach(this.fields, function(item, index){
            dojo.place(item.domNode, form);
        }, this);

        // добавим кнопку submit
        var submit = new dijit.form.Button({
            type: "submit",
            label: "Отправить"
        });
        dojo.place(submit.domNode, form);
        
        // вернем HTML нашей формы
        return form.domNode.innerHTML;
    }
});

Этого достаточно для создания и простейшего отображения полей.

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

  1. Шаблоны с _Templated  для большего контроля отображения формы.
  2. AJAX валидация полей - следующее чем хочу поделиться.
  3. AJAX отправка формы.
Если на стороне клиента есть необходимость отлавливать события полей, то это легко можно сделать с помощью dojo.connect (или dijit.connect):

dojo.xhrGet({
    url: '/path/to/form/json/',
    handleAs: 'json',
    load: function(data) {
        // создадим форму
        var form = new JSONForm(data);

        // присоединимся к событию onFocus поля "field1"
        form.fields_dict["field1"].connect(form.fields_dict["field1"],
            "onFocus", function(){ alert('Фокус!'); });

        // отобразим форму
        dojo.byId('container').innerHTML = form.generate();
    }
});

Ну как? надеюсь был полезен.

пятница, 8 июля 2011 г.

Перегрузка функций закодированых ionCube и т.п. в PHP 5.3

Хотел порадовать вас Django и Dojo, но волею судьбы пришлось возиться с PHP.

Итак, проблема: имеет файл file.php закодированный, например, ionCube. Надо узнать какие функции есть в нем и перегрузить какую-либо.

1. С помощью get_defined_functions() и Reflection или Doqumentor смотрим что есть в файле.

2. Далее, переименовываем файл в base.file.php и создаем file.php со следующим содержимым:

<?php
namespace testns;
require("base.file.php");
?>

3. Все хорошо, можем перегрузить, например, функцию test_func():

<?php
namespace testns;
require("base.file.php");

function test_func()
{
}
?>

Внутри файла вызов test_func() вызовет нашу новую функцию. Старую можно вызвать так: \test_func().

Вот, собственно, и все. За одним исключением: Если в файле base.file.php есть классы, то их нужно явно импортировать в текущий namespace. Например для класса TestClass это будет выглядеть так:

<?php
namespace testns;
require("base.file.php");

use \TestClass as TestClass;

function test_func()
{
}
?>

Надеюсь это кому-то поможет!

среда, 6 июля 2011 г.

Всем привет!

Долго ли, коротко ли, но я дошел до собственного блога!

Зовут меня Александр, живу и работаю в Санкт-Петербурге. Люблю жену, ребенка, люблю спорт (хоть сейчас и не частый гость в додзе). Люблю веб и все что с этим связано, но особенно Python и Django! и хочу "поделиться" своей любовью с другими. Главное чтобы запал не пропал, но как говорится "надо себя заставить"! В моих ближайших планах несколько статей про Django и Dojo, и обработке форм.

За сим, откланяюсь.
И до скорого!