среда, 18 апреля 2012 г.

По мотивам собеседований

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

1. Python

Какие бывают типы данных в Python?
Чем отличается list от tuple?
Что такое immutable объекты? Какие Вы знаете? В чем особенность?
Что такое dict? Что может являться ключом? Что не может являться ключом? Каким образом нужно изменить класс что бы он смог быть ключом?

Что такое декораторы?
В какой момент вызывается декоратор?
Может ли декоратор НЕ возвращать декорируемую им функцию?
Можно ли декорировать класс?
Может ли класс быть декоратором?

Что такое новые классы? чем отличаются от старых классов?
Чем отличается @staticmethod от @classmethod? 
Что происходит при обращении к property b экземпляра класса А? Как ищутся properties/methods класса?
Что происходит если property не найдено?
Что такое MRO? алгоритм решения проблемы?
Что такое metaclass?
Как создать metaclass?
Как сделать класс использующий metaclass?

Что такое процессы и потоки в Python? Какие возникают проблемы?
Какие есть решения для синхронизации данных при работе с потоками?
Что такое GIL?

Что такое итераторы?
Что такое генераторы?
Что такое list comprehensions?
Что быстрее встроенные функции map reduce filter или аналогичный код просто через for? Почему?

Что значит ключевое слово global?
Какие особенности областей видимости в Python?
Зачем нужен код исключения?
Что такое lambda?

2. Django

Какой основной шаблон проектирования лежит в основе фреймверка?
Что пошагово происходит когда пользователь нажал на ссылку? от браузера до приложения и обратно.
Что такое middleware?
Как сделать сообщение с трейсбеком при ошибке(500.html недостаточно)?
Какие типы отношений между таблицами бывают в Django-ORM?
Как реализовать отношение ManyToMany без ORM?
Какие встроенные сигналы бывают в Django?
Основные способы развертывания приложений?
Что такое mod_wsgi?
Что такое fixtures?

3. SQL

Что такое внешний ключ и зачем он нужен?
Что такое GROUP BY?
Что такое ORDER BY?
Что обозначает ключевое слово HAVING?
Что в чем отличие между LEFT JOIN, RIGHT JOIN и INNER JOIN?

4. Unix

Можно ли сделать ssh подключение к другому компьютеру с компа, к которому ты итак уже подключен через ssh? как это сделать?

5. SCM

Можно ли удалить коммиты из репозитория в git? Как?
Можно ли восстановить коммиты?
В чем отличие веток git от веток в svn?
Что такое git reset?
Что такое git rebase?
Чем git rebase отличается от git merge?

6. Алгоритмы

Что такое сложность алгоритмов? Как вычисляется?
Какова сложность алгоритма 
for i in x:
    for j in y:
        i*j
Какие алгоритмы сортировки знаете?
Какие есть способы обхода дерева?

7. Прочее

Что такое doctype в html?
Что такое meta в html?
Что значит переменная g в Flask?

понедельник, 20 февраля 2012 г.

Anonymizer для Encounter


Что это?
Простенький инструмент для того, чтобы обойти ограничение количества активных участников в играх Encounter.

Как это работает?
Вы общаетесь с Анонимайзером, а он общается с движком Encounter. При этом вас может быть много, а Анонимайзер один. Поэтому движок Encounter считает, что читает задания и вводит коды только один игрок. А вы в это время видите в браузере привычный интерфейс и не замечаете разницы (кроме разницы в скорости отклика движка).

Как это совмещается с принципом FairPlay?
Моя позиция такова, что ограничение количества активных участников в играх Encounter является глупым, вредным и неэффективным. Глупым - потому что никак не ограничивает число фактических участников игры, а всего лишь затрудняет им доступ к движку. Вредным - потому что провоцирует команды искать окольные пути. Неэффективным - потому что большинство регулярно играющих команд уже проложили и утоптали эти окольные дорожки.
Пожалуй, единственная цель, которая успешно достигается этим ограничением, - это насильственное дробление команд с целью повышения доходов авторов игр.
Таким образом, настоящий FairPlay в играх Encounter возможен только при отмене ограничения количества активных участников. Именно эту цель (отмену ограничения) я и преследую.


Установка(upd: 29.10.2013).
1) Качаем инсталлятор отсюда
2) Устанавливаем
3) В папке, куда оно поставилось, редактируем файл settings.ini
там заполняем:
username - существующий логин под которым будет играть команда.
password - пароль
domain - домен, на котором мы будем играть.

