Friday, September 21, 2012

Django cache templatetag with dynamic backend parameter

I've found a nice project django-adv-cache-tag.

From the project website:

With django-adv-cache-tag you can :
  • add a version number (int, string, date or whatever, it will be stringified) to you templatetag : the version will be compared to the cached one, and the exact same cache key will be used for the new cached template, avoiding keeping old unused keys in your cache, allowing you to cache forever.
  • avoid to be afraid of an incompatible update in our algorithm, because we also use an internal version number, updated only when the internal algorithm changes
  • define your own cache keys (or more simple, just add the primary key (or what you want, it's a templatetag parameter) to this cache key
  • compress the data to be cached, to reduce memory consumption in your cache backend, and network latency (but it will use more time and cpu to compress/decompress)
  • choose which cache backend will be used
  • define {% nocache %}...{% endnocache %} blocks, inside your cached template, that will only be rendered when asked (for these parts, the content of the template is cached, not the rendered result)
  • easily define your own algorithm, as we provide a single class you can inherit from, and simply change options or whatever behaviour you want, and define your own tags for them
I am using template fragments caching a lot, but some parts of website need invalidating depending on products type etc. 
I am invalidating cache on save & delete signals for model.

Here's the example:
Say there are 5 types of products and your website includes listing for them (which needs lots of data processing etc) and you want to cache it.


from django.db import models

PRODUCT_TYPE_CHOICES = (
('type1','type2'),
('type2','type2'),
('typen','typen'),
)

class Product(models.Model):
    attr1 = ...
    attrn = ...
    type =  models.CharField("Product Type", max_length=6, choices=PRODUCT_TYPE_CHOICES)
Using one backend, modifying product of type1 will purge the entire cache backend. Set up more backends:
# settings.py

CACHES= {
    'templates_products_type1': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
        },
    'templates_products_type2': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11212',
        },
}
You can also use Redis for caching, which requires you to run one redis instance with multiple databases. Let's create our own {% my_cache %} templatetag with cache backend name as a first vary_on parameter. (this is the quickest way as you can also use get_pk() or even add your own parameters to {% my_cache %} templatetag. You could also use fragment name but it's not resolvable. Let's get to the code:
#my_cache_tags.py
from adv_cache_tag.tag import CacheTag
from django.core.cache import get_cache
from django import template

register = template.Library()

class MyCacheTag(CacheTag):
    def get_cache_object(self):
        backend = self.vary_on[0]
        return get_cache(backend)
MyCacheTag.register(register, 'my_cache')
Here's the template code:
# assuming products_type variables is assigned
{% load my_cache_tags %}

{% with backend="templates_products_"|add:products_type %}
{% my_cache 0 products backend other_vary_on_variables %}
{% for product in product_list %}
{{ product }}
{% endfor %}
{% endmy_cache %}
When saving or deleting products of some type, just purge template cache for produdcts only of this specified type.
from django.core.cache import get_cache
#function run on model save / delete

def clear_products_template_cache(type):
    cache_name = "templates_products_" + type
    cache = get_cache(cache_name)
    cache.clear()
NOTE: This is a SIMPLIFIED solution. I just wanted to point out that you can easily use different backends for one template cache tag. It's your job to make some additional processing code (especialy get_cache_object() for selecting cache backend etc.

Monday, September 17, 2012

Kantor Alior Banku i tajemniczy licznik

W taryfie prowizji i opłat bank wielokrotnie opiera się na wartości "licznika", do wartości którego można
przelewać walutę za darmo. Jeśli nie chcesz czytać całego wpisu, to proszę bardzo.

Możesz wymienić (w tym kantorze) nawet 10 tyś. franków szwajcarskich i nie zapłacisz prowizji za przelew.

Dalej moje boje z kantorami...
W związku z koniecznością wymiany waluty, przy złodziejskim spreadzie walutowym (a więc różnicy w cenie kupna i sprzedaży waluty) postanowiłem poszukać lepszego rozwiązania.

Jako pierwszy przetestowałem serwis inkantor.pl. Jest to jednak kantor, który nie podlega nadzorowi KNF.
Następnym kantorem internetowym był gocash.pl. Jestem z niego zadowolony, a kurs wymiany franka
był jeszcze korzystniejszy.

