Google Talk: authentication failure

Od wczorajszego wczesnego popołudnia nie mogłem się zalogować na swoje konto GTalk za pomocą żadnego komunikatora. Nic, zero, nada. GMail nadal działał bez zarzutu, czat na stronie logował się momentalnie, słońce świeciło, a Elvis wciąż był martwy. Zacząłem podejrzewać najgorsze.

Kiedy przyjechałem do domu, okazało się, że miałem rację — kończyło mi się piwo. Niezrażony, spróbowałem mimo wszystko skorzystać z komunikatora. Niestety — z podobnym skutkiem. Powoli zaczynałem przypuszczać, że coś jest nie tak. Sprawa była grubymi nićmi szyta. Bez wątpienia komuś zależało, by zerwać moje kontakty z Karolem z warzywniaka pod oknem. Czyżby cena ogórków szklarniowych miała wkrótce przekroczyć cenę cukru, a ten biedny chłopak próbował mnie ostrzec?

Dziś rano wznowiłem dochodzenie. Już w tramwaju byłem pewien, że ktoś mnie śledzi. Jakie jest bowiem prawdopodobieństwo, by sześćdziesiąt trzy osoby jechały akurat w tym samym kierunku, co ja? Dotarłem do biura, by zaraz po wejściu wytoczyć najcięższe działa: sięgnąłem do działu pomocy Google.

Jeśli zatem i ciebie chcą pozbawić kontaktu z Karolem, rychło wnijdź pod adres wskazany poniżej:

https://www.google.com/accounts/DisplayUnlockCaptcha

See no eval, hear no eval, speak no eval

Choć sam konta w Książeczce Maryja nie posiadam, przyszło mi ostatnio ścierać się z ichnim API, coby dla klienta wdrożenia poczynić. Po przejrzeniu dostępnych bibliotek (w tym dość żałosnego python-sdk), stanęło na dość popularnym projekcie pyfacebook.

Oryginalne repozytorium było wybrakowane pod względem funkcjonalności, wybraliśmy więc fork, który z pewnością stworzył fan Władcy Pierścieni. Jak się miało później okazać, „jeden, by wszystkie zgromadzić i w ciemności związać” dość wiernie oddaje uzyskany efekt.

Człowiek głupi, to przy pierwszym problemie zajrzał w bebechy ofiary. Po tym stanął jak wryty i całe Satchmo przeleciało mu przed oczami. Na początek tradycyjny problem — przejście się w glanach po separacji warstw:

from threading import local

_thread_locals = local()
def get_facebook_client():
    """
    Get the current Facebook object for the calling thread.

    """
    try:
        return _thread_locals.facebook
    except AttributeError:
        raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
class FacebookMiddleware(object):
    """
    Middleware that attaches a Facebook object to every incoming request.
    The Facebook object created can also be accessed from models for the
    current thread by using get_facebook_client().

    callback_path can be a string or a callable.  Using a callable lets us
    pass in something like lambda reverse('our_canvas_view') so we can follow
    the DRY principle.
    """
    # ...

    def process_request(self, request):
        # ...
        _thread_locals.facebook = request.facebook = Facebook(self.api_key,
                self.secret_key, app_name=self.app_name,
                callback_path=callback_path, internal=self.internal,
                proxy=self.proxy, app_id=self.app_id, oauth2=self.oauth2)

Przerażające konstrukcje zaczęły się jednak później:

# generate the Facebook proxies
def __generate_proxies():
    for namespace in METHODS:
        methods = {}

        for method, param_data in METHODS[namespace].iteritems():
            methods[method] = __generate_facebook_method(namespace, method, param_data)

        proxy = type('%sProxy' % namespace.title(), (Proxy,), methods)

        globals()[proxy.__name__] = proxy

__generate_proxies()
class Facebook(object):
    """
    Provides access to the Facebook API.

    ...
    """

    def __init__(self, api_key, secret_key, auth_token=None, app_name=None,
                 callback_path=None, internal=None, proxy=None,
                 facebook_url=None, facebook_secure_url=None,
                 generate_session_secret=0, app_id=None, oauth2=False):
        # ...
        for namespace in METHODS:
            self.__dict__[namespace] = eval('%sProxy(self, \'%s\')' % (namespace.title(), 'facebook.%s' % namespace))

Skończyło się na własnym forku i refaktoryzacji tych i wielu innych fragmentów kodu. Poprawioną wersję można znaleźć na GitHubie.

Na koniec stare powiedzenie ludowe:

Gdy bowiem zoczysz, iż jest coś narzeczy, a za cwanego masz się i uważasz __globals__ i eval() za sprawy rozwiązanie, mylisz się wielce, przeto idź przypudrować nos¹.

¹ Ciało, 2003

Button kontra przeglądarki

Ku pamięci:

  • Firefox¹ ignoruje line-height.

  • Firefox¹ dodaje własny element z dwupikselowym paddingiem i pikselowym borderem:

    button::-moz-focus-inner {
        border: 0 none;
        padding: 0;
    }
  • Wszystkie przeglądarki domyślnie wliczają ramki i padding do rozmiarów elementu:

    button {
        box-sizing: content-box;
        -moz-box-sizing: content-box;
        -ms-box-sizing: content-box;
        -webkit-box-sizing: content-box;
    }
  • IE7 i starsze nie radzą sobie z automatyczną szerokością przycisków:

    button {
        overflow: visible;
        width: 0;
    }
    button[type] {
        width: auto;
    }

¹ Dotyczy ogólnie produktów Mozilli.

Tymczasem w biurze…

Uruchomiliśmy nową wersję serwisu Centrum Faktur!

Listę nowości opisałem już w podlinkowanym tekście, więc właśnie tam zapraszam zarówno spragnionych wiedzy, jak i miłośników mojej twórczości literackiej.

Kobiety i dary można podsyłać do mnie. Problemy, błędy, uwagi — tradycyjnie, na Burzę Mózgów.

Flattr

Flattr rozprzestrzenia się wirusowo. Ja swoje zaproszenie dostałem od Deejay’a. Jeszcze dwa można zdobyć u niego, trzy kolejne tutaj.

Jeśli zatem lubicie dzielić się pieniędzmi (i wierzycie, że Flattr nie zniknie razem z nimi), przyłączcie się do tej jedynej w swoim rodzaju sieci wymiany napiwków. Po trzy zaproszenia zgłoście się w komentarzach — wyślę je na adres podany w formularzu.

Faktury przez API

Odkąd rozpocząłem współpracę z Mirumee, w listach od moich czytelniczek wciąż powtarzało się jedno pytanie — kiedy? W końcu zrobiłem jednak te badania, a blog to nie miejsce na dyskusje o moim życiu łóżkowym, dlatego dziś pomówimy o CentrumFaktur.

Co wybrałbyś zatem, gdyby zaoferowano ci body shot z ciała dowolnie wskazanej przez siebie kobiety¹ lub nieskrępowany dostęp do API pozwalającego szybko, łatwo i bezpiecznie wystawiać faktury z poziomu twojego programu?

¹ Chciałem tu napisać, że panie mogą sobie wybrać mężczyznę, ale w ten sposób zmniejszyłbym szanse na to, że ktokolwiek wybierze API.

Mamy cholerną nadzieję, że nie lubisz tequili, bo w stworzenie tego API zainwestowaliśmy już sporo czasu. Nie owijając w bawełnę, przechodząc do meritum, bez ogródek i zbędnych ceregieli, waląc prosto z mostu i nie lejąc wody przedstawiam dokumentację API CentrumFaktur.

Zdradzę przy okazji, że odnośnikiem tym dzielę się z wami nieprzypadkowo. Wiem bowiem, że przeczytacie, bez wyjątku, wszystko, co tylko napiszę. I będzie się wam podobało. To moja strona i ja tu ustalam zasady.

PIT 37

Przychodzi taki moment, kiedy nie da się już odwlekać rozliczenia podatków. Ja swoją deklarację postanowiłem złożyć dopiero dzisiaj. Nie obyło się bez przygód.

Linux x86-64

e-Deklaracje wymagają Adobe AIR. Adobe AIR nie obsługuje 64-bitowego Linuksa. Tyle.

Dane z poprzedniego roku (niezbędne do rozliczenia elektronicznego) udało się wydobyć ręcznie:

$ sqlite3 ~/.appdata/e-Deklaracje.*/Local\ Store/settings.dat 'SELECT xmlDocument FROM EDK_DECLARATIONS;'

Z Wine jest podobnie — jedyna wspierana architektura, to x86.

Linux x86

Adobe AIR 1.5 jest zjebany, robi w środku bardzo złe rzeczy (budowa plików .rpm z rozszerzeniem .deb i tym podobne) i nie działa nigdzie poza Fedorą.

Adobe AIR 2.0 instaluje się bez problemu, przy okazji psując uprawnienia katalogów z ikonami, plikami .desktop i typami MIME. Adobe Reader 9 instaluje się bez problemów. e-Deklaracje uruchamiają się, pozwalają na stworzenie profilu i w zasadzie tyle. Proces acroread zajmuje całą dostępną pamieć i moc procesora. Dziękujemy ci, Adobe.

