Jak zrobić startup w dwa i pół tygodnia?
Słowo wstępu – będąc na urlopie odwiedziłem rodzinne strony. W międzyczasie wylegiwania się na słońcu i robienia kilometrów na rowerze, namówiłem moją mamę do zmianę podejścia do publikacji treści w Internecie. Ma ona dużo pomysłów, content generuje ciekawy i unikalny, po różnych stronach go rozmieszcza, ale nie ma z tego żadnych korzyści. Wszystko co publikuje w różnych serwisach jest własnością tych serwisów. Strata czasu. Postanowiliśmy, że podzielę się moim hostingiem, na którym mam dużo wolnego miejsca i będzie mogła sobie zacząć w normalny sposób blogować – w Wordpressie. Musiałem tylko wymyślić nazwę domeny… I wymyśliłem.
W sobotę 31 lipca kupiłem domenę chwila-dla-siebie.pl. Coś poszło nie tak i strefa w DNSie po stronie panelu administracyjnego mojego providera się nie utworzyła jak należy. Konfiguracja DNS’ów musiała więc poczekać do poniedziałku, kiedy ostatecznie zgłosiłem błąd i został on rozwiązany ręcznie.
Pomysł i realizacja
Kiedy oczekiwałem na dostęp do strefy, przypomniało mi się, że 10 lat temu pewien człowiek na swojej stronie z przepisami kucharskimi obwieścił wszem i wobec, że stronę zamyka, zabiera swoje wiaderko, grabki i opuszcza piaskownice. Stwierdziłem wtedy, że szkoda, żeby się to wszystko zmarnowało, bo korzystałem z tej strony czasami. Cała strona przeleżała u mnie w archiwach jakieś 10 lat. Jako dodatek do bloga, stwierdziłem, że taka strona z przepisami będzie strzałem w dziesiątkę… i zacząłem pisać…
Po ponad dwóch tygodniach aplikacja prezentuje się następująco:
Poniżej zamieszczam loga dzień po dniu jej tworzenia
dzień 1, niedziela
- Napisałem pierwotną wersję parsera do przepisów, analizujący dokumenty html i wypluwający łatwiejsze do przetwarzania za pomocą wyrażeń regularnych pliki tekstowe. Okazało się, że dysponuję 6182 przepisami.
- Przygotowałem sobie szkielet aplikacji. Wykorzystałem do tego kod mojego bootstrapa Django / JavaScript / CSS (link do opisu na innym moim blogu, w języku angielskim). Dla interfejsu użytkowników wykorzystałem mój bazowy template – z layoutem pionowym (nagłówek, treść, stopka).
dzień 2, poniedziałek
- modele – przygotowałem modele dla kategorii i przepisów i spędziłem trochę czasu na dopracowanie wyświetlania tych informacji w panelu admina Django.
dzień 3, wtorek
- Zdeployowałem aplikację na serwerze. Przygotowałem konfigurację serwera www, wrzuciłem sztampowe template’y do wyświetlania błędów. Aplikacja póki co działała na screenie, a serwer WWW obsługiwał zapytania jako reverse proxy.
dzień 4, środa
- Dodałem layout poziomy (kategorie, przepisy, sidebar). Strona dostosowuje swój rozmiar do szerokości okna przeglądarki, chyba, że jest ona mniejsza niż zadeklarowana minimalna szerokość.
dzień 5, czwartek
- Napisałem sobie kod do obsługi paginatora przepisów i opisałem to w poprzednim wpisie
dzień 6, piątek
- Zaimplementowałem wyświetlanie kategorii, paginację przepisów i wyświetlanie konkretnego przepisu.
dzień 7, sobota
- Napisałem obsługę wyszukiwania przepisów. kod póki co nie jest specjalnie wydajny, ale został napisany tak, aby zastosowanie cache’owania było proste.
- Zrobiłem też prosty, brzydki design dla layoutu. Jakieśtam obrazki, żeby strona nabrała kształtów, które mnie osobiście dają motywację do dalszej pracy.
dzień 8, niedziela
- Dodałem templatkę do dodawania przepisów i jej obsługę, następnie uzupełniłem obsługę o mailowanie do mnie przez aplikację informacji o nowym przepisie.
- Dodałem do przepisu przyciski „lubię to” z Facebooka i „dodaj na Śledzika”.
dzień 9, poniedziałek
- Trochę się przyłożyłem, pomyślałem i narysowalem nowy design dla layoutu. Narysowałem kilka podstawowych backgroundów i ustawiłem je w css’ie. Wyszło wystarczająco zadawalająco, żebym mogło tak zostać
dzień 10, wtorek
- Podzieliłem logo na dwie części i poprawiłem je od strony kodu – chciałem aby po wyłączeniu styli, dokument wyglądał tak jak powinien (tagi h1).
- Dodałem model News’ów, dodałem możliwość edycji do interfejsu Django Admin, news początkowo wpisany w templatkę przeniosłem do skryptu inicjalizującego bazę, a do templatki wstawiłem kod wyświetlający Newsy z bazy. To akurat pamiętam, bo całe mieszanie z newsami zajęło mi tyle ile trwały Fakty na TVN’ie
- Zrobiłem trochę porządków w modelach, dodałem obsługę vars’ów i memcache, również z mojego frameworka, które wcześniej uznałem za tymczasowo zbędne.
dzień 11, środa
- Wyczyściłem bazę i zaimportowałem ponownie przepisy. Założyłem też konto mojej koleżance (którą z tego miejsca gorąco pozdrawiam
), która pomogła mi tego dnia sprawdzić 150 przepisów
- Dodałem osoby które to lubią z Facebooka (ta większa wersja, z ryjkami) do strony głównej.
dzień 12, czwartek
- Dodałem funkcjonalność zapisywania historii zmian w przepisach z interfejsem dla superusera w Django Adminie, umożliwiającym przeglądanie wprowadzonych zmian i ich cofania (Undo).
dzień 13, piątek
- Zarejestrowałem się w jednej z sieci afiliacyjnych, a następnie w jednym z programów partnerskich. W międzyczasie weryfikowaliśmy kolejne przepisy, których było już 320.
- Dodałem reklamę z Google Adsense, przycisk „Share” z Facebooka.
- Wrzuciłem też na stronę favicona.
dzień 14, sobota
- Weryfikacja kolejnych 100 przepisów.
- Usunąłem z głownej Facebookowe osoby które to lubią, założyłem kuchni stronę na Facebooku i dodałem „Like Boxa” do ów Facebookowej strony.
- Dodałem do strony kod śledzenia Google Analytics oraz dodałem stronę do Google Webmaster Tools
dzień 15, niedziela
- Zmieniłem obsługę aplikacji przez serwer WWW, obecnie pracuje jako wsgi. Bawiłem się też w zmienianie Suzuki Hayabuzy we włochatego potwora w Gimpie (efekty pracy udostępnione na moim profilu Facebookowym
)
dzień 16, poniedziałek
- Gimp i przygotowanie trzech obrazków reklam do sidebara. Wrzuciłem obrazki na serwer, przygotowałem szkielet modułu adbox, który zawierał tablicę z reklamami i parametrami ich wyświetalnia i zaimplementowałem statyczne wyświetlanie losowej reklamy z tablicy.
dzień 17, wtorek
- Napisałem fadera do reklam w javascripcie, który dzięki wspomnianemu na początku bootstrapowi, eksportował mi je z pythona za pomocą jsona do javascriptu, automagicznie się includował, rozpoznawał które to reklamy ma animować, a następnie je animował
Chodziło mi to po głowie od jakiegoś już czasu, coś podobnego ma Allegro na głównej.
A tymczasem…
W międzyczasie oczywiście chodziłem do pracy i robiłem tam inne rzeczy. Musiałem też trochę czasu poświęcić mojemu samochodowi, który w trakcie tych dni ucierpiał przed parkingiem pod pracą, bo w moje drzwi wjechało Mondeo. Spisywanie, telefony, oględziny, lakiernik, pisma itp. Straszne pożeracze czasu. No ale dałem radę.
Acha, i spałem.
Startup nie startup
Zdaję sobie sprawę, że specjaliści od Web 2.0 i social networkingu, np. Grzegorz Marczak z Antyweb’a prawdopodobnie mieliby zastrzeżenia do określania tej aplikacji mianem startupu, a gdyby miał go na swoim blogu opisywać, zebrałbym jako autor więcej batów niż ta aplikacja ma linijek kodu, ale co tam.
Wszystkich Was na stronę aplikacji serdecznie zapraszam – jest ona dostępna pod adresem kuchnia.chwila-dla-siebie.pl (oraz jako skrót z tej domeny: nme.pl/kuchnia).
W następnym wpisie przedstawię jak wygląda obecna konfiguracja panelu Django Admina aplikacji.
Helper dla Pagination w Django
Wyświetlanie dużej ilości pozycji w aplikacji webowej jest zwykle związane z koniecznością zastosowania paginacji stron. Dobra nowina – Django takowego posiada. Zła nowina – brakuje w nim jednej prostej funkcji.
Ów funkcją jest zawężenie ilości stron do określonej ilości (lub odległości od aktualnie wybranej) i wstawienie wielokropków w określonych miejscach. Śpieszę więc z moim rozwiązaniem.
Aktualizacja: W kodzie funkcji został poprawiony drobny błąd logiczny oraz dodana została obsługa czegoś co nazwałem bounce – czyli „odbijania się”; polega to na tym, że jeśli wybrana jest strona druga, zakres wynosi 3, to z prawej strony widzimy elementy 3, 4, 5 oraz dzięki odbiciu – 6, 7, które powinny być widoczne po lewej stronie, ale wykroczyłoby poza zakres dostępnych stron. Podobnie paginator zachowa się, jeśli odwiedzalibyśmy ostatnie strony. Ta niewielka poprawka sprawia, że paginator zachowuje się znacznie przyjaźniej dla użytkownika. Przykładowe zastosowanie można objerzeć na stronie, dla której go stworzyłem – to strona z przepisami kuchennymi
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def ranged_pages (pages, current, dist, border):
""" Zwraca fragment listy /pages/ zawężoną do /dist/ wokół /current/
oraz oddalone o /border/ od początku i końca listy """
if bounce:
bounce_left = pages.index(current)-dist
bounce_right = len(pages)-pages.index(current)-1-dist
if bounce_left < 0:
dist += abs(bounce_left)
if bounce_right < 0:
dist += abs(bounce_right)
return map(lambda x: [None,x][(x > current-dist-1) &
(x < current+dist+1) | (pages.index(x) < border) |
(pages.index(x) > len(pages)-border-1)],
filter(lambda x: (x > current-dist-1) &
(x < current+dist+1) | (pages.index(x) < border+1) |
(pages.index(x) > len(pages)-border-2), pages))
# przykładowe zastosowanie:
pages = map(lambda x:x,range(1,25))
print 'dane wejsciowe:\n%s\n' % pages
print 'aktualny:%d, dystans:%d, od_granic:%d\n%s\n' % \
( 12,3,2, ranged_pages(pages, 12, 3, 2))
print 'aktualny:%d, dystans:%d, od_granic:%d\n%s\n' % \
( 5,4,3, ranged_pages(pages, 5, 4, 3))
Funkcja ranged_pages zajmuje się całym zadaniem. Jest ona jednocześnie przykładem jak można pisać efektywnego jednolinijkowca, który jest nieczytelny i brzydki jak noc.
Parametry które przyjmuje funkcja to kolejno:
- lista stron z
Pagination(objects).page_range - indeks aktualnej strony
- ilość stron na lewo i prawo od aktualnej, które mają być wyświetlane
- ilość stron od początku i końca listy
Po uruchomieniu przedstawionego wyżej przykładu dostajemy następujący wynik:
dane wejsciowe:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24]
aktualny:12, dystans:3, od_granic:2
[1, 2, None, 9, 10, 11, 12, 13, 14, 15, None, 23, 24]
aktualny:5, dystans:4, od_granic:3
[1, 2, 3, 4, 5, 6, 7, 8, 9, None, 22, 23, 24]
Pozycję None oczywiście podmieniamy wielokropkiem. Robimy to już w konkretnym template.
Dlaczego nazwałem to helperem? Taką konwencję przyjąłem już dawno temu – większość funkcji konkretnej aplikacji które wykorzystywane są przez views.py umieszczam w pliku helpers.py z którego importuje do widoków. Rozwiązanie sprawdza się świetnie, szczególnie jeśli do kodu jakiegoś projektu nie zaglądam zbyt często – nie trzeba się głowić gdzie takie usprawniacze trzymamy.
Jeśli w swoim kodzie nie stosujesz zbyt często funkcji lambda, map, reduce i filter, uważam, że warto zapoznać się z ich dokumentacją. Inne przykłady ich praktycznego zastosowania przedstawiłem we wpisie po angielsku opisującym prosty konwerter CIDR na maskę bitową.
Podsumowanie ostatnich miesięcy
Bardziej spostrzegawczy którzy tutaj zaglądają pewnie zauważyli, że od kilku miesięcy nie pojawił się na tym blogu żaden wpis – śpieszę więc z wyjaśnieniami.
Na nme.pl, poza podstawowym blogiem, znajduje się jeszcze odrębna kategoria z własnym kanałem RSS – zawierająca wpisy w języku angielskim. Trochę głupio to wyglądało, że widoczna była jedynie pusta kategoria – dlatego trafiło tam kilka wpisów. Z bloga zniknęły linki do mojego minibloga i niedawno założonego mikrobloga. Uznałem, że nie są tu potrzebne – jedynie zaciemniają wizerunek bloga, który miał być z założenia techniczny.
Od mniej więcej miesiąca pracuje sobie nad nowym projektem w Django. Na chwilę obecną aktualny jego stan można zobaczyć na stronie urevs.com. Informacje na temat postępów prac opisuje na blogu pod adresem blog.urevs.com. Projekt w obecnej postaci wydaje się być jeszcze nie zaczęty, ale zawiera już wiele funkcjonalności:
- możliwość zakładania kont które będą identyfikatorami OpenID (czyli OpenID Provider), podczas zakładania kont wykorzystana została reCAPTCHA
- Aby zalogować się w serwisie nie trzeba mieć założonego lokalnie konta – można zalogować się poprzez m.in:
- Twitter OAuth
- Facebook Connect
- Federated Login for Google Account Users
- Yahoo! ID
- OpenID (gotowe skróty m.in do WordPress.com, Blogger itd)
- Serwis obsługuje https, lokalizację językową (obecnie dostępne są języki polski i angielski) i mój bootstrap do rozwijania aplikacji JavaScript i jego rozszerzenie – do generowania CSS’ów.
W międzyczasie muszę się jeszcze zabrać do roboty i dokończyć odświeżoną wersję mojego portalu do zarządzania siecią, którą już kończę przenosić na Django. Zabrałbym się pewnie prędzej, gdyby nie kompletny brak motywacji… W końcu trwa lato… i nie chce się tyle czasu siedzieć nad mądrymi projektami
Wygodny storage z użyciem memcache w Django
Czasami zachodzi konieczność napisania na szybko aplikacji w Django która standardowo coś tam trzyma w bazie danych, coś przetwarza i wyświetla. Jeśli aplikacja ma być prosta, a w bazie mają być trzymane proste struktury danych, to czy napewno musimy tyle czasu poświęcać na dopracowanie modeli? Jasne, że nie.
Poniższy kod oferuje nam możliwość trzymania dowolnych danych w postaci klucz-wartość w bazie danych. Dodatkowo – owe dane mogą mieć dowolną postać – może być to tekst, tablica asocjacyjna czy wartość typu Boolean.
Ponadto – biblioteczka ta opiera się o opisaną w poprzednim wpisie obsługę memcache’u w Django – co sprawia, że do póki nie zmienimy wartości danych, a ich obecność w cache’u nie zdąży wygasnąć – nie będziemy w ogóle obciążać naszej bazy danych. Osobiście wykorzystuję ten kod do trzymania ustawień aplikacji. Niektóre ustawienia są typu boolean, inne to stringi, jeszcze inne – tabele. Rozwiązanie sprawdza się świetnie.
Właściwy kod
Modele – models.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 nme.pl
# Dual licensed under MIT and GPL.
from django.db import models
class Vars (models.Model):
key = models.CharField(max_length=50)
value = models.TextField()
Proste prawda? Nie zapomnijcie wydać komendy django-admin syncdb która utworzy odpowiednią tabelę w bazie. Teraz czas na właściwą bibliotekę – vars.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 nme.pl
# Dual licensed under MIT and GPL.
""" Simple variables (key-value) handling with memcache """
import pickle,base64
import memcache,models
from settings import conf
class __Vars:
def get (self,key,default=None):
""" get Vars variable value """
if memcache.enabled:
value = memcache.get('va-%s' % key)
if value != None:
return pickle.loads(base64.b64decode(str(value)))
settings = models.Vars.objects.filter(key=key)
if settings:
value = settings[0].value
if value:
return pickle.loads(base64.b64decode(str(value)))
else:
return default
else:
return default
def __getitem__ (self,msg):
return self.get(key)
def set (self,key,value):
""" set Vars variable value """
value = base64.b64encode(pickle.dumps(value))
if memcache.enabled:
memcache.set('va-%s' % key,value)
settings = models.Vars.objects.filter(key=key)
if settings:
setting = settings[0]
setting.value = value
setting.save()
else:
models.Vars(key=key,value=value).save()
def has_key (self,key):
""" return True or False if Vars variable exists """
settings = models.Vars.objects.filter(key=key)
if settings:
return True
else:
return False
def delete (self,key):
""" deletes given key from Vars and memcache """
if memcache.enabled:
memcache.delete('va-%s' % key)
settings = models.Vars.objects.filter(key=key)
for i in settings:
i.delete()
variables = __Vars()
Przykładowe użycie
Zastosowanie biblioteki z poziomu django-admin shell:
In [1]: from vars import variables
In [2]: if not variables.has_key('test'):
...: variables.set('test','tekst')
...:
...:
In [3]: print variables.get('test')
tekst
In [4]: variables.set('test',{'asd':1,'data':['struct','ure']})
In [5]: variables.get('test')['data'][1]
Out[5]: 'ure'
In [6]: variables.delete('test')
In [7]: variables.get('test')
In [8]: variables.has_key('test')
Out[8]: False
Jak to działa?
Składowanie dowolnego typu danych w rekordzie bazy zrealizowane jest w oparciu o dwie standardowe biblioteki pythonowe – pickle i base64. Pierwsza z nich oferuje zakodowanie dowolnej struktury danych do stringa, druga natomiast – zastosowanie notacji base64 – przez co zabezpieczamy się przed ewentualnymi problemami z obsługą znaków z którymi nasz bazodanowy backend mógłby mieć ewentualnie problemy. Innymi słowy – rozwiązanie przedstawione powyżej gwarantuje nam pełną przenośność danych.
Zachęcam gorąco do korzystania
Memcache w Django: krok ku lepszej skalowalności
Podczas tworzenia aplikacji webowych, warto w miarę wcześnie pomyśleć o skali z jaką nasze rozwiązanie będzie miało się w przyszłości zmierzyć. Niezależnie od tego czy owa aplikacja ma pracować w jednym przedsiębiorstwie, czy próbuje zainstnieć szerzej, w sieci – w każdym przypadku może się okazać, że odniesie sukces. To z kolei sprawi, że ilość jej użytkowników urośnie… Firma może się rozwinąć, przejąć konkurencję, aplikacja webowa może się okazać takim strzałem w dziesiątkę jak nie tak dawno temu Nasza-Klasa – nigdy nie wiadomo
W momencie gdy miałaby go odnieść, nie będzie już zbyt wiele czasu na przebudowę kodu, a napewno nie będzie go na jego całkowitą reorganizację.
Jeśli rozwiązanie jest skryptem uruchamianym od święta – może to być zwykły skrypt CGI. Jeśli jednak miałby być uruchamiany częściej – wartałoby już pomyśleć o mod_php czy mod_python… Albo jeszcze lepiej – fastcgi, a w przypadku pythona – wsgi. Kiedy użycie aplikacji rośnie nadal – frontend mnożymy na kolejne węzły stawiając przed nimi balancera robiącego za reverse proxy. A w backendzie klastrujemy… ale co na styku macierz, klaster? Ilość zapytań do bazy rośnie, io waity zaczynają rosnąć i robi się nieciekawie… Da się to rozwiązać?
Jak to robi Google
Google od jakiegoś czasu pokazuje nam jak powinny być tworzone intensywnie wykorzystywane aplikacje webowe i na jak zorganizowanym backendzie powinny pracować, aby skalowały się najefektywniej. Myślę, że warto z tej wiedzy skorzystać zanim io waity zaczną spędzać nam sen z powiek.
Miałem okazję napisać sobie kilka małych aplikacji które pracują w chmurze na platformie Google – tzw. Google App Engine (w skrócie GAE). Warto było poświęcić trochę czasu aby to rozwiązanie poznać. Łatwiej było mi dzięki temu można zrozumieć między innymi dlaczego poszczególne serwery Google nie są potężnymi serwerami rackowymi z bardzo silnymi procesorami, ale raczej „lżejszymi” serwerkami, ale za to wypełnionymi po brzegi koścmi pamięci.
Dlaczego takie lżejsze maszyny? Poza oszczędnością energii i pieniędzy są one wystarczające do obsługi pythonowych frameworków, w przeciwieństwie do tych PHP’owych.
Google skupia się na Javie i Pythonie, gdzie prawie pewne jest to, że ich rozproszony Datastore – system bazodanowy, został stworzony w oparciu o język Python. Język ten znany jest m.in z wydajności, szczególnie, jeśli osoba która go używa stosuje się do najbardziej podstawowych zasad jak nie allokowanie ogromnej ilości pamięci itp. Dlaczego więc w serwerach Google tyle pamięci? Odpowiedzią jest memcache.
Google chwali się tym, że stworzyli sobie własną implementację cache, inspirowaną oprogramowaniem memcached i zachęca, aby developerzy aplikacji App Engine’owych również korzystali z memcache’a.
Na czym polega memcache
Uproszczony graf przedstawiający działanie memcache’u:
Koncepcja działania jest prosta – zamiast odwoływać się bezpośrednio do bazy danych, odwołujemy się do memcache’a – jeśli trafiliśmy na dane (hit), wykorzystujemy je, jeśli nie (miss), dopiero wtedy odwołujemy się do bazy, jednocześnie zapisując pobrane z bazy dane w naszym cache’u. Dzięki temu prostemu zabiegowi, nasza strona, która podczas każdego odwołania do niej potrzebuje jakichś danych z bazy, jest w stanie funkcjonować praktycznie w ogóle jej o nic nie odpytując – wszystko co potrzebne będzie trzymać w pamięci podręcznej.
Zastosowanie i dokumentacja
Aby wykorzystać memcache w Google App Engine, polecam zapoznanie się z dokumentacją na stronach Google.
A co jeśli chcielibyśmy wykorzystać memcache w Django? Naturalnie – nic nie stoi na przeszkodzie – Django posiada coś takiego jak cache framework, który na dodatek jest bardzo dobrze udokumentowany.
Django samo z siebie jest przystosowane do wykorzystania cache’u przy obsłudze widoków czy template’ów, ale również oferuje dostęp niskopoziomowy – niemalże analogiczny do tego z Google App Engine… Niemalże, bo jakoś wersja Google bardziej przypadła mi do gustu. Dlatego przygotowałem sobie prosty interfejs, który wydaje mi się być odrobinę bardziej logiczny. Kod oczywiście załączam. Mam nadzieję, że komuś się przyda.
Konfiguracja systemu, projektu Django oraz moja niskopoziomowa obsługa
Zakładając, że pracujemy na systemie GNU Debian/Ubuntu oraz Django jest już w systemie jakąś drogą zainstalowane (osobiście w tym akurat przypadku polecam instalację z oryginalnych źródeł nad paczką Django z dystrybucji), doinstalowujemy obługę memcached, włączamy ją i restartujemy demona memcached:
sudo -i aptitude install memcached python-memcache sed -i 's/no/yes/' /etc/default/memcached service memcached restart exit
teraz w naszym projekcie Django włączamy obsługę cache:
w pliku settings.py:
CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
jeśli chcemy wykorzystać moją małą biblioteczkę poniżej, do settings.py musimy dodać też konfigurację memcache:
class conf:
class memcache:
disabled = False
timeout = 600
Moja niskopoziomowa obsługa memcache.py:
#!/usr/bin/python
# -*- coding: utf-8 -*-
""" cache low-level backend based on memcached """
from django.core.cache import cache
from settings import conf
def get(key, default=None, timeout=conf.memcache.timeout):
""" returns value for given key from cache, and refreshes it
in memcache with automatic conversion from unicode
to str - fix to:
__import__() argument 1 must be string without null bytes, not str """
value = cache.get(key, default)
if value is not default:
cache.set(key, value, timeout)
if type(value) is unicode:
value = str(value)
return value
def set(key, value, timeout=conf.memcache.timeout):
""" sets a value for given key in cache """
cache.set(key, value, timeout)
def delete(key):
""" remove given key from cache """
cache.delete(key)
# in the name of syntatic sugar
disabled = bool(conf.memcache.disabled)
enabled = not disabled
A przykład zastosowania pojawi się jutro albo pojutrze – w nowym wpisie.
Dodatkowy wolumin Ext3 jako plik na Fat32
Wielu użytkowników Linuxa ma zinstalowane dwa systemy operacyjne na notebookach i komputerach stacjonarnych. Nie jestem tutaj wyjątkiem. Jak już mam naklejkę licencyjną to przecież jej nie zdrapię
Mojemu Linuxowemu LVM’owi dałem 30 GB, Windowsowi również, a resztę stanowi wolumin FAT32. Kiedyś reszty nie było, ale cóż – musiałem kupić nowego twardziela z powodu podejścia producentów do „ekonomicznego” trybu pracy dysków twardych
Co jednak można zrobić w sytuacji, gdy nagle potrzebujemy trochę więcej przestrzeni dyskowej z obsługą Linuxowych uprawnień? Odpowiedź jest prosta – możemy wykorzystać trochę wolnej przestrzeni FAT32, aby stworzyć sobie na nim wolumin ext3 w postaci zwykłego pliku.
Tworzenie woluminu ext3 w pliku
Aby utworzyć 15 GB wolumin ext3, przechodzimy w linii komend na dysk FAT32 i wydajemy polecenie:
cd /media/[nasz podmontowany wolumin FAT32]/ dd if=/dev/zero of=volume bs=1G count=15
Chwilkę to potrwa, ale ostatecznie powstaje wolumin:
15+0 przeczytanych recordów 15+0 zapisanych recordów skopiowane 16106127360 bajtów (16 GB), 957,413 s, 16,8 MB/s
Sprawdzamy nasz plik:
du -sh volume
15G volume
Wszystko się zgadza. Tak przygotowany plik należy teraz sformatować:
mke2fs -j volume
Zostaniemy zapytani, czy napewno chcemy formatować urządzenie które nie jest blokowe – potwierdzamy nasze intencje.
mke2fs 1.41.11 (14-Mar-2010) volume nie jest specjalnym urządzeniem blokowym. Kontynuować mimo to? (t,n) t warning: Unable to get device geometry for volume Etykieta systemu plików= Typ OS: Linux Rozmiar bloku=4096 (log=2) Rozmiar fragmentu=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 983040 i-węzłów, 3932160 bloków 196608 bloków (5.00%) zarezerwowanych dla superużytkownika Pierwszy blok danych=0 Maksymalna liczba bloków systemu plików=4026531840 120 grup bloków 32768 bloków w grupie, 32768 fragmentów w grupie 8192 i-węzłów w grupie Kopie zapasowe superbloku zapisane w blokach: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208 Zapis tablicy i-węzłów: zakończono Tworzenie kroniki (32768 bloków): wykonano Zapis superbloków i podsumowania systemu plików: wykonano Ten system plików będzie automatycznie sprawdzany co każde 39 montowań lub co 180 dni, zależnie co nastąpi pierwsze. Można to zmienić poprzez tune2fs -c lub -i.
Montowanie woluminu plikowego
Teraz możemy już podmontować sobie tak stworzony wolumin:
sudo mkdir /mnt/tmp sudo mount -o loop volume /mnt/tmp df -h
System plików rozm. użyte dost. %uż. zamont. na /dev/mapper/sys-root 22G 11G 9,8G 52% / [...] /dev/loop0 15G 166M 14G 2% /mnt/tmp
I to wszystko – wolumin jest już gotowy do użycia
Wydajna i bezpieczna zdalna synchronizacja katalogów w Ubuntu
Będąc między innymi developerem, który pracuję na różnych maszynach, potrzebuje mieć swój kod dostępny na każdej stacji przy której zasiadam.
Przez jakiś czas sprawdzał się rozproszony system kontroli wersji. Wystarczyło robić push i pull poprzez ssh. Z czasem, kiedy np. miałem rozgrzebany kod, nie chciałem robić commita, tworzyć odgałęzienia – zacząłem pakować kod i przesyłać w tej postaci. Przez jakiś czas to znosiłem. Nie było to jednak zbyt przyjemne. Kilka drobnych zmian, a okazywało się, że muszę przesyłać kilka MB danych. Nie narzekałbym, gdybym nie miał w domu łącza niesymetrycznego
Z czasem, kiedy ilość projektów nad którymi pracowałem wzrosła, poczułem pewien dyskomfort (prawie jak pani Żanet Kaleta
). Tym większy, że zdarzało mi się pracować nad aplikacją i biblioteką jednocześnie w ramach jednego projektu, gdzie oba kody miały swoje indywidualne drzewa kontroli wersji. Nawet jeśli jedno było w podgałęzi drugiego – Mercurial, bo z tego systemu kontroli wersji korzystam – zdawał sobie sprawe, że dany katalog należy już do innego drzewa kontroli wersji.
Co począć w takiej sytuacji? Rozwiązanie przypadkowo podsunął kolega, jednocześnie przypominając mi jak to rozwiązanie efektywnie spisywało się w dystrybucji Gentoo, z której miałem okazję jakiś czas temu przez dłuższy nawet czas korzystać. Chodzi oczywiście o rsync’a.
Niżej przedstawioną metodę praktykuję już od jakiegoś czasu. Synchronizuję się do/z jednej centralnej maszyny. Synchronizuję katalog zawierający 300 MB kodu, źródeł grafik, duże drzewa hg. Nie zdarzyło mi się jeszcze, mimo wykonywania dość drastycznych zmian w podstrukturze, żeby trwało to dłużej niż wcześniej trwało przesłanie spakowanej paczki o rozmiarze 1 czy 2 MB.
Załóżmy, że w katalogu domowym /home/user/dev mam swoje repozytorium. Na maszynie centralnej, dane trzymane będą w katalogu /srv/archive/dev.
Aby wysłać dane z notebooka, na serwer, wydajemy polecenie:
rsync -azv --delete -e ssh /home/user/dev MASZYNA_CENTRALNA:/srv/archive
Aby pobrać dane z serwera na notebooka wydajemy:
rsync -azv --delete -e ssh MASZYNA_CENTRALNA:/srv/archive/dev /home/user
Jeśli nie jesteśmy pewni, czy efekty komendy będą zadowalające, warto dodać parametr --dry-run, który sprawi, że zostanie nam wyświetlona jedynie lista zmian, a nic tak naprawdę nie zostanie zmienione.
Myślę, że warto poświęcić kilka minut aby wewnątrz swojej biblioteczki stworzyć katalog sync, a w nim dwa skrypty send i recv które będą wykonywać te czynności, a gwarantuje, że w zamian zaoszczędzicie znacznie więcej czasu w przyszłości.
Prosty plugin jQuery: Dynamiczny favicon
Ten prosty wpis ma za zadanie przybliżyć Wam dwa tematy jednocześnie – jak stworzyć prosty plugin do jQuery oraz jak z poziomu JavaScriptu podmienić obrazek favicon dla strony. Zamierzam przedstawić plugin który właśnie taką czynność wykonuje
jQuery chyba nie trzeba przedstawiać. Jest to w mojej opinii najłatwiejsza w użyciu biblioteka czyniąca język JavaScript prostym i wygodnym w użyciu. Co więcej – można ją bardzo łatwo rozszerzać o dodatkową funkcjonalność za pomocą pluginów. Uważam, że warto wiedzieć jak taki plugin można sobie szybko przygotować.
jquery.favicon_replace.js wygląda następująco:
(function($) {
$.fn.favicon_replace = function(url) {
$('head link').each(function() {
if ($(this).attr('rel') == 'shortcut icon') {
$(this).remove();
}
});
$('<link rel="shortcut icon" href="'+url+'" />')
.appendTo('head');
};
})(jQuery);
Jak powyższy kod działa: tworzymy closure której jako parametr podajemy strukturę jQuery. Wewnątrz closury definiujemy funkcję, która sprawdza atrybut rel każdego taga typu link wewnątrz head porównując go do shortcut icon. Znaleziony w ten sposób element jest kasowany. Następnie tworzony jest nowy link typu shortcut icon, z nowym url’em. Urla podajemy funkcji jako parametr. Closura w postaci anonimowej funkcji jest wykonywana, a w efekcie jQuery zyskuje nową funkcję.
Zastosowanie tego plugina jest proste – oto przykład:
$(document).favicon_replace('/images/new-favicon.ico');
Warto napomnieć, że favicon wcale nie musi być ikoną. Możecie równie dobrze zaserwować przeglądarce animowanego gifa, nawet z rozszerzeniem ico, który zostanie automatycznie przeskalowany przez przeglądarkę i animowany na pasku adresu
Generowanie losowych haseł w Ubuntu
Hasła, każdy ma ich przynajmniej kilka w pamięci, ale życie uczy, że nie warto stosować takich samych do autoryzacji bankowych, kont na naszej-klasie, gadu czy na roota
Chciałbym Wam przedstawić bardzo proste narzędzie z którego korzystam z powodzeniem od kilku lat – nazywa się apg (Automated Password Generator) i służy do wygodnego generowania haseł.
Narzędzie oferuje dwa rodzaje haseł – mniej zaawansowane, ale znacznie łatwiejsze do zapamiętania dla osób znających język angielski oraz bardziej zaawansowane (zawierające poza literami dodatkowe krzaczki). Za pomocą dodatkowych parametrów możemy wpłynąć na zawartość dużych, małych liter lub liczb.
Instalacja jest standardowa:
aptitude install apg
Help
Wygląda tak:
apg -h apg Automated Password Generator Copyright (c) Adel I. Mirzazhanov apg [-a algorithm] [-r file] [-M mode] [-E char_string] [-n num_of_pass] [-m min_pass_len] [-x max_pass_len] [-c cl_seed] [-d] [-s] [-h] [-y] [-q] -M mode new style password modes -E char_string exclude characters from password generation process -r file apply dictionary check against file -b filter_file apply bloom filter check against filter_file (filter_file should be created with apgbfm(1) utility) -p substr_len paranoid modifier for bloom filter check -a algorithm choose algorithm 1 - random password generation according to password modes 0 - pronounceable password generation -n num_of_pass generate num_of_pass passwords -m min_pass_len minimum password length -x max_pass_len maximum password length -s ask user for a random seed for password generation -c cl_seed use cl_seed as a random seed for password -d do NOT use any delimiters between generated passwords -l spell generated password -t print pronunciation for generated pronounceable password -y print crypted passwords -q quiet mode (do not print warnings) -h print this help screen -v print version information
A teraz praktyka
Wystarczy znajomość kilku parametrów:
Generowanie 5-ciu haseł: -n
apg -n 5 ookyopIack berpAxEr DusEphac yomlipvog~ weehacir
Generowanie haseł o minimalnej długości: -m
apg -n 5 -m 10 BlysBicWeo fefimvesBa CuAtecHeoc crejvibred CynfiShnoi
Generowanie haseł z wymową (aby łatwiej było je zapamiętać): -t
apg -n 5 -m 10 -t WaigJocWig (Waig-Joc-Wig) Geagsyinau (Geags-yin-au) Cunefkalt9 (Cun-ef-kalt-NINE) Pemyidyish (Pem-yid-yish) agsirasuk7 (ags-ir-as-uk-SEVEN)
Generowanie bardziej zaawansowanych haseł: -a 1
apg -a 1 -n 5 -m 10
N;%5RWNM|B
$48+HGvlac
{@M!yPMa-y
jyI<{\3;{i
t)nThAUw?~
Skrypt ten jest bardzo przydatny, kiedy musimy wygenerować komuś hasło ponieważ nie jest w stanie sobie czegoś wymyślić (i pewnie wymyślił by ostatecznie nazwę swojego psa lub imię małżonki) lub stawiamy następny z kolei serwer mysql, a nie chcemy zostawiać takiej dziury jak nie tak dawno ktoś z ekipy Wykopu, kiedy to wykradziono im hasła użytkowników.
Wirtualni userzy ftp w Ubuntu
Poniżej znajduje się instrukcja opisująca jak skonfigurować krok po kroku bardzo prosty i bezpieczny serwer ftp obsługujący wirtualne konta – użytkowników, których fizycznie nie ma zdefinowanych w systemie. Jako serwer ftp wykorzystany zostanie vsftpd (very secure ftp daemon).
Założenia: potrzebujemy prostego serwera ftp do którego możemy zdefiniować kilku użytkowników, gdzie każdy ma dostęp do tego samego katalogu (opis jak należy to skonfigurować, aby każdy użytkownik miał swój własny katalog znajduje się we wpisie źródłowym, którego adres zamieściłem na końcu wpisu).
Najpierw instalujemy serwer ftp oraz narzędzie do obsługi bazy użytkowników:
aptitude install vsftpd db4.2-util
Tworzymy katalog ftp:
mkdir /home/ftp chown ftp:ftp /home/ftp
(aplikacja domyślnie podczas instalacji zakłada katalog /srv/ftp)
Podmieniamy plik /etc/vsftpd.conf na następujący:
# /etc/vsftpd.conf listen=YES listen_ipv6=NO #listen_port=2121 anonymous_enable=NO local_enable=YES guest_enable=YES virtual_use_local_privs=YES pam_service_name=vsftpd-virtual guest_username=ftp local_root=/home/ftp local_umask=022 write_enable=YES hide_ids=YES dirmessage_enable=YES use_localtime=YES xferlog_enable=YES log_ftp_protocol=YES setproctitle_enable=YES connect_from_port_20=YES chroot_local_user=YES chroot_list_enable=NO secure_chroot_dir=/var/run/vsftpd/empty
W razie potrzeb – zmieniamy ustawienia – np. jeśli chcemy zmienić port na którym aplikacja ma nasłuchiwać – usuwamy znak komentarza z linii listen_port i zmieniamy numer portu.
Przygotowujemy plik /etc/pam.d/vsftpd-virtual:
auth required pam_userdb.so db=/etc/vsftpd-virtual account required pam_userdb.so db=/etc/vsftpd-virtual
Przygotowujemy plik z użytkownikami i hasłami /etc/vsftpd-konta:
uzytkownik1 haslo1 uzytkownik2 haslo2
umieszczamy hasła w bazie:
db4.2_load -T -t hash -f /etc/vsftpd-konta /etc/vsftpd-virtual.db
a następnie restartujemy usługę:
service vsftpd restart
Możemy już zalogować się do serwera, przykładowo:
lftp -p 2121 -u uzytkownik1,haslo1 localhost lftp test1@localhost:~> ls lftp test1@localhost:/>
Źródło: http://linuxforfun.net/2008/04/05/vsftpd-virtual-users/



