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

Django: "Кто на сайте" ?


В одном из проектов понадобилось реализовать функцию "Кто на сайте". Средней продолжительности копания привели к модулю django-onlineiser.


$sudo easy install django-onlineuser


далее в settings.py:

#добавляем приложение в список используемых в проекте
INSTALLED_APPS += ('oblineuser')

#регистрируем его middleware в соответствующем списке
MIDDLEWARE_CLASSES += ('onlineuser.middleware.OnlineUserMiddleware')



в нужном html шаблоне подгружаем теги этого приложения и там же отображаем html-код этой приблуды

{% load onlineuser_tags %}
Кто здесь?
{%onlineinfos%}


У меня, чтобы заработало пришлось немного модифицировать код класса из middleware.py, обернув имеющийся там код в try except

class OnlineUserMiddleware:
    def process_request(self, request):
        try:
            user = request.user
            ip=request.META['REMOTE_ADDR']
            if user.is_authenticated():
                o, created = Online.objects.get_or_create(user=user)
                o.session_key = ip
            else:
                o, created = Online.objects.get_or_create(ident=ip)
                o.session_key = ip
            if not created:
                o.save()
        except:
            return


В результате видим простейшую статистику о гостях и онлайн юзерах.

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

Но чуть позже стало понятно 2 факта (помимо бага описанного выше):
1. сразу после логаута пользователя счетчик пользователей не уменьшается, а счетик гостей не увеличивается
2. после логина счетчик гостей не уменьшается, хотя счетчик пользователей увеличивается

Не совсем релевантно получается.

Я допилил это приложение с помощью сигналов о логине и логауте:


#этот код я добавил в middleware.py рассматриваемого приложения, хотя можно и любое другое удобное место 
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.dispatch import receiver
@receiver(user_logged_in)
def count_logins(sender,request,user,**kwargs):
    ip=request.META['REMOTE_ADDR']
    g=Online.objects.get(session_key=ip, user=None)
    if(g):
        g.delete() #удаляем гостя , после того , как он стал залогиненным юзером
    #создаем или обновляем запись в таблице приложения (этот код я скопипастил из имеющегося миддлваре-класса
    o, created = Online.objects.get_or_create(user=user) 
    o.session_key = ip
    if not created:
        o.save()    
 
@receiver(user_logged_out)
def count_logouts(sender,request,user,**kwargs):
    o=Online.objects.get(user=user) 
    o.delete() #сразу при логауте удаляем запись из таблицы



Если в ближайшее время не придумаю как более красиво это все дело улучшить, отпишу автору или посто форкну это приложение )

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

Django: Добавляем свои собственные проверки объектов в админку



Если понадобилось дополнить стандарные проверки, которые делает Django при создании, редактировании, удалении объектов через панель администратора, то существует несколько извращенных способов.
Опишу самый простой, наглядный и незатратный.

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

Например у вас есть таблица "Матч" , в полях которой есть две ссылки (ForeignKey) на таблицу "Команда". То есть играют две команды ) Сама с собой команда, понятно, играть не может. По крайней мере в тех командных видах спорта , о которых доводилось слышать )

Код проверки будет примерно прост (добавить метод в класс модели "Матч"):


    def clean(self):
        from django.core.exceptions import ValidationError
        if(self.team1.id == self.team2.id):
            raise ValidationError('Команда не может играть сама c собой.')            

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

p.s. сейчас в голову пришла мыслишка: будет ли вызываться этот метод, при сохранении объекта не через админку..хм.. надо будет матчасть почитать или выяснить эмпирически.

Если кто уже в курсе - буду признателен за ответ. Хотя кому это я, читателей пока у меня судя по всему нет )))))

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

Django: пишем свой менеджер модели


Недавно натолкнулся на часто востребованную операцию "получить следующее по дате" в моих Django-моделях. Встроенный менеджер objects имеет прекрасный метод latest. Симметричного ему , что-то типа next, я не нашел (может плохо искал).
И так как сейчас я как раз в самом разгаре прокачки django-скиллов (подобно новому персонажу в diablo , когда ну очень много нового и очень быстро происходят level-апы :), то я с радостью покопал тему расширения функционала менеджера и сделал нужный мне метод.