Например, есть игра с ограничением состава на домене msk.en.cx
Наш файл должен иметь примерно такой вид:
username=air
password=airpassword
domain=msk.en.cx
По-умолчанию, сервер будет работать на порту 8001. Если он у вас чем-то занят - то необходимо поменять его в последней строке файла anonymizer.py на любой открытый порт на вашем компьютере.

ВНИМАНИЕ. Домен вводим именно в формате "xxx.en.cx", а не "www.xxx.en.cx" или "http://xxx.en.cx/" Будьте внимательны.

Запуск.(upd: 29.10.2013)
Кликаем на ярлычке.
Если у вас при запуске была следующая ошибка:
socket.error: [Errno 98] Address already in use
значит 8001 порт у вас занят. нужно либо освободить его либо указать другой в скрипте.
Теперь все. Сервер готов к использованию.

Открываем в браузере http://<ваш внешний айпишник>:8001/
Cвой внешний айпишник можно определить зайдя сюда
То есть если у вас айпи 79.125.20.11 и порт указан по-умолчанию, открывать будем
http://79.125.20.11:8001/
В открывшемся окне смотрим залогинены ли мы. Если нет - снова редактируем файл и вводим правильные логин и пароль еще раз. сохраняем файл, перезапускаем сервер, обновляем страницу в браузере.
Если залогинены - кидаем ссылку в чат остальной команде. Приятной игры.

ВНИМАНИЕ. Если вы выходите в сеть через роутер то в браузере вы увидите что страница не найдена. В этом случае вам нужно единоразово "пробросить порты": 
смотрим свой локальный айпишник, идем в настройки роутера, находим там port forwarding и указываем forwarding для определенного порта на свой айпишник, сохраняем настройки, обновляем страничку в браузере. все.


Нюансы.
Анонимайзер "запоминает" все введенные ответы всеми игроками которые его используют в данный момент и не дает повторно вводить одно и тоже в систему - для того что бы было меньше палева в мониторинге.
Сбросить память ему можно введя в качестве ответа слово "ап". Само слово на сервер отправляться не будет но теперь он начнет запоминать ответы игроков с нуля. Это полезно при переходе на следующий уровень в играх линейного типа.

Советы.
1) Рекомендую не играть с помощью анонимайзера в игры типа брутфорс или гх ибо быстродействия вашего компьютера и скорости интернет-соединения может не хватить для того что-бы быстро обрабатывать кучу запросов в секунду. В этом случае анонимайзер может подлагивать.
2) Рекомендую обновлять задания при помощи кнопки "Обновить" в меню игры.
3) Рекомендую использовать анонимайзер исключительно по прямому назначению - то есть смотреть задания и бить ответы. Не нужно там ф5тить статистику или писать посты на форум или делать денежные переводы и тд и тп. Это все лишние запросы к серверу, что может привести к лагам и тормозам.

Исходник.
anonymizer.py
# -*- coding: utf8 -*-
import urllib
import random

from flask import Flask, request
app = Flask(__name__)

from MozillaEmulator import *
dl = MozillaEmulator()

username = ""
password = ""
domain = ""

answers = ['ап']
answer = ""

def do_login():
    login_url = "http://%s/Login.aspx?return=/" % domain
    page = dl.download(login_url, postdata=urllib.urlencode({"Login": username,
                                                             "Password": password}))
    print "******** READY ********"

@app.route('/')
def home():
    url = "http://%s/" % domain
    page = dl.download(url)
    return page

@app.route('/', methods=['GET', 'POST'])
def index(link):
    url = "http://%s/%s?%s&lol=%s" % (domain, link, request.query_string, random.random())
    if request.method == 'GET':
        page = dl.download(url)
    if request.method == 'POST':
        post_data = {}
        for k, v in request.form.to_dict().items():
            post_data[k] = v.encode('utf-8')
            if k == 'Answer':
                answer = post_data[k]
        if answer in answers:
            page = dl.download(url)
            if answer == 'ап':
                global answers
                answers = ['ап']
        else:
            answers.append(answer)
            page = dl.download(url, postdata=urllib.urlencode(post_data))
    return page

if __name__ == "__main__":
    do_login()
    app.run(port=8001, host='0.0.0.0')

Если есть вопросы по установке-настройке-использованию - пишите прям тут в комментах.

понедельник, 6 февраля 2012 г.

VirtualBox как окружение для проекта

