воскресенье, 27 ноября 2011 г.

Django: лимитированные запросы - не забываем про исключения


Для того, чтобы составить лимитированный запрос средствами Django используется нотация доступа к элементам массива.
Например, нам нужно получить ближайшее событие. Для этого список событий надо осортировать, выбрать только те, дата которых больше текущей и вернуть событие с наименьшей датой.


event = Event.objects.order_by('date').filter(date__gte=datetime.datetime.now())[0]



[0] - как раз и отвечает за лимитирование.

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


try:
  event = Event.objects.order_by('date').filter(date__gte=datetime.datetime.now())[0]
except:
  event = None


понедельник, 21 ноября 2011 г.

Django: кастомизируем форму комментариев от django-comments


DJANGO-фреймворк включает в себя встроенный механизм комментирования контента произвольного типа. Довольно удобная штука. Имеет даже встроенные механизмы борьбы со спамом, модерацией, удобный интерфейс администрирования... Но, как и любое коробочное решение , рано или поздно требует доводки.
В моем случае не потребовалось писать кастомное приложение с наследованием классов встроенных комментариев - надо было лишь изменить интерфейс формы комментария.
Вот, что надо сделать, чтобы отрисовать свою форму:

1. включить шаблонный тег: {%load comments%} в самую "верхушку" шаблона страницы, откуда будет приниматься комментарий пользователя
2. в том месте, где будет показываться форма , поместить шаблонный тег: {% render_comment_form for entry%}
3. в основной директории для хранения шаблонов создать подкаталог 'comments' и поместить в него файл form.html , который будет, например с таким содержанием:

{% if user.is_authenticated %}
<form action="/comments/post/" method="post">
{%csrf_token%}
<textarea name="comment" id="id_comment" rows="2" style="width: 90%;"></textarea>
<input type="submit" name="post" value="Add">
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
</form>
{% else %}
<p class="red big">авторизуйтесь!<br>только зарегистрированные пользователи могут оставлять комментарии.</p>
{% endif %}

Инструкция 'if user.is_authenticated' необязательна - Вы можете и разрешить комментарии для неавторизованных пользователей (вы можете даже вообще не использовать встроенный механизм авторизации =)

Тег {%csrf_token%} также не имеет никакого отношения к топику, но для полноты картины оставил и его.

Далее идет html-код моей формы, в которой от кучи "коробочных" полей , я оставил только текст комментария. Остальное мне не нужно было. Автор комментария (а это будет id - авторизованного пользователя) проставится в соответствующее поле самим приложением comments.

Шаблонный цикл, вставляющий некие hidden-поля ОБЯЗАТЕЛЕН - без этих самых полей засабмитить форму не получится. Для понимания: в скрытых полях , которые я добавил таким образом, передаются тип контента (ContentType), идентификатор объекта (ForeignKey), которому относится комментарий и еще поле для защиты от спама, которое не-бот никогда не увидит и не заполнит, а вот тупой бот может и перемудрить, отправив заполненным и его =)

С удалением полей просто. Но следует понимать, что ДОБАВЛЯЯ свои собственные поля, надо не забыить НАУЧИТЬ понимать и принимать эти поля само приложение. Правильнее всего в этом случае написать свое кастомное приложение для комментирования. Об этом неплохо написано на djangoproject.com - копипастить уже написанное смысла не вижу :)

пятница, 18 ноября 2011 г.

Django: превращаем RawQuerySet в JSON-ответ


Всем хорош Djanfo-фреймворк. Да только вот ORM далеко не самый удобный. Хорошо еще что появилась возможность выполнять Raw-запросы. Например, у менеджера objects() есть метод raw(), с помощью которого можно сконструировать произвольный select.
Но радость от такой возможности омрачается тем, что у RawQuerySet, который получаем на выходе из raw() , нет привычных методов count(), values(), values_list() и тому подобных, так любезно предоставляемых нам классом QuerySet.
Я натолкнулся на это, когда мне потребовалось сформировать HTTP-ответ в формате JSON, содержащий результат довольно хитрой выборки из БД...В итоге список словарей пришлось конструировать вручную.

Примерно так это выглядит:

from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson

def sample_view(request):
    mymodels = MyModels.objects.raw("select * from myapp_mymodel")
    mlist = list()
    for m in mymodels:
        mlist.append(
         {'id': m.id,'title': m.title,'start': m.date,'end':m.date,}
    )
    data = simplejson.dumps(mlist, cls=DjangoJSONEncoder)
    return HttpResponse(data)

среда, 16 ноября 2011 г.