#models.py

#Менеджер (обратите внимание на базовый класс)
class EventManager(models.Manager):
    def next(self):
        import datetime
        try:
            return Event.objects.order_by('date').filter(date__gte=datetime.datetime.now())[0]
        except:
            return None
#Модель, для которой нужен метод next
class Event(models.Model):
    title = models.CharField()
    date  = models.DateTimeField()
    objects = MatchManager() #Явно указываем, кто нами рулит


После этого в соответствующем view я спокойно могу использовать:

Event.objects.next(), что гораздо удобнее и менее хрупко , чем конструирование фильтра.

Пара пояснений, любая модель, пронаследованная явно или опосредованно от models.Model имеет поле objects, значением которого является встроенный менеджер модели (объект класса models.Manager), который имеет кучу полезных методов. Мы создали класс-потомок и чуток его дополнили.
При желании можно параметризовать поле, по которому будет искать объекты метод next - подобно тому, как это делает latest. Но пока этого не требовалось, буду тратить время на что-то другое.
Что такое менеджер модели, во всяком случае, мне стало чуток понятнее =)

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");
}

среда, 24 августа 2011 г.

Django: ошибка сегментирования при выполнении syncdb

Сегодня натолкнулся на "корки" при попытке перестройки БД в одном из своих django-проектов после добавления нового приложения.
Вызов python manage.py suncdb приводил к "ошибке сегментирования" и аварийному завершению команды. Естественно, ни о каком обновлении БД речи в такой ситуации идти не может.
Проблема была в недостаточном количестве памяти =) Виртуальный сервачок , на котором случислась оказия слабоват и без возможности настройки свопа.

В общем , если кто натолкнется на что-то подобное, посмотрите, не отожрал ли кто-то всю память =)

пятница, 5 августа 2011 г.

Работа с cups в Perl

Если в системе используется служба печати cupsd, то в приложениях написанных на Perl можно довольно легко работать с установленными принтерами..

Вот простенький пример получения списка установленных принтеров в системе:


use Net::CUPS;
use Net::CUPS::Destination;
my $cups = Net::CUPS->new();
my @printers = $cups->getDestinations() or die "No printers";
foreach my $printer (@printers){
print 'Printer:',$printer->getName(),"\n";
}


Результат запуска на машине с двумя установленными принтерами:

Printer:SMB_HP1
Printer:SMB_HP2

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

Валидация полей форм при помощи jQuery

Для валидации web-форм освоил неплохой плагин для jQuery: Validation.

Необходимо на стороне клиента произвести первичную проверку корректности ввода данных.
Ниже пример простой формы регистрации.



<html>
<head>
<title>Регистрация</title>
<script src="/js/jquery.js" type="text/javascript"></script>
<script src="/js/jquery.validate.js" type="text/javascript"></script>
<script>
$(function(){

$("#reg-form").validate({ //находим форму по ее идентификатору
rules: { // здесь задаются правила валидации для каждого поля (ключ хеша - значение аттрибута name соответствующего input-а
pass1: {
required: true, //обязательное
minlength: 5, //ограничение на мин.длину
},
pass2: {
required: true,
minlength: 5,
equalTo: $("#id_pass1"), //проверяем, что второй пароль равен первому , используя jquery локатор для поиска нужного поля по его id
},
email: {
required: true,
email: true // проверка формата email (доступность домена не проверяется)
},
},
messages: { // здесь описываем какие сообщения об ошибках показывать после тех или иных проверок
email: {
required: 'Данное поле должно быть заполнено!',
email: 'Неправильный формат адреса e-mail',
},
pass1: {
required: 'Данное поле должно быть заполнено!',
minlength: 'Минимальная длина пароля: 5',
},
pass2: {
required: 'Данное поле должно быть заполнено!',
minlength: 'Минимальная длина пароля: 5',
equalTo: 'Введенные пароли не совпадают',
},
},
success: function(label) {//плагин-валидатор для сообщений об ошибках использует по-умолчанию элемент label
// здесь указываем код, который должен быть выполнен после успешной валидации поля
//удаляем класс error и добавляем класс ok
//плагин-валидатор по-умолчанию добавляет класс error для элементов, которые использует для сообщений об ошибках, поэтому сделайте поддержку соответствующих классов в своем css
label.html('OK').removeClass('error').addClass('ok');
}
});

});