Если нужны точные версии серверного софта, то разработка может осложняться. В таких случаях, вместо того, чтобы тратить время на поиски пакетов для вашей десктопной ОС, можно просто взять VirtualBox, установить там тот же (или похожий) дистрибутив, что используется в реальном окружении проекта, и установить нужные версии софта уже там.

Код при этом редактируется (и runserver запускается) на ОС-хозяине, а проблематичные серверы отлично работают на ОС-госте. Для доступа к сервисам гостя с хозяина можно настроить port forwarding. На Mac OS X 10.6.6 это делается, например, так:


/Applications/VirtualBox.app/Contents/MacOS/VBoxManage modifyvm "postgres8.4.8" --natpf1 "guestmemcached,tcp,,11212,,11211"


Выполняется это при выключенной VirtualBox-машине, её имя - postgres8.4.8. В примере guestmemcached - имя "связи", 11212 - номер порта хозяина, 11211 - номер порта гостя.

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

четверг, 19 января 2012 г.

django-mptt и South

В доке на django-mptt рекомендуют сделать manage.py syncdb при подключении MPTT. При использовании South мы сделаем две миграции: schemamigration после того, как унаследуем модель от MPTTModel и datamigration после неё, чтобы перестроить дерево. К сожалению, с последним пунктом маленькая проблема.



Для перестройки дерева вызывается метод менеджера TreeManager rebuild(). Однако внутри миграции модель "заморожена", и менеджера такого у неё нет. Решение прямое - добавить к модели менеджера и вызвать rebuild()




from mptt.managers import TreeManager
from mptt.models import MPTTOptions
...
def forwards(self, orm):
"Write your forwards methods here."
Place = orm['places.Place']
Place.add_to_class('tree', TreeManager())
Place.add_to_class('_mptt_meta', MPTTOptions())
Place.tree.init_from_model(Place)
Place.tree.rebuild()


Так сработает. Place тут - модель, дерево для которой надо перестроить.

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

Шаг назад в django form wizard

Если надо вернуться на шаг назад в django form wizard, то можно это сделать на клиенте джаваскриптом.

 

<a href="javascript:$('input[name=wizard_step]').val(0);$('#id_step_3_form').submit();&rt;

Так как визард верит переданным формам и узнаёт, на каком шаге он находится, из передаваемых ему POST-данных, то это работает. Ухищрения на стороне сервера здесь работать не будут, потому что сервер может вернуть либо данные в ответ на запрос, либо редирект, который заставит броузер сделать GET-запрос. Таким образом, прервется цепь POST-запросов, передающих данные визарда.

пятница, 2 декабря 2011 г.

viewtags

Как скрестить view и template_tag?

Случалась ли у Вас ситуация когда у вас на страничке есть некий блок. При загрузке страницы он реализуется например как рендер template_tag'а:
@register.inclusion_tag('my_block.html', takes_context=True)
def my_block_tag(context):
    return {
        # some additional context
    }
В шаблоне как обычно:
{% load my_tags %}
{% my_block_tag %}
Дальше, этот блок имеет pagination, который реализуется Ajax-запросом на сервер, где соответствующий view рендерит тот же самый кусок страницы:
def my_block_ajax(request, ...):
    #формируем нужные данные для нужной страницы
    return JsonResponse("my_block.html", {
        # какие-то данные
    })

Все замечательно но у нас получается есть 2 вещи (template_tag и view) которые по сути делают одно и тоже - возвращают один и тот же блок.
Как уйти от повторения кода?
Самое просто решение которое приходит в голову - сделать во views импорт нужного template_tag'а и возвращать в view результат работы этого самого template_tag'a:
from myapp.templatetags.my_tags import my_block_tag

def my_block_ajax(request, ...):
    #формируем нужные данные для нужной страницы
    return my_block_tag(some_context)

С виду все хорошо но давайте посчитаем что у нас есть. Есть view и есть tag(есть даже библиотека тэгов - а это уже само по себе отдельная папка и пару файлов), есть записи в urls.py для каждого ajax-запроса:
url(r'ajax/my_block/$', 'my_block', name='my_block'),
Сильно много у нас всего. А если у нас таких блоков 10? 20? это просто огромная масса лишнего повторяющегося кода.
А самое главное у нас одно и тоже по сути логическое действие для одного блока на странице разнесено как минимум по 2м отдельным файлам - my_tags.py и views.py.