Obecnie kurs sprzedaży CHF:

3,3717 - gocash
3,3819 - inkantor
3,3666 - kantor alior


W obu przypadkach przelew walutowy zlecony do mbanku trafiał tego samego dnia, o ile zlecony został przed godziną 11ą. Było to nieco problematyczne, gdyż wymiany waluty chciałem dokonywać o godz: 14:50, z której to mbank pobiera kurs do wymiany złotówek na franki w dniu płatności raty za kredyt,
a ja nie chcę bawić się w spekulanta.

Dodam jeszcze, że w przypadku inkantor.pl pobierano ode mnie opłatę ~5 zł za przelew, zaś gocash.pl takiej opłaty nie pobierał.
Może później zrobię porównanie tych serwisów.

Obecnie czas przyszedł na kantor aliorbanku http://kantor.aliorbank.pl
Kursy walut są jeszcze korzystniejsze w moim przypadku. Nie jest to znaczna różnica, gdyż w przypadku gocash.pl te kursy również były bardzo korzystne.

Przelew waluty (nawet w trybie ekspresowym) jest darmowy, ale "do wartości licznika". Co owa wartość oznacza?
Jest to wartość, która powiększa się przy każdej wymianie waluty.
Pierwsza myśl, jaka przychodzi do głowy, to "ile wynosi górna wartość licznika", powyżej której naliczana jest opłata ?
W internecie krążą różne wartości, np. 600 CHF (dla franka szwajcarskiego). Autor tej tezy informuje więc,
że nie opłaca się wymieniać więcej niż 600 franków, gdyż przelanie ich będzie już kosztowne.

Ciekawy tego zagadnienia zacząłem szukać wartości granicznej licznika i niestety nie udało mi się jej znaleźć.
Pani na infolini potwierdziła, że nie ma takiej granicy.

Chodzi o to, by nie wpłacać franków w kasie i korzystać z darmowych przelewów za pośrednictwem tegoż kantoru. Można więc śmiało przelewać każdą wartość waluty, którą za pośrednictwem kantoru wymieniamy.
Pani na infolinii wspomniała też o innych typach zleceń, ale nie to jest tematem tego wpisu.
Najczęściej z kantoru korzystać będzie zwykły zjadacz chleba, który wymienia walutę do swojego kredytu hipotecznego oszczędzając dobre kilkadziesiąt złotych na racie.

Saturday, September 15, 2012

KNF razem z rządem i bankami w jednym tańcu

Nie jest to referat na temat działalności KNF. Są to tylko moje przemyślenia (być może czasami pozbawione sensu), które zmuszą kogoś mądrzejszego do napisania "mądrego referatu", który skłoni co niektórych do zastanowienia się nad polityką tej instytucji i jej roli w życiu Polaków.

 
Komisja Nadzoru Finansowego, powstała w 2006r. instytucja mająca chronić klientów instytucji podlegających pod ten nadzór (a więc np. banki, czy też instytucje ubezpieczeniowe, domy maklerskie itd.) Czy jest jednak tak, że instytucja ta chroni tych najsłabszych ? W moim przekonaniu tak nie jest. Większość wydawanych przez tą instytucję rekomendacji w rzeczywistości negatywnie odbija się na tych najbardziej skubanych przez owe banki - klientów z zaciągniętymi kredytami hipotecznymi. Nie tylko poprzez swoją politykę ogranicza ona dostępność kredytów, ale też zmierza ku wbiciu kolejnej szpili tym najsłabszym. Jak czytać można za niezalezna.pl
Komisja Nadzoru Finansowego zobowiązała banki do prześwietlenia nieruchomości zabezpieczających udzielone przez nie kredyty. Powstanie swoisty spis inwentaryzacyjny nieruchomości kredytowanych w Polsce. Powodem tej gigantycznej akcji są spadające wciąż ceny nieruchomości oraz coraz większa groźba niewypłacalności kredytobiorców.
Artykuł zacznę takim powyższym sformułowaniem i przejdę do grzechów KNFu, które wg. mnie popełnił (świadomie lub nie).