</script>
</head>
<body>

<form action="" method="post" id="reg-form">
<label for="id_email">email:</label><input type="text" name="email" id="id_email">
<label for="id_pass1">пароль 1:</label><input type="password" name="pass1" id="id_pass1">
<label for="id_pass2">пароль 2:</label><input type="password" name="pass2" id="id_pass2">
<input type="submit" name="submit">
</form>

</body>



Плагин валидации поддерживает следующие правила проверки:

...
messages: {
required: "This field is required.",
remote: "Please fix this field.",
email: "Please enter a valid email address.",
url: "Please enter a valid URL.",
date: "Please enter a valid date.",
dateISO: "Please enter a valid date (ISO).",
number: "Please enter a valid number.",
digits: "Please enter only digits.",
creditcard: "Please enter a valid credit card number.",
equalTo: "Please enter the same value again.",
accept: "Please enter a value with a valid extension.",
maxlength: $.validator.format("Please enter no more than {0} characters."),
minlength: $.validator.format("Please enter at least {0} characters."),
rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
range: $.validator.format("Please enter a value between {0} and {1}."),
max: $.validator.format("Please enter a value less than or equal to {0}."),
min: $.validator.format("Please enter a value greater than or equal to {0}.")
...


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

p.s. ну и , конечно же, проверив данные на стороне клиента не забываем делать это и на стороне сервера.

воскресенье, 3 июля 2011 г.

Капча в django-приложении за пару-минут

Для того , чтобы максимально быстро "прикрутить" к своему django-приложению защиту от ботов в виде капчи, проще всего воспользоваться компонентом django-simple-captcha.

Инсталляция

sudo easy_install django_simple_captcha
либо
python setup.py install после скачивания нужного архива из списка доступных

Добавление поля к классу формы


...
from captcha.fields import CaptchaField
...
class FormWithCaptcha(forms.Form):
captcha = CaptchaField(label=_("Captcha:"))


settings.py

Необходимо добавить приложение captcha в список используемых в проекте. Для этого в файле settings.py проекта необходимо добавить соответствующую запись в INSTALLED_APPS:

INSTALLED_APPS = (
...
'captcha',
...
)

urls.py

У приложения djngo-simple-captcha есть свои url-ы, которые необходимо добавить в вашу общую схему url-ов в файле urls.py проекта:

...
urlpatterns += patterns('',
url(r'^captcha/', include('captcha.urls')),
)
...


Вот, собственно, и вся "базовая комплектация". При желании можно настроить под себя сообщения, внешний вид, стили - ведь это с точки зрения пользователя компонента - просто поле django-формы со всеми вытекающими..
Капча не "сложная" и , говорят, распознается с неприлично большой вероятностью. Но "на первых порах" довольно удачное решение, которое со временем и _по необходимости_ можно заменить на что-то более "бронированное". Например reCaptcha.
Отмечу, что успешно использую этот компонент в нескольких своих проектах (не обладающих, правда, суперпосещаемостью).

Intro...

Я - специалист по тестированию и контролю качества программного обеспечения. Довольно много лет уже за плечами в этой ипостаси. Работать и расти в этой сфере изначально я не планировал, но так получилось , что я нашел себя в "тестировании", где и продолжаю развиваться и добиваться неплохих результатов. Свои скромные успехи я связываю во многом с тем, что ни на минуту (начиная с первого курса института) не прекращал вращаться и в другой очень близкой и смежной с тестированием сфере - сфере разработки. У меня есть блог , целиком посвященный тестированию, где я время от времени делюсь опытом, забавными тематическими историями и т.д. и т.п. В моей деятельности как разработчика время от времени также случаются моменты, о которых не мешало бы рассказать, поделиться полученным опытом, дабы кто-то другой смог быстрее оправиться от удара граблей, на которые наступил ранее я... И чтобы не валить все в кучу и не смущать тестировщицкую аудиторию моего qa-блога я завел этот.. Посмотрим , что из этого получится )