django: контроль операций INSERT, UPDATE, DELETE

Механизм моделей Django позволяет контролировать операции вставки , изменения и удаления объектов из БД двумя способами (не считая триггеров непосредственно в БД):

Способ первый: Cигналы

Модуль django.dj.models содержит основной набор сигналов, связанных с операциями с БД: pre_save, ost_save, pre_delete, post_delete.
Довольно подробно работа с сигналами описана здесь
Мне, как еще не до конца искушенному django_разработчику для решения реальных задач сигналы пока не приходилось использовать. Поэтому какими-либо тонкостями работы с ними делиться было бы глупо))
А вот второй способ использую довольно активно.

Способ второй: переопределение методов Save, Delete класса Models в классах-наследниках.

А именно такими классами и описываются модели Django-приложений.
- Почему нет отдельного метода для update?
- Потому что для этого используется save.
Сигнатура метода save содержит 1 параметр, которым должен быть self. То есть ссылка на сам объект. У объекта, который еще ни разу не сохранялся в БД поле id пустое, а у того, который был считан из БД и предназначен для обновления (update) идентификатор уже инициализирован. Соответственно, простейшая проверка внутри обработчика сигнала позволяет разделить код обработки save от кода обработки update.

понедельник, 31 октября 2011 г.

django: пример Ajax-аутентификации


В данной статье описывается пример реализации ajax-аутентификации в django.
На стороне клиента используется js-фреймворк Dojo. Ничто не мешает взять любой другой, например jquery.
Итак приступим:
1. определяем специальный view для обработки ajax-запросов (используем только POST)
#views.py
from django.contrib.auth.forms import AuthenticationForm
@csrf_protect #предварительно в settings.py надо подключить соответствующее moddleware
@never_cache
def ajax_login(request, template_name='registration/login.html',
          redirect_field_name=REDIRECT_FIELD_NAME,
          authentication_form=AuthenticationForm,
          current_app=None, extra_context=None):
    logger = logging.getLogger(__name__)
    manipulator = AuthenticationForm(request)
    redirect_to = request.REQUEST.get(redirect_field_name, '')
    if request.method == "POST":
        form = authentication_form(data=request.POST)
        if form.is_valid():
            netloc = urlparse.urlparse(redirect_to)[1]
            auth_login(request, form.get_user())
            if request.session.test_cookie_worked():
                request.session.delete_test_cookie()
            logger.error('SUCCESS')
            return HttpResponse(simplejson.dumps("OK"))
        else:
            logger.error('FAILED')
            return HttpResponse(simplejson.dumps("FAIL"))
    else:
        return HttpResponseRedirect('/');
        form = authentication_form(request)
    request.session.set_test_cookie()
    current_site = get_current_site(request)
    context = {
        'form': form,
        redirect_field_name: redirect_to,
        'site': current_site,
        'site_name': current_site.name,
    }
    context.update(extra_context or {})
    return render_to_response(template_name, context,
                              context_instance=RequestContext(request, current_app=current_app))

2. вставим в соответствие конкретному url определенную выше view

from my_auth_app.views import ajax_login

urlpatterns += patterns('',
    url(r'^login/ajax', ajax_login),
)

3. Описываем шаблон формы и клиентскую js-часть:

<script>
    var sendLoginButton;
    function sendLogin(){
      dojo.xhrPost({
        url: '/login/ajax/',
        load:sendLoginCallback,
        form: dojo.byId('loginForm'),
        handleAs: "json",
        error: function(response,ioArgs) {
         dojo.byId("messagestatus").innerHTML = "Попробуйте еще раз!";
         dojo.byId("loginForm").style.display = "yes";
      });
      dojo.byId("loginForm").style.display = "none";
      dojo.byId("messagestatus").innerHTML = "Проверка...";
    }

    function sendLoginCallback(type, data, evt) {
      console.error(type);
      console.error(data);
      console.error(evt);
      if (type == 'FAIL'){
         dojo.byId("messagestatus").innerHTML = "Попробуйте еще раз!";
         dojo.byId("loginForm").style.display = "";
      }
      else {
        if (data == "false") {
          dojo.byId("loginForm").style.display = "";
          dojo.byId("messagestatus").innerHTML = "Your password or username is invalid";
          dojo.disconnect(sendLoginButton, 'onclick', 'sendLogin');
          var anim = dojo.lfx.html.highlight("messagestatus", [255, 0, 0], 200).play(100);
          dojo.connect(anim, "onEnd", function() {  dojo.connect(sendLoginButton, 'onclick', 'sendLogin'); });

        }
        else {
           window.location="/";
        }
      }
    }
    function init(){
      console.log(document.cookie);
      sendLoginButton = dojo.byId('loginButton');
      var loginForm = dojo.byId('loginForm');
      if(loginForm){
          loginForm.onsubmit = function() { return false; }
          dojo.connect(sendLoginButton, 'onclick', 'sendLogin');
      }
   }
   dojo.addOnLoad(init);
</script>
<div id="messagestatus"></div>
{% if user.is_authenticated %}
<p id="welcome">Хелло, {{user.username}}</p>
{% else %}
  <form  method="post" id="loginForm" class="blocks">
  {%if form.errors %}
    Неудачный вход.
  {% endif %}
     <label for="id_user">Ник:</label> {{ form.username }}
   Ошибка:{{form.username.errors}}
Пояснение:{{ form.username.help_text }}
  <label for="id_password">Пароль:</label> {{ form.password }}
 Ошибка:{{form.password.errors }}
Пояснение:{{ form.password.help_text }}
<input id="loginButton" class="large button" type="submit" value="ВОЙТИ" />
  </form>
{% endif %}

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

perl: несколько проблем установкой GD и способы их решения

Сегодня потребовалась установка CPAN-модуля GD.pm на один из серверов.
При установке возникла ошибка:

perl -MCPAN -e 'install "GD"'

we get the following fatal error message:
**UNRECOVERABLE ERROR**
Could not find gdlib-config in the search path. Please install libgd
2.0.28 or higher.
If you want to try to compile anyway, please rerun this script with
the option --ignore_missing_gd. 
...

Ок, пытаюсь ставить libgd из репозитория (Debian 5 Lenny):

$ apt-get install libgd2-xpm
$ apt-get install libgd2-xpm-dev

Пробую ставить опять perl-модуль - та же ошибка.

Раскопки и гуглеж позволили выяснить, что дело в том, что в штатный пакет не входит скрипт gdlib-config.

Решить проблему можно , поставив libgd самостоятельно, взяв исходники, например, отсюда

$ wget http://www.boutell.com/gd/http/gd-2.0.28.tar.gz
$ tar xvf gd-2.0.28.tar.gz
$ cd gd-2.0.28
$ ./configure --prefix=/usr
$ make
$ sudo make install

После этого повторяем попытку поставить GD.pm.

Может возникнуть еще одна проблема:
Manifying blib/man3/GD::Polygon.3pm
  LDS/GD-2.46.tar.gz
  /usr/bin/make -- OK
Warning (usually harmless): 'YAML' not installed, will not store persistent state
Running make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/GD..........Testing using png support.
t/GD..........FAILED test 10                                                 
        Failed 1/12 tests, 91.67% okay
t/Polyline....ok                                                             
Failed Test Stat Wstat Total Fail  List of Failed
-------------------------------------------------------------------------------
t/GD.t                    12    1  10
Failed 1/2 test scripts. 1/13 subtests failed.
Files=2, Tests=13,  1 wallclock secs ( 0.12 cusr +  0.02 csys =  0.14 CPU)
Failed 1/2 test programs. 1/13 subtests failed.
make: *** [test_dynamic] Ошибка 255
  LDS/GD-2.46.tar.gz
  /usr/bin/make test -- NOT OK
//hint// to see the cpan-testers results for installing this module, try:
  reports LDS/GD-2.46.tar.gz
Warning (usually harmless): 'YAML' not installed, will not store persistent state
Running make install
  make test had returned bad status, won't install without force


Подозрение на отсутствие библиотеки libpng. Ставим ее:
# apt-get install libpng3 libpng3-dev

Опять пробуем ставить perl-модуль - опять то же самое.

Удаляем libgd, конфигурим , собираем и ставим ее заново.Делаем так, потому что изначально у нас не было libpng и , соответственно, libgd была собрана без ее поддержки. Надо пересобрать:

# в директории с исходниками GD
$ make clean 
$ ./configure --prefix=/usr
$ make 
$ sudo make install

После этого GD.pm должен установиться успешно.

вторник, 27 сентября 2011 г.

Django: "Accessor for field... clashes with related field" error after syncdb


If you see error message
Accessor for field ... clashes with related field
after syncdb command then likely problem in that one of your tables reffers another tables by 2 (or more) of its fields..

Example:


class User(models.Model):
     name = models.TextField()
     birth_date = models.DateTimeField()

class Message(models.Model):
     from = models.ForeignKey(User)
     to = models.ForeignKey(User)
     text = models.TextField()
#table 'Messages' has two links to table 'User'

Django could resolve this issue by defaulf by adding incremental postfix to accessor names for example.. But I have never hear about such django feature :)