Zanim przejdę dalej pozwolę sobie opisać rynek mieszkaniowy w ostatnich latach. Mam kolegów, którzy w okolicach roku 2003r. zaciągnęli kredyt hipoteczny we franku szwajcarskim. Kilka lat później (a mieli czas do lipca 2008r.) spłacając lub też sprzedając swoją nieruchomość mogli nieźle zarobić.

Stało się tak ponieważ kurs franka w 2003r. oscylował w granicach 3 zł, zaś w lipcu 2008r. wartość jego spadła do 1,99 zł.
Dodatkowo zadziałał tutaj sprytnie wykorzystany przez deweloperów program "Rodzina na swoim", który sztucznie napompował - wspomaganą chęcią zakupu przez osoby urodzone w latach wyżu demograficznego, czyli ~8x - bańkę na rynku nieruchomości. Marże deweloperów kształtowały się nawet w okolicach 50%!

Dzisiaj mamy franka po około 3,5 zł, gdzie jeszcze niedawno wartość jego wynosiła 4 zł.
Mamy również dużą część "umoczonych", których wartość nieruchomości jest znacznie poniżej wartości kredytu ze względu na:
1. Wzrost wartości franka szwajcarskiego do polskiego złotego.
2. Spadek cen nieruchomości. Bańka musiała pęknąć i tak się też dzieje.

Efektem takiej sytuacji jest kolejny atak na bezbronnych klientów maszyny bankowej, ale o tym dalej.

Mamy rok 2008, gdzie kurs franka szwajcarskiego spada do wartości 1,99 zł.
Ciężko w telewizji usłyszeć o niebezpieczeństwie zawierania kredytów hipotecznych w tej walucie.
Nawet nasz "sprawny" KNF nie widzi przeszkód w udzielaniu takich kredytów. Przypomnę, że jest to instytucja działająca od 2006r.


Banki nie dość, że udzielały kredyty we franku na potęgę, to dodatkowo jeszcze namawiały obecnych zadłużonych w złotówkach do przewalutowania swoich kredytów na franka szwajcarskiego.
Demon kusił niższymi ratami, bo przecież frank jest nisko, a oprocentowanie też niskie.
Osobiście dostawałem sygnały o telefonach, gdzie namawiano do tak idiotycznego kroku.

Przypomnę jeszcze raz. KNF milczał.
Korzystając z okazji, udałem się do mojego banku celem przewalutowania kredytu z franka szwajcarskiego na złotówki. Moje doświadczenie techniczne w zakresie analizy technicznej pozwalało na wyciągnięcie takich wniosków. W banku stanęło nade mną 4 panów w białych koszulach, którzy mocno namawiali mnie do odstąpienia od tego kroku. Nie dałem się jednak przekonać i wymusiłem na nich umożliwienie mi
złożenia takiej dyspozycji.
W swojej bezsilności, otrzymałem kawałek czystej kartki A4, gdzie napisać miałem dyspozycję.

Odpowiedź na moją dyspozycję otrzymałem po 2,5 miesiącu, gdy kurs franka poszybował w górę.
Dodatkowo warunki nowej umowy były nieciekawe. Proszę uwierzyć, że do dzisiaj banki mają
totalną swobodę w ilości czasu, jaki potrzebują na rozpatrzenie wniosku o przewalutowanie.
Nie jest to określone w regulaminie (bynajmniej mojego banku), a sam UOKiK nie widzi w tym nic dziwnego.

Wiemy zatem już, że KNF nie widział w kredytach walutowych nic złego w 2008r. Wiem także,
że wydał później bankom rekomendacje, które znacznie ograniczają możliwość udzielania kredytów hipotecznych w walucie obcej. Kiedy tak mocno zmieniło się podejście KNFu do takich kredytów?

Stało się to w 2010r., gdy kurs franka szwajcarskiego oscylował w granicach 2,8-3 zł, a więc było 33% wyższy, niż w lipcu 2008r, gdy tak wciskano ludziom kredyty w tej walucie. Ktoś, kto brał kredyt w wysokości 300 tyś. zł, do spłaty ma dzisiaj do spłaty ponad 400 tyś. zł.