Online

Ostatecznie rozliczyłem się w przeglądarce, za pomocą aplikacji pitroczny.pl¹. Polecam tę wersję — oszczędza nerwów, ma lepszy interfejs (nie wygląda jak zeskanowany PIT) i, przede wszystkim, działa.

¹ Korzystając z tego odnośnika przeznaczasz 1% podatku na Fundację Nowoczesna Polska.

Django: TestCase i testowanie szablonów

Bardzo brzydki hack, oparty o załącznik do ticketu 7611. Czyści wewnętrzny cache Django, ale pozwala testować działanie kodu opierającego się o renderowanie szablonów bez ryzyka, że aplikacja owe szablony nadpisze:

import os
import sys
from django.conf import settings
from django.template import loader
from django.test import TestCase

class TemplateTestCase(TestCase):
    def _pre_setup(self):
        self._template_setup()
        super(TemplateTestCase, self)._pre_setup()

    def _post_teardown(self):
        self._template_teardown()
        super(TemplateTestCase, self)._post_teardown()

    def _template_setup(self):
        if hasattr(self, 'template_loaders'):
            self._old_template_loaders = settings.TEMPLATE_LOADERS
            settings.TEMPLATE_LOADERS = self.template_loaders
            loader.template_source_loaders = None
        if hasattr(self, 'template_dirs'):
            self._old_template_dirs = settings.TEMPLATE_DIRS
            test_dir = os.path.dirname(sys.modules[self.__module__].__file__)
            settings.TEMPLATE_DIRS = [os.path.join(test_dir, dirname)
                                      for dirname in self.template_dirs]

    def _template_teardown(self):
        if hasattr(self, '_old_template_loaders'):
            settings.TEMPLATE_LOADERS = self._old_template_loaders
            loader.template_source_loaders = None
        if hasattr(self, '_old_template_dirs'):
            settings.TEMPLATE_DIRS = self._old_template_dirs

Użycie:

class TestSomething(TemplateTestCase):
    template_loaders = ('django.template.loaders.app_directories.load_template_source',)

    def test_foo(self):
        pass

Django: pluralize

Najważniejszą rzeczą, jaką powinniście wiedzieć na temat filtra pluralize jest ta, żeby go nigdy nie używać. Nigdy. Jego istnienie wynika z lenistwa ludzi, którzy nigdy w życiu nie przygotowywali aplikacji do tłumaczenia. Jeśli nie wyraziłem się dość jasno, używanie filtra pluralize wywoła u ciebie raka, stereoporoże i zatwardzenie rozsiane.

Jakkolwiek sprytnie by nie wyglądał, ten kod nie nadaje się do niczego:

<p>
    There {{ item_count|pluralize:"is,are" }}
    {{ item_count }} thing{{ item_count|pluralize:"s" }}.
</p>

Dlaczego się nie nadaje? Spróbujcie go przetłumaczyć na kilka języków. Niektórzy chwytają się brzydkich obejść, inni wolą zrobić to zgodnie ze sztuką. Jeśli kiedykolwiek pracowaliście przy lokalizacji oprogramowania, doskonale wiecie, że tłumaczenie jednego słowa jest tak samo sensowne, jak tłumaczenie każdej litery z osobna.

Poprawna wersja jest przy okazji czytelna:

<p>
{% blocktrans count item_count as items %}
    There is {{ items }} thing.
{% plural %}
    There are {{ items }} things.
{% endblocktrans %}
</p>

{% blocktrans %} z {% plural %} używają ungettext, dzięki czemu bez konieczności odprawiania czarów radzą sobie z polskimi liczebnikami (jeden tysiąc, dwa tysiące, pięć tysięcy).

Django: direct_to_template

Nie wiem, czemu niektórzy tak uparcie korzystają z funkcji render_to_response, by za każdym razem przekazać do niej ręcznie stworzony obiekt kontekstu. Zresztą, sami oceńcie, która wersja jest ładniejsza:

from django.shortcuts import render_to_response
from django.template import RequestContext

def foo(request):
    # ...
    return render_to_response('foo.html', {'bar': baz},
                              context_instance=RequestContext(request))

Czy może:

from django.views.generic.simple import direct_to_template

def foo(request):
    # ...
    return direct_to_template(request, 'foo.html', {'bar': baz})

A teraz pomnóżcie to przez liczbę widoków w aplikacji.