To cut a long story short.. problem is solved by adding extra attributes for every referencing field:


class User(models.Model):
     name = models.TextField()
     birth_date = models.DateTimeField()

class Message(models.Model):
     from = models.ForeignKey(User,related_name="message_sender")
     to = models.ForeignKey(User,related_name="message_reciever")
     text = models.TextField()

syncdb shouldnt show accessor errors after that..

Django: ошибка "Accessor for field ... clashes with related field"


Если в результате syncdb вашей django-схемы вы видите сообщение :
Accessor for field ... clashes with related field
то скорее всего дело в том, что одна из ваших таблиц двумя своими полями ссылается на другую таблицу схемы.

Пример:


class User(models.Model):
     name = models.TextField()
     birth_date = models.DateTimeField()

class Message(models.Model):
     from = models.ForeignKey(User)
     to = models.ForeignKey(User)
     text = models.TextField()
#то есть таблица Сообщение имеет два внешних ключа на таблицу Пользователь

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

Проблема же решается дополнительным атрибутом для ссылающихся полей:



class User(models.Model):
     name = models.TextField()
     birth_date = models.DateTimeField()

class Message(models.Model):
     from = models.ForeignKey(User,related_name="message_sender")
     to = models.ForeignKey(User,related_name="message_reciever")
     text = models.TextField()

После этого syncdb должно выполниться без проблем (только, конечно, нет других ошибок =)

четверг, 15 сентября 2011 г.

highlight.js в blogspot-дневниках


Решил сообщения своего блога сделать более наглядными добавив подсветку фрагментов кода.
Перебрав несколько вариантов реализаций , остановился на highlight.js.
Поддерживает кучу языков программирования и даже специально некоторые фреймворки. Например Django.
Легко подключается, особенно, если ваш блог располагается на сервере, к которому есть полный доступ.
Для того чтобы подключить подсветку к blogger тоже не надо особо извращаться:

1. Перейти в html настройки текущего используемого шаблона
2. в секции <head> подключить js-исходники, которые, к слову, хостятся в том числе и на yandex и могут быть подключены прямо оттуда...
3. в той же секции <head> подключить нужную CSS (существует несколько вариантов тем, демки которых доступны на сайте разработчика)
4. в пределах <body> ( либо в исходном коде поста, либо , опять же, в каркасе шаблона ) инициализировать эту подключенную библиотеку
5. оформить код, требующий подсветки в теги <pre> и <code> , указав нужный язык в аттрибуте class

Вот собственно и все.

Примерно так это должно быть:

<pre><code class="html">
<head>
<link href='http://yandex.st/highlightjs/6.0/styles/vs.min.css' rel='stylesheet'/> 
<script src='http://yandex.st/highlightjs/6.0/highlight.min.js'></script>
</head>
<body>
<script>hljs.initHighlightingOnLoad();</script>
</body>
</code>
</pre>

Ну и без примера никуда. Вот, пожалуйста, для Си:


#include 'stdlib.h';
int main(){
   /*any comments*/
   printf("%s","Hello World!");
   return 0;
}

django: Работаем с параметрами HTTP-запроса в шаблонах


Часто возникает необходимость узнать, к примеру, текущий URL при отрисовке шаблона. Например, простейшая вещь - добавить на те или иные страницы сайта Facebook-овский лайк. Разработчики этой соц.сети предлагают нам следующий код для вставки:



Как видим, url статично. Вставляем код в шаблон и вместо url подставляем {{request.get_full_path}}. Отображаем обновленную страницу - результата пока нет. Дело в том, что мы забыли добавить в settings.py request-препроцессор контекста.
 
TEMPLATE_CONTEXT_PROCESSORS = ( 
#здесь перечислены какие-то другие процессоры     
'django.core.context_processors.request', 
#здесь перечислены какие-то другие процессоры 
) 
 

После этого при отображении страницы с вставленным кодом будет отображаться фейсбуковская лайк-кнопка. Отмечу, что в приведенном примере используется функция get_full_path, которая возвращает относительный url со всеми GET-параметрами запроса. Есть еще функция path - возвращает относительный url без имени домена и без GET-параметров . Узнать имя хоста / домен позволяет функция get_host. Таким образом, для получения полного абсолютного URL страницы в шаблоне необходимо добавить код:
  {{request.get_host}} {{request.get_full_path}} 

специально указывать слеш после хоста не нужно, так как *path - функции возвращают url уже с лидирующим слешем.


int main(){
 printf("%s","Hello world");
}