Jak wiadomo, dla klienta najlepszym rozwiązaniem jest zaciągać kredyt walutowy, gdy wartość waluty obcej jest wysoka. Zmniejsza to znacznie (choć nigdy nie eliminuje) ryzyko związane ze wzrostem wartości
tego kredytu dla zarabiających w złotówkach. W złotówkach bowiem albo kredyt jest spłacany, albo kupowana jest waluta celem spłaty raty.

Jest to szwindel jeszcze większy niż obecne Amber Gold. Mamy rok 2012 i kredytobiorcy stawiani są często w sytuacji krytycznej spowodowanej:
1. Wzrostem wartości kredytu - spowodowane wzrostem wartości franka szwajcarskiego CHFPLN (i nie tylko, ale jako mój przypadek z tą walutą piszę artykuł wzorując się na franku).
2. Spadkiem cen nieruchomości wywołanym pęknięciem sztucznie napompowanej bańki, na której zyskali głównie deweloperzy dzięki wyżowi demograficznemu oraz programowi "Rodzina na Swoim".

Oba powyższe punkty sprawiły, że banki czują się mniej zabezpieczone i żądają nowych zabezpieczeń kredytu. Tutaj wystosuję kilka pytań:
1. Czy istnieje jeszcze pojęcie "ryzyko kredytowe". Jeśli tak, proszę o odpowiedź, na czym ono polega.
Obecne czasy pokazują, że nawet spekulacyjne banki mogą liczyć w ostateczności na podatników, gdyż są "za duże, żeby upaść" (ale o tym innym razem).
2. Czy banki przy udzielaniu kredytu nie zawarły z klientem umowy,  w której to obie strony świadome są podejmowanego ryzyka?
3. Czy żaden pan w białej koszuli (zwany analitykiem) w 2008r. nie przewidywał tego, co stanie się po 2010r. z rynkiem nieruchomości?

Jestem pewien, że doskonale wiedziały. Na polu walki pozostał osamotniony Kowalski, od którego żądać się będzie wykupienia dodatkowego ubezpieczenia kredytu. Mało jeszcze bankom zysków, które z roku na rok rosną. Trzeba wyciągać z Kowalskiego ile wlezie.

W przypadku odmowy wykupienia takiego dodatkowego - często bardzo kosztownego - ubezpieczenia jest wypowiedzenie kredytu przez bank. Stawia do klienta w sytuacji, w której musi od razu spłacić cały kredyt.
Jeśli to nie poczyni, następuje egzekucja z majątku i jego sprzedaż. Chodzi oczywiście o mieszkanie.
Różnica między kwotą pozyskaną ze sprzedaży mieszkania i wartości kredytu dalej ciąży na kliencie.
Pomijam tutaj niemałe koszty całego postępowania, które również obciążą klienta.

Do realizacji tego diabolicznego planu brakuje jednak jeszcze jednej rzeczy, ale o tym dalej.

Gdzie zatem jest KNF?

KNF nie próżnuje i leci z odsieczą. Komu?  Z pewnością nie Kowalskiemu.
Jak wyczytać możemy, instytucja ta zamierza "wymusić" na bankach, by stworzyły szczegółową bazę nieruchomości oraz jej wartości - włączając w to aspekty, czy też wydarzenia mające wpływ na wartość danej nieruchomości.

KNF tłumaczy to potrzebą monitorowania rynku kredytów. Ja jednak widzę inne dno (a nawet dwa) tego ruchu.

1. Bankom niełatwo jest egzekwować wykup dodatkowego ubezpieczenia. Wymaga do często kosztownego wykonania wyceny nieruchomości w sytuacji, gdy klient od razu nie ulega żądaniu banku.
Bank sam sobie ustala taką wartość. Klient, który nie z nią się nie zgadza, musi dokonać wycenę (a więc za nią zapłacić). Nie zawsze to jednak bankowi wystarcza, ale to inna kwestia. Na odsiecz idzie KNF, który zapewne za jakiś czas ustali wartości z tej przecudownej bazy za "wiążące" i znacznie ułatwi to egzekwowanie od biednych owieczek wykupu ubezpieczeń.