ViewTags
Дальше пойдет интересное решение этой проблемы. Оговорюсь сразу, идея не моя, реализация тоже, но этим механизмом я пользовался и мягко говоря впечатлился:
Итак, задача скрестить view и template_tag.
Создаем файл utils/viewtags.py
  1. Делаем декоратор
    import inspect
    from django import template
    from django.template.loader import get_template
    from django.http import Http404
    from django.shortcuts import render_to_response
    from django.utils.importlib import import_module
    from django.conf import settings
    
    
    register = template.Library()
    template.builtins.append(register)
    
    _viewtags_registry = {}
    
    
    def viewtag(template, args=[]):
        def decorator(func):
            _register_tag(func, template)
            _viewtags_registry[func.__name__] = (func, template, args)
    
            return func
        return decorator
    
    Этим декоратором мы будем оборачивать вьюшки.
  2. Вьюшки будем обрабатывать вот так
    def view(request, viewtag_name):
        try:
            func, template_, arg_processors = _viewtags_registry[viewtag_name]
        except KeyError:
            raise Http404()
    
        argnames = inspect.getargspec(func).args
        kwargs = {}
        for argname in argnames:
            if argname in request.REQUEST and argname != 'request':
                kwargs[argname] = request.REQUEST[argname]
        for proc in arg_processors:
            kwargs = proc(request, kwargs)
    
        return render_to_response(template_, func(request, **kwargs), template.RequestContext(request))
  3. Все наши viewtags будем хранить в app/viewtags.py
    Поэтому при старте сервера загружаем все наши viewtags
    def autodiscover():
        for app in settings.INSTALLED_APPS:
            try:
                import_module('%s.viewtags' % app)
            except ImportError, e:
                pass
    
  4. Авторегистрируем template_tags для наших viewtags
    def _register_tag(func, template):
        def tag(parser, token):
            parts = token.split_contents()
            args, kwargs = [], []
            for part in parts[1:]:
                if '=' in part:
                    kwargs.append(part.split('=', 1))
                else:
                    args.append(part)
    
            return TempNode(func, template, args, dict(kwargs))
        register.tag('vt:' + func.__name__, tag)
    
    class TempNode(template.Node):
        def __init__(self, func, template_, args, kwargs):
            self.func = func
            self.template = get_template(template_)
            self.args = [template.Variable(arg) for arg in args]
            self.kwargs = dict([(k, template.Variable(v)) for (k,v) in kwargs.items()])
    
        def render(self, context):
            args = [context['request']] + [arg.resolve(context) for arg in self.args]
            kwargs = dict([
                (key, val.resolve(context)) for (key, val) in self.kwargs.items()
            ])
            context.update(self.func(*args, **kwargs))
            res = self.template.render(context)
            context.pop()
            return res
    
Собственно все. Теперь, как этим добром пользоваться:
В urls.py единоразово(!) делаем запись
url(r'^viewtag/([-\w]+)/$', 'my_project.utils.viewtags.view', name='viewtag'),
В viewtags.py импортируем наш декоратор viewtag и оборачиваем вьюшку:
from my_project.utils.viewtags import viewtag

@viewtag('my_block.html')
def my_block(request, page=0):
    # делаем что нам там нужно
    return {
        #context
    }
Благодаря пункту 3 мы автоматически будем иметь в системе "запись" в urls.py для всех функций описанных в файлах viewtags.py

Теперь к шаблонам.
Что-бы указать блок на странице будем писать так:
{% vt:my_block params %}
В пункте 1 у нас зарегистрированы функции для тэгов которые называются "vt". То есть если есть функция обернутая в viewtag то можно писать в шаблоне {% vt:имя_функции %} и это будет работать.

Ну и пейджинация.
Если раньше у нас был url для каждой ajax-вьюшки отдельно:
$.get('{% url my_block_ajax %}', {page: 2}, function(data) {
        $('body').append(data);
    });
То теперь мы делаем url для вьюшки с названием "viewtag" и параметром передаем конкретное название нужного viewtag:
$.get('{% url viewtag "my_block" %}', {page: 2}, function(data) {
        $('body').append(data);
    });
Считаем еще раз. Библиотек своих template_tags у нас вообще нет. Кучи однотипных записей в urls.py для каждого отдельного ajax-запроса у нас тоже нет. Код для каждого блока представлен одной общей функцией в viewtags.py
По-моему симпатично. Лично у меня количество кода значительно уменьшилось и все стало более понятным.

четверг, 17 ноября 2011 г.

Selenium и jQuery

Проблема: Надо вызвать jQuery через Selenium

Решение: В методе SeleniumTestCase вызываем
self.selenium.storeEval('''this.browserbot.getUserWindow().jQuery('#places-ammount span span').text('1')''', 'foo')