2. Od długiego czasu mówi się o podatku katastralnym. Byłby to niezły zastrzyk gotówki dla zrujnowanego na własną prośbę budżetu. Na drodze stoi jednak niemożność nałożenia takiego podatku, gdyż niełatwe jest
dokonanie wyceny nieruchomości, na podstawie to której naliczony ma być kataster.
Słyszy się pomysłach naliczania podatku w zależności od odległości od centrum miasta itd., ale raczej szybko zdają sobie rządzący sprawę, jak idiotyczne są to pomysły. Z resztą niebawem uzyskają pomoc powstałej w 2006r. instytucji.

Czy ważna jest afera Amber Gold w cieniu tej sprawy?. Bohater jednej z reklam rzekłby "pikuś", przy całej tej aferze związanej z rynkiem nieruchomości oraz udzielonymi kredytami i związanymi z tym działaniami instytucji finansowych, a także publicznych.

Mam nadzieję, że z czasem zacznie się o tym mówić coraz głośniej i zawita to na żółtych paskach w telewizji
informacyjnych.

Póki co życzę Wszystkim niskiego kursu waluty obcej, szczególnie franka szwajcarskiego.
Od gilotyny uratował wszystkich umoczonych szwajcarski bank centralny, "usztywniając" kurs franka do euro na poziomie 1.2. Gdyby nie ten ruch, zapewne zobaczylibyście niedawno "5kę" przy kursie tej waluty do polskiego złotego.

Wiem, że życzenie to nie spodoba się to coraz liczniejszej rzeszy polskich imigrantów, ale nie oszukujmy się - i tak nie macie zamiaru wrócić do kraju :-) więc tak mocno nie stracicie, bo nie będzie potrzeby przewalutowania swoich franków, funtów, koron norweskich na złote.

Eksporterzy jakoś sobie poradzą :) CHF w końcu to nie EUR :)

Umoczeni sprzedadzą nieruchomość, spłacą kredyt, albo po prostu go przewalutują i przestaną być narażonymi na skubanie przez banki spekulantami.

Tuesday, June 19, 2012

Django verbose_name in your template

There are many situations when you want to display model field verbose_name in template when displaying tables and other data. Ideally you would put this value in header section of your table for DRY purposes.
The problem is that it's unable to use model _meta in templates. You can achive the same using an example verbose_name_tags.py templatetag.
# verbose_name_tags.py

from django import template

register = template.Library()

def get_field_verbose_name(instance, arg):
    return instance._meta.get_field(arg).verbose_name
register.filter('field_verbose_name', get_field_verbose_name)

def get_queryset_field_verbose_name(queryset, arg):
    return queryset.model._meta.get_field(arg).verbose_name
register.filter('queryset_field_verbose_name', get_queryset_field_verbose_name)

you can get your header names rendered the DRY way.
Assuming your model looks like this:
class Product(models.Model):
    name = models.CharField(_(u"Name"), max_length=20)
    weight = models.PositiveIntegerField(_(u"Weight"))

Your template would be similar to the one below:
{% load verbose_name_tags %}

<table>
  <thead>
  <tr>    
    <th>{{ object_list|queryset_field_verbose_name:"name" }}</th>
    <th>{{ object_list|queryset_field_verbose_name:"weight" }}</th>
  </tr>
  </thead>
  <tbody>
{% for product in object_list %}    
  <tr>
    <td>{{ product.name }}</td>
    <td>{{ product.weight }}</td>
  </tr>
{% endfor %}
</table>

What if you need sorting ?
For some time I've been using a django-sorting for sorting my tabular data.
The new code
{% load sorting_tags %}
{% autosort object_list %}

<table>
  <thead>
  <tr>    
    {# _("Name").. need to specify the display name all the time.. bad.. #}
    <th>{% autosort 'name' _("Name") %}</th>
    <th>{% autosort 'weight' _("Weight") %}</th>
  </th></tr>
  </thead>
  <tbody>
{% for product in object_list %}  
  <tr>
    <td>{{ product.name }}</td>
    <td>{{ product.weight }}</td>
  </tr>
{% endfor %}
  </tbody>
</table>

Great, but this is not DRY! You can do this the other way. Note: I think {% autosort %} should take verbose_name by default (maybe will take a look into the code later, but for now we can achieve the same using a {% with %} and verbose_name_tags.py template tags.
{% load sorting_tags %}
{% load verbose_name_tags %}

{% autosort object_list %}

{% endfor %}
<table>
  <thead>
  <tr>
    {% with name=object_list|queryset_field_verbose_name:"name" %}    
    <th>{% autosort 'name' name%}</th>
    {% endwith %}
    {% with weight=object_list|queryset_field_verbose_name:"weight" %}
    <th>{% autosort 'weight' weight %}</th>
    {% endwith %}
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>{{ product.name }}</td>
    <td>{{ product.weight }}</td>
  </tr>
  </tbody>
</table>

Now we can just change verbose_name in models.py and we don't have to worry about the table headers.
You can also use a "field_verbose_name" for model instances:
{# assuming {{ product }} is an instance of Product model #}
{% load verbose_name_tags %}

{{ product|field_verbose_name:"name" }}: {{ product.name }}

Some might wonder why didn't I simply use one templatetag name which would identify if it checks for queryset or instance field. I decided to use different names to quickly see If I act with querysets or instances. Hope you like it.

Thursday, June 14, 2012

Twitter Bootstrap button radius (rounded corners) size

Twitter Bootstrap is a great tool from Twitter. It's a CSS framework (using lesscss) that lets you create your own CSS stylesheets easily.

In one of projects I needed to decrease the size of buttons radius, which is too huge by default (~5px).

Thanks to .border-radius() mixin you can change button radius.

Here's the code I put in my own .less file.
You can put this in bootstrap.less (where necessary imports are included) file and recompile.

The file below changes radius to 1px.

@new_radius: 2px;

.dropdown-menu {
        .border-radius(0px);
}
.btn {
        .border-radius(@new_radius);
}
.btn-group > .btn {
  .border-radius(@new_radius);
}
.btn-group > .btn:first-child {
        .border-radius(@new_radius 0 0 @new_radius);
}
.btn-group > .btn:last-child,
.btn-group > .dropdown-toggle {
        .border-radius(0 @new_radius @new_radius 0);
}
.btn-group > .btn.large:first-child {
        .border-radius(@new_radius 0 0 @new_radius);
}
.btn-group > .btn.large:last-child,
.btn-group > .large.dropdown-toggle {
        .border-radius(0 @new_radius @new_radius 0);                                                                         
}
.btn-group > .dropdown-toggle:first-child {
        .border-radius(@new_radius);
}
.btn-group > .btn-large:only-child,
.btn-group > .btn:only-child {
        .border-radius(@new_radius);
}

Hope you find it useful!

Wednesday, April 11, 2012

Clearly and IReader - Safari Reader in your Chrome and Firefox

If you are a Safari user and use the "reader" function you will find such functionality missing in Google Chrome / Firefox.

IReader @ Chrome

Safari Reader is a nice builtin function that makes reading long articles easy. It removes banners so the article looks more like reading a plain book.
There are 2 extensions I found nice.

1. IReader (Chrome / Firefox )
2. Clearly (Chrome / Firefox )


Clearly @ Chrome
Try both. Haven't read the code, but seems like IReader parses the HTML code on site load what makes site loading slower (it's just my assumption). When IReader is available for the page, new icon appears in omnibox.
Clearly icon is always visible in the extensions box.

Yeah.. I'm a strong chrome extensions opponent.. it makes Chrome slooower.





Tuesday, March 27, 2012

Gunicorn + Nginx - a much better way to deploy your Django website

I've been a lighttpd + FastCGI django user for a long time.
Now there's a better solution -> gunicorn.

What is a gunicorn?
Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.
If you are a fastcgi user, please check gunicorn asap. I'm sure you will get much more from your hardware :-)
Ps: Note that you get the most if database is not the bottleneck. That's why you should use cache everywhere if possible.

Here's my script for gunicorn + daemontools for supervising:
#!/bin/sh

# SITE_PATH is a directory where settings.py is stored
SITE_PATH=/var/www/django/projects/my_project/; export SITE_PATH
USER=www-data
TCP=127.0.0.1:8888

exec setuidgid $USER /usr/bin/gunicorn_django -b ${TCP} -w 3 --max-requests=1000 ${SITE_PATH}settings.py 2>&1

Seems like wsgi is also recommended with the new 1.4 version of Django
If you’re new to deploying Django and/or Python, we’d recommend you try mod_wsgi first. In most cases it’ll be the easiest, fastest, and most stable deployment choice.
Check django site for more information.

UPDATE:
Forgot to mention that I've switched from lighttpd to Nginx.
You can use the script above with the following nginx config:
server {
    listen   xx.xx.xx.xx:80;
    server_name domain.com www.domain.com;
    root /var/www/django/projects/my_project/public;
    access_log /var/log/nginx/domain-access.log;

    rewrite ^/admin(.*) https://domain.com/admin$1 permanent;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /media {
        root /var/www/django/projects/my_project/django/contrib/admin;
    }
    location /static {
        root /var/www/django/projects/my_project/site_media;
        access_log off;
    }
    location /site_media {
        root /var/www/django/projects/my_project;
    }
    location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For  $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_connect_timeout 10;
        proxy_read_timeout 10;
        proxy_pass http://127.0.0.1:8888/;
    }

}

There's a trick you need to apply to get the real ip of the request source as seen in the line:
proxy_set_header X-Forwarded-For  $remote_addr;

Here's a code I'm using:
class XForwardedForMiddleware(object):
       def process_request(self, request):
        if "HTTP_X_FORWARDED_FOR" in request.META:
            ip = request.META["HTTP_X_FORWARDED_FOR"]
            if ip.startswith('::ffff:'):
                ip = ip[len('::ffff:'):]
            request.META["REMOTE_ADDR"] = ip
            request.META["REMOTE_HOST"] = None

Add XForwardedForMiddleware to your settings.py MIDDLEWARE_CLASSES setting.

Friday, March 2, 2012

Django HTML5 date input widget with i18n support and jQueryUI DatePicker fallback

Here's my manual for creating a HTML5 form date input with jQueryUI Datepicker and multilingual support.

What we need is:
1. django-floppyforms
2. jQueryUI Datepicker + jQuery 3. Modernizr javascript library
and of course - Django
You need to have some basic python, django & jquery knowledge.
Make sure multilingual support is enabled:
USE_I18N = True

and request context available for template
#settings.py

TEMPLATE_CONTEXT_PROCESSORS = (
  # ...
  'django.core.context_processors.request',
  # ...
)
In your base template, you can use LANGUAGE_CODE variable.
<html lang="{{ LANGUAGE_CODE }}">

There are 3 ways to get this running

1. Totally django way - prividing django form & widget with the "lang" variable"

Your calendar widget needs to gain information about the language, so you need to pass a variable to your form, which then sends this variable to the widget.
def add_view(request):
    
    form = SomeForm(data = request.POST, lang=request.LANGUAGE_CODE)
    if form.is_valid():
        do_some_stuff()

from django import forms
from common.widgets import DatePicker

class SomeForm(forms.ModelForm):
    data = forms.CharField()
    when = forms.DateField()

    def __init__(self, *args, **kwargs):
        lang = kwargs.pop('lang')
        super(SomeForm, self).__init__(*args, **kwargs)
        
        self.fields['when'].widget = DatePicker(lang=lang)
import floppyforms as forms
from django.conf import settings

class DatePicker(forms.DateInput):
    template = "common/datepicker.html"

    def __init__(self, *args, **kwargs):
        self.lang = kwargs.pop('lang')
        super(DatePicker, self).__init__(*args, **kwargs)
    
    def get_context(self, name, value, attrs):
        ctx = super(DatePicker, self).get_context(name, value, attrs)
        ctx['lang'] = self.lang
        ctx['STATIC_URL'] = settings.STATIC_URL
        return ctx
The below, could be attached to python render() method in the widget, but I think this is more elegant way, and thanks to floppyforms we get native HTML5 date widget if available. I am aware of that floppyforms has an example of html5 datepicker, but the way the floppyforms author checks for date type doesn't seem to work - that's why I've used modernizr.
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       $(function () {
         $.datepicker.setDefaults($.datepicker.regional['{{ lang }}']);
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>

What the above code does is check if
<input type="date" />
is available. If not, it runs the jqueryUI calendar widget.
The javascript attached is to the specified selector. I've decided to go this way as I needed the calendar widget on ajax loaded content.
This is a Django (well.. not the sortest way).

2. Javascript way. Less django form messing.

You can achieve the similar effect without prividing both SomeForm & DatePicker widget with the lang variable.
import floppyforms as forms

class DatePicker(forms.DateInput):
    template = "common/datepicker.html"
Let the form know the widget it should use
from django import forms
from common.widgets import DatePicker

class SomeForm(models.Model):
    data = forms.CharField()
    when = forms.DateField(widget=DatePicker)
In the widget template code, you need jQuery for language detection.
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       
         $(function () {
         var lang = $('html').attr('lang');
         $.datepicker.setDefaults($.datepicker.regional[lang]);
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>
but I'm affraid this might not work on ajax loaded content where you need your calendar widget.

3. Django way, without messing the widget code too much.

Another way could be simply providing the form with data-lang attribute. The form would then look as follows:
from django import forms
from common.widgets import DatePicker

class SomeForm(forms.ModelForm):
    data = forms.CharField()
    when = forms.DateField()

    def __init__(self, *args, **kwargs)
        lang = kwargs.pop('lang')
        super(SomeForm, self).__init__(*args, **kwargs)

        self.fields['when'].widget = DatePicker(attrs={'data-lang': lang})
and then in the datepicker.html
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       $(function () {
         $.datepicker.setDefaults($.datepicker.regional[$('#{{ attrs.id }}').attr('data-lang'));
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>

The 2 ways above have no {{ STATIC_URL }} in widget template available. You need to get it yourself, via templatetag, or simply using hardcoded url.

Hope someone gets some tips from this manual

There for sure are many other ways, feel free to share them. Remember to include javascript modernizr, jQuery and jQuery files!

Thursday, February 9, 2012

Google Chrome slow? Disable extensions and get the real speed

I'm a Google Chrome fan since early versions. With every new version it consumed more memory. Today, after upgrading it to v. 17 I have decided to try Firefox v.10 What I saw was faster site rendering! Though FF seems to be less responsive (especially when switching tabs) sites were loading faster. It also  consumed less memory.

Finaly, I have disabled all chrome extensions with --disable-extensions switch. Viva Chrome! It's fast again!

If your chrome gets slower, disable extensions. You won't get fast page loads with extensions enabled.

Here's the extension list I was using:

  • Last Pass, 
  • Page Rank, 
  • Gmail Checker+, 
  • Firebug Lite, 
  • Smooth Gestures, 
  • RSS Finder, 
  • FlashBlock (that's the most suspicious one regarding the page load imho).
Question is "Is Google Chrome worth using without some of these extensions?" ...

Edit: You can also speed up Chrome with RAMDisk .
In my Chrome shortcut I added flags as follows:

--disk-cache-size=26214400  --media-cache-size=26214400 --disk-cache-dir=g:/ --purge-memory-button

This limits cache size to ~26 Megabytes.





Wednesday, February 1, 2012

Django language set in the database field applied on login

In one of my Django projects available only for logged in users I needed to set language setting in the database, so user can change the language setting in his profile.

Here comes a handy "user_logged_in" (Django >= 1.3) signal which lets us do things after user is logged in. In our case, we modify session and change the language.

My user_details.models.py:
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.conf import settings

class UserProfile(models.Model):
    user = models.OneToOneField(User, blank=Flse)
    language = models.CharField(verbose_name=_(u"Interface Language"), 
                   max_length=4, choices=settings.LANGUAGES)

@receiver(user_logged_in)
def lang(sender, **kwargs):
    lang_code = kwargs['user'].userprofile.language
    kwargs['request'].session['django_language'] = lang_code


settings.py

gettext = lambda s: s

LANGUAGES = (
    ('pl',gettext('Polski')),
    ('en',gettext('English')),
    ('de',gettext('Deutsch')),
)