PHP i Python – środowisko developerskie

Podziel się z innymi!

    Jakiś czas temu zacząłem pracować dla klienta, który realizuje swój projekt w oparciu o koncepcję mikroserwisów Martina Fowlera. Nowe serwisy pisane są w Pythonie ale utrzymanie i dalszy rozwój starszych częściej architektury wymaga znajomości języka PHP. Mam ponad pięcioletnie, ciągłe doświadczenie w programowaniu w PHP, ale sięga ono czasów kiedy użycie JavaScriptu na serwerze wydawało się być pomysłem absurdalnym, kiedy konto na Naszej Klasie było tak powszechne jak dziś profil na Facebooku, a na współdzielonych hostingach królowało PHP w wersji 5.3, która zasłynęła m.in z wprowadzenia instrukcji goto.

    Odkąd porzuciłem PHP na rzecz Pythona nie tylko zacząłem tworzyć bardziej pythoniczny kod, ale wzbogaciłem też swój warsztat deweloperski o nowe narzędzia, praktyki i metodologie. Po tych kilku latach patrzę już na PHP przez pryzmat doświadczeń zdobytych w trakcie programowania w innych językach. Nie dawno miała miejsce premiera PHP w wersji 7, która zbiera dobre recenzje i ma rewelacyjne wyniki w benchmarkach. Postanowiłem zapoznać się z tą nową odsłoną – dla mnie zapomnianego już lekko – języka. Pierwsza krok jaki musiałem wykonać to instalacja najnowszej wersji PHP na komputerze.

    PHPbrew

    Dwie wersje Pythona 2 i 3 zainstalowane równolegle w systemie to standard jednak chcąc mieć możliwość skorzystania z mniej popularnych wersji warto posłużyć się dodatkowym oprogramowaniem takim jak pythonz będącego forkiem nie wspieranego już projektu pythonbrew, który oficjalnie odsyła do pyenv (nie mylić z pyvenv, o którym za chwilę).

    PHP ma swój odpowiednik pythonbrew – PHPbrew. Instalacja tego narzędzia jest bezproblemowa i szczegółowo opisana na stronie projektu http://phpbrew.github.io/phpbrew/ więc nie będę jej powielał. Po instalacji PHPbrew możemy przystąpić do instalacji samego PHP w różnych wersjach. Podobnie jak analogiczne narzędzia Pythona – PHPbrew do zainstalowania PHP oraz jego rozszerzeń wymaga także zadbania o zależności. Ja np. spotkałem się z komunikatem configure: error: Please reinstall readline – I cannot find readline.h, który w uprzejmy sposób poinformował mnie o konieczności doinstalowania pakietu deweloperskiego redline

    sudo aptitude install libreadline-dev
    phpbrew install php-5.5.9

    albo instalacji PHP bez obsługi tego rozszerzenia np w wersji 7.

    phpbrew install php-7.0.6 -readline
    

    Przełączenie się na wybraną wersję PHP wymaga wydania prostej komendy w konsoli:

    phpbrew use php-7.0.6

    Od tej chwili możemy rozpocząć pracę z wybraną wersją PHP w konsoli lub też odpalić wbudowany w PHP od wersji 5.4 serwer, a po zakończeniu pracy wrócić do domyślnie zainstalowanej w systemie wersji.

    phpbrew off

    virtPHP

    Zarówno Python, jak i PHP stale się rozwijają. Wciąż dochodzą mniejsze lub większe usprawnienia, pojawiają się nowe rozszerzenia, biblioteki, moduły. Przełomowe zmiany, takie jak w przypadku wydania siódmej odsłony PHP następują stosunkowo rzadko, a co za tym idzie mechanizmy zarządzania wersjami danego języka w systemie przydają się sporadycznie. O wiele częściej deweloperzy spotykają się z potrzebą konfiguracji środowiska deweloperskiego dobraną specjalnie pod dany projekt.

    Python ze swoim wirtualnym środowiskiem virtualenv trochę mnie rozpieścił jako dewelopera. Virtualenv lub też jego funkcjonalny następca – wbudowany w Pythona 3 moduł pyvenv pokazał mi że praca nad różnymi aplikacjami, wymagającymi często odmiennego zestawu bibliotek i konfiguracji może być łatwa i przyjemna.

    Instalacja, utworzenie wirtualnego środowiska – w tym wypadku w katalogu projektu – i uruchomienie go poprzez wywołanie skryptu, który ustawia wszelkie zmienne środowiskowe.

    sudo apt-get install python-virtualenv
    cd ~/my_project/
    virtualenv .ve
    source .ve/bin/activate

    Do wersji 3.3 Pythona zalecane jest używanie virtualenv zamiast pyvenv bo dopiero w wersji 3.4 pyvenv ma wbudowanego menagera pakietów pip umożliwiającego łatwą instalację zależności.

    pip install django

    Pip umożliwia zrzucenie listy zainstalowanych pakietów do pliku dzięki czemu możliwe jest np. zachowanie konfiguracji wirtualnego środowiska w repozytorium Git

    pip freeze > requirements.txt

    i późniejsze jego odtworzenie na innej maszynie.

    pip install -r requirements.txt

    Komfort pracy, jaki daje wirtualne środowisko jest nie do przecenienia. W katalogu projektu mamy tylko i wyłącznie pliki projektu. Wszystkie zależności – niezbędne moduły i biblioteki dostarczane przez zewnętrznych developerów trzymane są w odmiennej lokalizacji i jedyne czego potrzebujemy do odtworzenia środowiska niezbędnego do uruchomienia projektu jest plik requirements.txt. Python dał mi zupełnie odmienne doświadczenie w tej kwestii w stosunku do projektów realizowanych w PHP gdzie wszelkie zależności – nie licząc wbudowanych funkcji języka oraz wkompilowanych w niego rozszerzeń – były trzymane, najczęściej w podkatalogu „vendors” i razem z plikami projektu do repozytorium pchane były całe feameworki i inne rozbudowane biblioteki, ewentualnie zaciągane zewnętrzne repozytoria.

    Jak wspomniałem przez ostatnie lata nie obcowałem z PHP zbyt intensywnie więc szukałem funkcjonalnego odpowiednika virtualenv. Znalazłem virtPHP. Twórcy virtPHP nie ukrywają, że wzorowali się na swym pythonowym odpowiedniku starając się stworzyć narzędzie równie łatwe a nawet podobne w użyciu. Biblioteka ta nie została jeszcze ustabilizowana. Ostatnia póki co wersja virtPHP 0.5.2-alpha jest dedykowana jedynie na systemy unixopochodne. Trochę martwi brak aktywności developerów przez ostatni rok – nie mniej zachęcam do wypróbowania możliwości wirtualnych środowisk w PHP.

    Kiedy już uporamy się z zainstalowaniem virtPHP, możemy przystąpić do utworzenia pierwszego wirtualnego środowiska dla projektu w PHP.

    cd ~/my_php_project/
    php virtphp.phar create .my-ve
    source .my-ve/bin/activate

    VirtPHP można łaczyć także z PHPbrew

    php virtphp.phar create --php-bin-dir="/home/user/.phpbrew/php/php-5.5.9/bin" .my-ve

    Od tej chwili można instalować biblioteki z repozytoriów PECL i PEAR

    pecl install mongo
    pear install pear.phpunit.de/PHPUnit

    Potencjał tego narzędzia oceniałbym jako wysoki gdyby nie problem z repozytoriami PHP. PECL to repozytorium rozszerzeń PHP napisanymi w języku C. Trudno mi ocenić jego aktualność – zwłaszcza w związku z wydaniem wersji PHP 7. PEAR to biblioteki napisane w PHP tyle, że to staroć nieuaktualniany o nowe wydania bibliotek (np. Symfony Framework). Ponieważ trzymanie zależności w podkatalogu „vendor” stało się powszechnie stosowaną praktyką w świecie PHP popularność zdobył sobie menadżer zależności, który niejako podtrzymał tradycję.

    Composer

    Menadżer zależności Composer może być uruchomiony w kontekście wirtualnego środowiska virtPHP co niesie ze sobą dodatkowe korzyści, ale równie dobrze może być używany niezależnie. Koncepcyjnie Composerowi najbliżej do npm znanego z Node.js.

    Instalacja Composera sprowadza się do ściągnięcia pliku composer.phar do katalogu głównego projektu.

    cd ~/my_php_project/
    php -r "readfile('https://getcomposer.org/installer');" | php
    

    Zależności są instalowane w podkatalogu vendors.

    php composer.phar require monolog/monolog
    php composer.phar require twig/twig

    natomiast w katalogu głównym tworzone są trzy pliki:

    ./vendors/autoload.php
    ./composer.json
    ./composer.lock

    Plik composer.json, zawiera bieżącą konfigurację zależności.

    {
        "require": {
            "monolog/monolog": "^1.19",
            "twig/twig": "^1.24"
        }
    }

    Z kolei w pliku composer.lock – również w formacie json – zapisane są szczegóły na temat zainstalowanych paczek i ich wersji. Tan plik jest bardzo rozbudowany. W repozytorium kodu zalecane jest przechowywanie obu plików, gdyż w przypadku próby odwzorowania konfiguracji na innej maszynie,

    php composer.phar install

    polecenie composer install korzysta właśnie z pliku composer.lock i dopiero w przypadku jego braku, z konfiguracji zapisanej w composer.json, ale wtedy nie ma pewności, że dostaniemy dokładnie taki sam wynik.

    Deweloperzy PHP są leniwi jak przystało na każdego szanującego się programistę i unikają pisania zbędnego kodu związanego z dołączaniem wymaganych klas i funkcji. Mają do dyspozycji autoloader. Sam byłem zakochany w koncepcie autoloadera dopóki nie zacząłem na full etat kodować w Pythonie. Okazało się, że importy są całkowicie wystarczające a do tego stanowią jeden z aspektów samodokumentującego się kodu. Jeśli unikasz tzw. importowania z gwiazdką, nie potrzebujesz zaawansowanego IDE aby wiedzieć, gdzie co jest i czego możesz się spodziewać w danym module. Pythonowcy nie wiedzą co to autoloader. PHP-owcy nie potrafią bez niego żyć tak więc Composer wspiera również ten mechanizm. Po zainstalowaniu zależności w katalogu vendors generowany jest plik autoload.php, który wystarczy tylko zaimportować.

    require __DIR__ . '/vendor/autoload.php';

    PHP przez wiele lat królowało na współdzielonych hostingach dając PHP dużą przewagę nad innymi językami skryptowymi używanymi w web developmencie. Wystarczyło połączenie FTP do przesłania plików na serwer i początkujący webmaster mógł się cieszyć z działającej strony www. Composer wymaga dostępu do shella tak więc z pełni jego zalet będą cieszyć się osoby pracujące nad serwisami publikowanymi na kontach z dostępem przez ssh np. na VPN-ach albo serwerach dedykowanych.

    Docker

    Przez ostatnie parę lat mocno rozwinęła się wirtualizacja. Deweloperzy bardzo chętnie sięgają dziś po narzędzia typu Vagrant lub Docker, które umożliwiają utworzenie niemal całkowicie odseparowanego środowiska deweloperskiego, niezależnego od konfiguracji komputera programisty, dającego się przenieść na inną maszynę. W kontenerze można dziś zamknąć zarówno aplikacje pisane w PHP, Pythonie jak i w wielu innych językach programowania więc to nie czyni różnicy między nimi. Docker ma ogromne możliwości, których nie zamierzam tutaj opisywać. Jest to temat na grubą książkę. Chcę jedynie zaprezentować przykład podstawowego użycia kontenera, w którym zamknięte jest PHP w wersji 7 do sprawdzenia możliwości nowej odsłony tego języka.

    PHP ma swoje oficjalne obrazy w repozytorium Dockera i w domyślnej konfiguracji, wraz z pełni funkcjonalnym Apachem daje się uruchomić po wydaniu zaledwie dwóch komend .

    docker pull php:7.0.6-apache
    docker run -p 8080:80 -v "~/my_php_project":/var/www/html --name my-php7-container php:7.0.6-apache

    Po wrzuceniu pliku do katalogu my_php_project

    echo '<?php phpinfo();' > ~/my_php_project/info.php

    I wybranie w przeglądarce adresu

    localhost:8080/info.php

    możemy obejrzeć sobie konfigurację PHP 7.

    Naturalnie trzeba mieś już zainstalowanego i skonfigurowanego Dockera. Warto ten wysiłek podjąć i rozpocząć przygodę z kontenerami. Już najprostsze przypadki użycia są przydatne i wcale nie trzeba pracować nad wielkim projektem o rozbudowanej architekturze aby docenić zalety konteneryzacji. Choć rozwiązanie to nie jest bez wad – obrazy Dockera sporo ważą, a uruchomione instancje zabierają sporo zasobów będąc bądź co bądź pełnoprawnymi systemami – w zamian dostajemy odseparowane pudełko, w którym możemy zamknąć np. wersję 2.4 blendera, która jako ostatnia obsługuje skrypty pisane w Pythonie 2.

    Każdy człowiek ucząc się nowej rzeczy czerpie ze swoich dotychczasowych doświadczeń. Programiści rozpoczynający pisanie oprogramowania w nowym dla nich języku starają się zorganizować swój warsztat pracy w sposób, który jest im znany. Udaje się to mniej lub bardziej bo każdy język programowania ma swoją specyfikę uwarunkowaną przyjętymi założeniami, filozofią, techniczną implementacją i wypracowaną praktyką. To co jest przyjęte za standard w jednej technologii w innej może być uważane za niemile widziane. Sztuką jest dostosować się i nauczyć poprawnie każystać z dedykowanych narzędzi. Zarówno PHP, jak i Python umożliwiają organizację pracy dewelopera w sposób umożliwiający łatwe przełączanie się pomiędzy różnymi projektami posiadającymi swoją konfigurację i zależności. Choć robią to w nieco odmienny sposób w mojej ocenie wygoda rozwiązań stoi na porównywalnym poziomie.

    Podziel się z innymi!

      Upload plików w Django

      Podziel się z innymi!

        Upload plików w Django

        W Django nie zapomniano o kwestii uploadu plików. Deweloperzy tego frameworka starali się uczynić tę czynność jak najprostszą mimo to prawidłowa realizacja tego zadania wymaga odrobiny wysiłku. Na początek przytoczę znaleziony na stackoverflow.com minimalny przykład prezentujący krok po kroku jak w Django pobrać od użytkownika plik i zapisać go na serwerze. Będzie on stanowił doskonały punkt wyjścia dla bardziej zaawansowanych tematów jak dbanie o równomierny rozkład plików w katalogach czy przycinanie obrazków w panelu administracyjnym z użyciem jQuery i jCrop, którymi to tematami zajmę się w kolejnych wpisach.

        Poniższy opis oparty jest na przykładzie, którego źródło można ściągnąć z github-a

        Przykład bazuje na Django 1.3 w którym główny plik settings.py i urls.py trzymane są w katalogu głównym projektu.

        minimal-django-file-upload-example/
            src/
                myproject/
                    database/
                        sqlite.db
                    media/
                    myapp/
                        templates/
                            myapp/
                                list.html
                        forms.py
                        models.py
                        urls.py
                        views.py
                    __init__.py
                    manage.py
                    settings.py
                    urls.py

        W Django 1.4 nastąpiła zmiana w domyślnej strukturze projektu i wyżej wspomniane pliki powinny trafić do podkatalogu o nazwie odpowiadającej nazwie projektu.

        1. Konfiguracja: /myproject/settings.py

        Przede wszystkim należy zacząć od prawidłowej konfiguracji bazy danych ponieważ szczegółowa ścieżka do pliku zapisywana będzie w bazie danych, mediów – czyli wskazania katalogu w projekcie, do którego będą trafiały wszystkie wgrywane przez użytkowników pliki oraz URL-a zarezerwowanego na potrzeby serwowania plików statycznych. Trzeba też pamiętać aby aplikacja zawierająca model ze zdefiniowanymi polami do uploadu plików była dodana do INSTALLED_APPS.

        DATABASES = {
            'default': {
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': '/path/to/myproject/database/sqlite.db'),
                'USER': '',
                'PASSWORD': '',
                'HOST': '',
                'PORT': '',
            }
        }
        
        MEDIA_ROOT = '/path/to/myproject/media/'
        MEDIA_URL = '/media/'
        
        INSTALLED_APPS = (
         …
         'myapp',
        )
        

        Więcej na temat konfiguracji mediów i plików statycznych w Django.

        2. Model: myproject/myapp/models.py

        Do uploadu plików w modelu Django służy pole FileField lub też jego specjalna wersja dla obrazków ImageField. Oba z wymienionych pól mają atrybut „upload_to” umożliwiający wskazanie szczegółowej lokalizacji składowania pliku.

        # -*- coding: utf-8 -*-
        from django.db import models
         
        class Document(models.Model):
            docfile = models.FileField(upload_to='documents/%Y/%m/%d')

        Jeśli ustawimy upload_to=’documents/%Y/%m/%d’ to w katalogu określonym w zmiennej MEDIA_ROOT zostanie utworzona ścieżka oparta na bieżącej dacie np. „documents/2013/03/09”.

        Atrybutowi „upload_to” możemy przypisać zarówno tekst, jak i też metodę czy funkcję zwracającą ścieżkę do katalogu, co daje nam większe możliwości, ale jest to kwestia bardziej złożona i omówię ją przy innej okazji.

        3. Formularz: myproject/myapp/forms.py

        Wykorzystanie obiektów formularzy w Django bardzo ułatwia pracę z formularzami o czym przekonałem się wielokrotnie. Także w tym przypadku najprościej jest użyć ModelForm – czyli formularza opartego na modelu, w którym nie trzeba definiować pól formularza tylko wskazać na jakim modelu dany formularz ma się opierać i które pola mają podlegać edycji. Przytaczany tu przykład jednak pokazuje jak stworzyć zupełnie niestandardowy formularz z wykorzystaniem kontrolki pola do uploadu pliku FileField. UWAGA! – nie należy mylić pól modelu z polami formularza, które mimo podobieństwa nazwy są czymś innym.

        # -*- coding: utf-8 -*-
        from django import forms
         
        class DocumentForm(forms.Form):
            docfile = forms.FileField(
                label='Select a file',
                help_text='max. 42 megabytes'
            )

        4. Widok: myproject/myapp/views.py

        W widoku realizowany jest cały proces obsługi formularza. Po przeprowadzeniu walidacji możemy śmiało pobrać plik z request.FILES i przypisać go wprost do pola modelu. Jeszcze łatwiej byłoby w przypadku formularza opartego o model, ale tak za to mamy większą elastyczność gdyż przed zapisaniem pliku w bazie danych moglibyśmy jeszcze coś z nim zrobić (np. zmienić nazwę).

        # -*- coding: utf-8 -*-
        from django.shortcuts import render_to_response
        from django.template import RequestContext
        from django.http import HttpResponseRedirect
        from django.core.urlresolvers import reverse
         
        from myproject.myapp.models import Document
        from myproject.myapp.forms import DocumentForm
         
        def list(request):
            # Handle file upload
            if request.method == 'POST':
                form = DocumentForm(request.POST, request.FILES)
                if form.is_valid():
                    newdoc = Document(docfile = request.FILES['docfile'])
                    newdoc.save()
         
                    # Redirect to the document list after POST
                    return HttpResponseRedirect(reverse('myapp.views.list'))
            else:
                form = DocumentForm() # A empty, unbound form
         
            # Load documents for the list page
            documents = Document.objects.all()
         
            # Render list page with the documents and the form
            return render_to_response(
                'myapp/list.html',
                {'documents': documents, 'form': form},
                context_instance=RequestContext(request)
            )

        5. Konfiguracja URL-i: myproject/urls.py

        Django nie udostępnia mediów z MEDIA_ROOT automatycznie gdyż byłoby to niebezpieczne w środowisku produkcyjnym. Na serwerze developerskim to Apache, Nginx czy Lighttpd zajmują się serwowaniem plików statycznych natomiast w środowisku developerskim trzeba sobie dodać regułkę do urls-ów.

        # -*- coding: utf-8 -*-
        from django.conf.urls.defaults import patterns, include, url
        from django.conf import settings
        from django.conf.urls.static import static
         
        urlpatterns = patterns('',
            (r'^', include('myapp.urls')),
        ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

        ,

        6. URL-e aplikacji: myproject/myapp/urls.py

        Aby widok z danej aplikacji był dostępny także dla niego trzeba dodać regułkę do urls.py

        # -*- coding: utf-8 -*-
        from django.conf.urls.defaults import patterns, url
         
        urlpatterns = patterns('myapp.views',
            url(r'^list/$', 'list', name='list'),
        )
        <pre>
         
        <h2>7. Szablon: myproject/myapp/templates/myapp/list.html</h2>
         
        <p>Aby można było wgrać plik na serwer wymagane jest aby tag formularza zawierał atrybut enctype="multipart/form-data" oraz żeby formularz był wysyłany metodą POST</p>
         
        <pre lang="html4strict">
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="utf-8">
                <title>Minimal Django File Upload Example</title>
            </head>
            <body>
            <!-- List of uploaded documents -->
            {% if documents %}
                <ul>
                {% for document in documents %}
                    <li><a href="{{ document.docfile.url }}">{{ document.docfile.name }}</a></li>
                {% endfor %}
                </ul>
            {% else %}
                <p>No documents.</p>
            {% endif %}
         
                <!-- Upload form. Note enctype attribute! -->
                <form action="{% url list %}" method="post" enctype="multipart/form-data">
                    {% csrf_token %}
                    <p>{{ form.non_field_errors }}</p>
                    <p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
                    <p>
                        {{ form.docfile.errors }}
                        {{ form.docfile }}
                    </p>
                    <p><input type="submit" value="Upload" /></p>
                </form>
            </body>
        </html>

        8. Uruchomienie przykładu

        Tak dla pełnego obrazu – aby uruchomić omawiany tu minimalny, przykład uploadu plików w django należy ściągnąć jego źródła lub przekopiować z wyżej zamieszczonych listingów a następnie uruchomić w konsoli odpowiednie komendy:

        > cd myproject
        > python manage.py syncdb
        > python manage.py runserver

        Po uruchomieniu serwera pod adresem localhost:8000/list/ zobaczymy – na początek pustą – listę zuploadowanych plików oraz formularz. Pliki powinny się zapisywać w katalogu analogicznym do „/path/to/myproject/media/documents/2013/03/09/”

        Autorem powyższego wyczerpującego przykładu jest Akseli Palén. Omówienie jest wzbogacone kilkoma moimi komentarzami.

        Podziel się z innymi!

          Pliki statyczne w Django

          Podziel się z innymi!

            Pracuję z Django od wersji 0.96 i w projektach, które utrzymuję jest jeszcze wiele zaszłości. Jedną z nich jest sposób składowania wszelakich plików statycznych w katalogu, którego ścieżka zdefiniowana była w settings.py w zmiennej MEDIA_ROOT. Tymczasem od wersji 1.3 developerzy Django postanowili rozróżnić pliki statyczne – czyli wszelkiego rodzaju javascript-y, css-y, grafiki itp. stałe elementy składające się na tzw. layout – od mediów, pod którą to nazwą powinniśmy rozumieć wszelkiego rodzaju pliki uploadowane.

            Na marginesie dodam, że aplikacja django-staticfiles była już wcześniej dostępna, ale integralną częścią frameworka stała się dopiero od wersji 1.3.

            Jak skonfigurować ustawienia mediów?

            Zacznę od mediów bo sprawa jest stara, prosta i wielokrotnie opisywana.

            Załóżmy, że struktura naszej aplikacji wygląda tak:

            project_root/
                manage.py
                mysite/
                    __init__.py
                    settings.py
                    urls.py
                    wsgi.py 
                news/
                    __init__.py
                    models.py
                    urls.py
                    views.py
                /media
                    /users
            	    user_avatar.jpg
            

            UWAGA! Od wersji 1.4 zmieniła się podstawowa struktura projektu w ten sposób, że podstawowa konfiguracja oraz główny plik z urls.py trafiły do podkatalogu z nazwą projektu – w tym wypadku „mysite”.

            Mediów dotyczą dwie zmienne w settings.py

            MEDIA_ROOT – przechowuje pełną ścieżkę do katalogu z mediami. Do tego folderu będą trafiały pliki wrzucane przez użytkowników z poziomu strony www. Stanowi on bazową ścieżkę dla parametru „upload_to” pól FileField i ImageField.

            MEDIA_ROOT = '/home/user/project_root/media'

            Jeśli zmienimy fizyczne położenie mediów (bo np. serwer produkcyjny czyta pliki statyczne tylko z podkatalogu public_html) to będziemy musieli zmienić MEDIA_ROOT.

            MEDIA_ROOT = '/home/user/project_root/public_html/media'

            ale MEDIA_URL i wszystkie odwołania w szablonach pozostaną takie same.

            MEDIA_URL – bazowy adres URL, pod którym dostępne będą pliki mediów.

            MEDIA_URL = '/media/'

            Przy takiej kongfiguracji do plików mediów będziemy odwoływać się następująco

            <img src="/media/images/user_avatar.jpg" alt="{{ user.username }}" />

            MEDIA_ROOT – Może mieć formę adresu względnego – najczęściej używaną na serwerze developerskim, albo adresu bezwzględnego, przydatnej na serwerach produkcyjnych

            MEDIA_URL='http://static.domain.pl/media/'

            Zmiana url-a wiąże się z koniecznością zmiany wszystkich odwołań w szablonach dlatego warto od razu przewidzieć taką możliwość i przekazać do szablonu zmienną MEDIA_ROOT i posługiwać się nią zamiast hardkodować adresy do plików mediów.

            <img src="{{ MEDIA_URL }}images/user_avatar.jpg" alt="{{ user.username }}" />

            Przy okazji wspomnę, że obiekty formularzy FileField i ImageField rzutowane do tekstu lub unicode-a zwracają względną ścieżkę do pliku

            {{ user.avatar }} # users/user_avatar.jpg

            metoda url zwraca pełen url zawierający w sobie MEDIA_URL

            {{ user.avatar.url }} # /media/users/user_avatar.jpg (albo http://static.domain.pl/media/users/user_avatar.jpg)
            <img src="{{ user.avatar.url }}" alt="{{ user.username }}" />

            UWAGA! Jeśli dla FileField lub ImageField dopuścimy możliwość wstawiania null-i (null=True, blank=True, default=None), w szablonie zawsze dobrze jest sprawdzać czy mamy do czynienia z plikiem czy null-em.

            <img src="{% if user.avatar %}{{ user.avatar.url }}{% endif %}" alt="{{ user.username }}" />

            a metoda path zwraca pełną ścieżkę do pliku zawierającą w sobie MEDIA_ROOT

            {{ user.avatar.path }} # /home/user/project_root/public_html/media/users/user_avatar.jpg

            Z plikami statycznymi wiąże się więcej zagadnień.

            Jaki jest modelowy sposób składowania plików statycznych w Django?

            Przede wszystkim zmienna STATIC_ROOT pełni rolę analogiczną do MEDIA_ROOT, ale tylko na serwerze produkcyjnym. Wbudowany – developerski – serwer Django w celu odnalezienia położenia plików statycznych korzysta z tzw. finderów zdefiniowanych w STATICFILES_FINDERS oraz STATICFILES_DIRS – ale po kolei.

            Pliki statyczne należy trzymać w podkatalogu „./static” każdej aplikacji

            project_root/
                manage.py
                mysite/
                    __init__.py
                    settings.py
                    urls.py
                    wsgi.py
                    static/ - pliki wspólne dla całej strony
                        /css
                            screen.css
                        /js
                            jquery.js 
                musicplayer/
                    __init__.py
                    models.py
                    urls.py
                    views.py
                    static/ - pliki statyczne aplikacji
                        /js
                            jplayer.js
                        /images
                            play.gif
            

            Ścieżkę każdego z podkatalogów powinniśmy dodać do krotki zmiennej STATICFILES_DIRS w settings.py

            STATICFILES_DIRS = (
                "/home/project_root/mysite/static",
                "/home/project_root/musicplayer/static",
            )

            Jeśli zdefiniujemy zmienną STATIC_URL następująco:

            STATIC_URL = '/static/'

            Adresy plików w szablonach będą wyglądały tak:

            <img src="/static/images/play.gif" alt="play" />

            Oczywiście tu podobnie jak w mediach warto przekazać do szablonu zmienną STATIC_URL,

            <img src="{{ STATIC_URL }}images/play.gif" alt="play" />

            bo na serwerze produkcyjnym możemy się zdecydować na podanie bezwzględnego url-a zamiast względnej ścieżki.

            STATIC_URL = 'http://static.domain.pl/static/'

            Czasami chcielibyśmy aby media aplikacji – dajmy na to musicplayer – znalazły się w podkatalogu o tej samej nazwie. Nie musimy w tym celu zmieniać struktury plików lecz wystarczy dodać prefix do ścieżki w STATICFILES_DIRS.

            STATICFILES_DIRS = (
                "/home/project_root/mysite/static",
                ('musicplayer', "/home/project_root/musicplayer/static"),
            )

            Fizyczne położenie pliku się nie zmieniło, ale zmiana konfiguracji pozwala traktować plik jakby się znajdował w dodatkowym podkatalogu.

            <img src="/static/musicplayer/images/play.gif" alt="play" />

            Aby całość zadziałała musimy jeszcze wskazać Django narzędzia wyszukiwania plików tzw. findery.

            STATICFILES_FINDERS = (
                'django.contrib.staticfiles.finders.FileSystemFinder',
                'django.contrib.staticfiles.finders.AppDirectoriesFinder',
            )

            Ponieważ w dwóch różnych podkatalogach /static możemy mieć plik o tej samej nazwie, może się zdarzyć że jeden „nadpisze” drugi i trzeba będzie wyszukać wszystkie lokalizacje w jakich może znajdować się plik o danej nazwie.

            Warto wtedy skorzystać z komendy findstatic

            $ python manage.py findstatic css/base.css admin/js/core.js
            #/home/special.polls.com/core/static/css/base.css
            #/home/polls.com/core/static/css/base.css
            #/home/polls.com/src/django/contrib/admin/media/js/core.js

            Pliki statyczne na serwerze developerskim

            Django nie udostępnia plików statycznych i mediów z automatu gdyż byłoby to niebezpieczne na serwerze produkcyjnym. Dlatego też także w środowisku developerskim trzeba zadbać aby serwer wiedział gdzie szukać plików poprzez dodanie odpowiednich reguł do urls.py

            from django.conf.urls import patterns, include, url
            from django.conf import settings
            from django.conf.urls.static import static
            from django.contrib.staticfiles.urls import staticfiles_urlpatterns
             
            urlpatterns = patterns('',
                # Uncomment the next line to enable the admin:
                url(r'^admin/', include(admin.site.urls)),
                ...
            )
             
            if settings.DEBUG:
                urlpatterns += staticfiles_urlpatterns()   
                urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

            Pliki statyczne na serwerze produkcyjnym

            Nie będę opisywał jak skonfigurować Apache 2, Nginx czy Lighttpd. Przyjmijmy, że dowolny serwer umożliwia serwowanie plików statycznych z poziomu podkatalogu /public_html.

            W pierwszej kolejności należy odpowiednio ustawić zmienną STATIC_ROOT w settings.py

            STATIC_ROOT = os.path.join(PROJECT_ROOT, 'public_html/static')

            Następnie należało by odpowiednio do ustawień STATICFILES_DIRS przekopiować (lub dowiązać) pliki i katalogi.

            project_root/
                public_html/
                    static/ - pliki wspólne dla całej strony
                        /css
                            screen.css
                        /js
                            jquery.js 
            	    musicplayer/ - pliki statyczne aplikacji
                            /js
                                jplayer.js
                            /images
                                play.gif
            

            Może to być nieco pracochłonne dlatego też wielu developerów do tej pory preferowało trzymanie statyków w jednym wspólnym katalogu. Po opublikowaniu strony na serwerze produkcyjnym wystarczyło tylko przekopiować lub przenieść statiki do katalogu publicznego oraz zmienić ustawienia STATIC_ROOT.

            Od wersji 1.4 mamy do dyspozycji komendę „collectstatic”, który całą robotę z kopiowaniem plików statycznych robi za nas. W trakcie developmentu możemy trzymać pliki statyczne w wielu podkatalogach a po wrzuceniu na live wystarczy tylko wywołać komendę –

            python manage.py collectstatic

            i mamy przekopiowane wszystkie pliki statyczne łącznie z tymi związanymi z panelem administracyjnym. Dodanie na końcu opcji -l albo –link,

            python manage.py collectstatic -l

            tworzy dowiązania symboliczne zamiast kopiować pliki.

            Staticfiles w starych projektach

            Przy podnoszeniu starych projektów do nowej wersji Django nie jesteśmy zmuszani do wydzielania mediów i relokacji plików statycznych. Dalej możemy trzymać wszystko w jednym katalogu, ale trzeba zadbać o odpowiednią konfigurację.

            MEDIA_ROOT = '/home/user/project_root/static'
             
            MEDIA_URL = '/static/'
             
            STATICFILES_DIRS = (
                MEDIA_ROOT,
            )
             
            STATIC_URL = MEDIA_URL
             
            STATIC_ROOT = '/home/user/project_root/public_html/static'

            UWAGA! Traktuj STATIC_ROOT jak zmienną używaną tylko na serwerze produkcyjnym i nawet jeśli nie wiesz jak powinna ta ścieżka wyglądać docelowo wskarz na katalog, który nie jest jednym z katalogów zdefiniowanych w STATICFILES_DIRS

            Podusmowanie

            Konfiguracja mediów w Django nie jest tak prosta jak to się wydaje doświadczonym developerom. Zagadnienie to nie jest poruszone w podstawowym tutorialu, a dokumentacja choć jedna z najlepszych nie prowadzi za rękę początkującego programistę.

            Choć staticfiles wraz z komendą collectstatic to bardzo wygodne rozwiązanie ułatwiające trzymanie porządku w projekcie, dodatkowo utrudnia zagadnienie zarządzania plikami statycznymi. Mam nadzieję, że swoim wpisem nie gmatwam dodatkowo całej sprawy, i że przyda się on komuś rozpoczynającemu dopiero pracę z frameworkiem Django.

            Podziel się z innymi!

              JavaScript w projekcie internetowym

              Podziel się z innymi!

                JavaScript jest technologią kojarzoną ze stronami WWW. Uruchamiany w przeglądarce internetowej język przez długi czas stanowił dodatek ożywiający jedynie statyczną treść. Sam przez wiele lat trzymałem się paradygmatu, że strona internetowa powinna być w pełni funkcjonalna i czytelna nawet jeśli użytkownik wyłączy w swojej przeglądarce obsługę JavaScript-u. Ta reguła jest nadal dobrze widzianą praktyką ale rzadziej przestrzeganą, gdyż pozbawiony javascript-owego dopingu interfejs użytkownika na tyle traci na wygodzie i atrakcyjności, że już mało kto decyduje się na skrajne ustawienia bezpieczeństwa.

                W chwili obecnej ilość kodu pisanego w JavaScript-cie powoli zrównuje się z ilością kodu tworzonego w językach działających po stronie serwera, a podejrzewam, że są i serwisy gdzie ją przewyższa. Programiści frontendowi zyskują coraz bardziej na znaczeniu bo też wymagania stawiane im są coraz większe. Dzisiaj pracodawcy nie szukają „koderów” potrafiących dołączyć i uruchomić cudzy, znaleziony gdzieś w sieci skrypt, którego jedynym zadaniem jest rozwijanie i zwijanie menu. Dzisiaj tworzy się rozbudowane aplikacje oparte na profesjonalnych frameworkach stworzonych przez – nierzadko duże (np. Yahoo YUI Library) – firmy i mające szerokie wsparcie wśród społeczności (np. jQuery) internetowych.

                Im bardziej złożone programy, im większa ilość skryptów – tym zarządzanie i utrzymywanie kodu JavaScript staje się trudniejsze. Fakt, że interpreterem języka JavaScript jest przeglądarka internetowa czyni sprawę jeszcze bardziej skomplikowaną gdyż musimy wziąć pod uwagę kilka dodatkowych aspektów związanych ze specyficznym środowiskiem w jakim pisane w tym języku programy są uruchamiane.

                Osadzać JavaScript w kodzie strony HTML czy wydzielać do plików js?

                Nie ma zastrzeżeń natury techninej ograniczających ilość kodu w źródle strony lub też określających co powinno być przeniesione do plików zewnętrznych. Zdrowy rozsądek oraz zasada, że należy rozdzielać logikę od warstwy wizualnej podpowiadają, że kod osadzony inline-owo powinien być jak najskromniejszy, a wszystko co nie jest specyficzne dla danej podstrony powinno zostać umieszczone w pliku.

                Przemawiają za tym zarówno względy estetyczne jak i pragmatyczne. Kod umieszczony w pliku łatwiej się debuguje, można go skompresować, poddać obfuskacji i dołączyć do innych stron. Ponadto przeglądarki domyślnie buforują pliki js – chyba, że mają inne ustawienia – co oszczędza liczbę żądań (requestów) kierowanych do serwera.

                Skrypt osadzony powinien być umieszczony w jednym miejscu. Przeplatanie kodu JavaScript między innymi elementami HTML-a dopuszczalne jest tylko w przypadku obsługi zdarzeń.

                <script type="text/javascript">
                    function someFunction() { alert('OK'); }
                </script>
                <input id="somebutton" type="button" value="Call function" onclick="someFunction();" />

                Jest to już trochę archaizm ponieważ dzięki nasłuchiwaczom (ang. listener) można każde zdarzenie obsłużyć bez dodawania do tagów HTML atrybutów onclick, onmouseover itd. Nicholas C. Zakas w książce pt. „JavaScript dla webmasterów – zaawansowane programowanie” opisuje różnice w implementacji tego mechanizmu w różnych przeglądarkach. Wspominam o tym tylko jako o ciekawostce gdyż każdy z czołowych frameworków udostępnia ujednolicone api do obsługi zdarzeń.

                Przykładowy kod z użyciem jQuery pokazuje jak podpiąć funkcję pod określone zdarzenie wybranego obiektu na stronie.

                $(document).ready(function() {
                    $('#somebutton').click(someFunction);
                });

                Kod ten umieszczony w nagłówku strony uruchomi się po załadowaniu całego drzewa DOM. Wynika z tego kilka niedogodności.

                Wszelkie inicjowane JavaScript-em widżety pojawią się z widocznym opóźnieniem – zostaną uruchomione dopiero po wyrenderowaniu całej strony. Niecierpliwi użytkownicy mogą zacząć klikać w kalendarzyk zanim jeszcze zostanie uruchomiony, albo zatwierdzać formularza bez podpiętych javascript-owych walidatorów. Jest to cena za czystość i porządek w kodzie.

                Czy można sterować cache-owaniem plików JavaScript w pamięci podręcznej przeglądarki?

                Podczas pierwszej wizyty na stronie WWW wszystkie jej elementy zostają pobrane i w zależności od ustawień przeglądarki oraz przesłanych nagłówków HTTP zapisane w pamięci podręcznej przeglądarki. Buforowanie (ang. cache) oszczędza liczbę żądań wysyłanych do serwera, transfer i przyśpiesza ładowanie strony. (Użyj about:cache w Firefox jeśli chcesz zobaczyć co zawiera Twój cache)

                Do sterowania zachowaniem cache-a służą nagłówki HTTP (ang. HTTP Headers). Dla treści dynamicznej najlepiej skorzystać z Cache-Control i tak aby zapobiec buforowaniu dokumentu należy wysłać następujące nagłówki.

                header("Pragma: no-cache");
                header("Cache-Control: max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
                header("Expires: 0");

                UWAGA! Można w tym celu użyć meta tagów ale nie wszystkie przeglądarki honorują meta tagi strujące cachem i nie są one brane pod uwagę przez serwery proxy

                <head>
                    <meta http-equiv="Expires" content="0" />
                    <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
                    <meta http-equiv="Cache-Control" content="post-check=0, pre-check=0" />
                    <meta http-equiv="Pragma" content="no-cache" />
                </head>

                Przy rzadko zmieniającej się treści lepsze są inne ustawienia bufora.

                header("Cache-Control: max-age=3600, must-revalidate, post-check=600, pre-check=1800");

                przeglądarka powinna przez 600 sekund podawać dokument wyłącznie z bufora, następnie przez 1200 sekund brać dokument z bufora, ale też sprawdzać w tle czy na serwerze jest nowsza wersja (i jeżeli jest to ją zaciągnąć do kolejnego użycia). Po upłynięciu 1800 sekund od pobrania przeglądarka powinna najpierw sprawdzić czy jest nowa wersja i jeśli jest to ją zaktualizować, a następnie użyć.

                Dla elementów statycznych takich jak np. pliki JavaScript mamy do dyspozycji dwa inne nagłówki: ETag i Expires. Nagłówek ETag informuje przeglądarkę czy konkretny plik został zmieniony. Wymaga każdorazowo nawiązania połączenia z serwerem w celu odpytania o aktualność danego zasobu. Z kolei nagłówek Expires określa okres (a właściwie datę końcową) przez jaki obiekty danego rodzaju powinny być traktowane jako aktualne. Ma to tę zaletę, że Expires wyznacza datę ważności i do czasu jej minięcia przeglądarka nie będzie próbowała podejmować nawet próby weryfikacji aktualności danego pliku na serwerze.

                <FilesMatch "\.(ico|jpg|jpeg|png|gif|js|css|swf)$">
                    <IfModule mod_expires.c>
                        ExpiresActive on
                        # 2592000 sekund = 30 dni
                        ExpiresDefault A2592000
                        Header append Cache-Control "public"
                    </IfModule>
                    # Wylaczenie naglowkow ETag
                    Header unset ETag
                    FileETag None
                </FilesMatch>

                Powyższy przykład prezentuje jak w htaccess zdefiniować 30 dniowy (2592000 sekund = 30 dni) okres ważności dla wybranych plików. Nagłówek ETag został wyłączony ponieważ w przypadku równoczesnego wysłania do klienta (czytaj przeglądarki) obu nagłówków, ETag ma priorytet.

                UWAGA! Jedynym sposobem na wymuszenie odświeżenie pliku z ustawionym Expires w przeglądarkach wszystkich odwiedzających jest zmiana jego nazwy.

                Zmiana nazwy pliku, który został podpięty w różnych częściach serwisu wiąże się z koniecznością dokonania poprawek w kilku miejscach w kodzie. Najłatwiej i najwygodniej zmienić nazwę foldera zawierającego elementy statyczne serwisu i jego nazwę trzymać w zmiennej. Wystarczy wtedy zmienić jedną wartość w konfigu i adresy wszystkich dynamicznie osadzonych w kodzie html obiektów zmienią się w jednej chwili. Jest to przepis, który można od biedy zastosować dla małopopularnych serwisów bowiem takie podejście ma przynajmniej dwie wady

                1. Trzeba zmieniać nazwę katalogu co jest kłopotem jeśli trzyma się statiki w repozytorium. Obejściem tego problemu jest użycie dowiązań symbolicznych.
                2. Zmiana pojedynczego obiektu dezaktualizuje wszystkie w danym folderze. Nawet jeśli zmienimy tylko favico-nkę to adresy wszystkich plików w danym katalogu się zmienią i zostaną pobrane na nowo przez przegladarki wszystkich użytkowników marnując transfer i zasoby serwera.

                Sprytniejszym rozwiązaniem jest dynamiczna zmiana nazwy pojedynczego pliku przez dodanie do niego np. czasu ostaniej modyfikacji oraz zdefiniowanie odpowiedniej regułki mode_rewrite w htaccess.

                RewriteEngine on
                RewriteRule ^(.*)\.[\d]{10}\.(css|js|jpe?g|gif|png)$ $1.$2 [L]

                Funkcja, która zajmie się modyfikowaniem nazwy pliku w kodzie dla PHP może wyglądać tak:

                /**
                 *  Given a file, i.e. /js/base.js, replaces it with a string containing the
                 *  file's mtime, i.e. /js/base.1221534296.js.
                 *
                 *  @param $file  The file to be loaded.  Must be an absolute path (i.e.
                 *                starting with slash).
                 */
                function auto_version($file)
                {
                  if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
                    return $file;
                 
                  $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
                  return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
                }

                sposób użycia

                <script src="<?php echo auto_version('/js/base.js'); ?>" type="text/javascript"></script>

                Analogiczny przykład dla Django wykorzystujący tę samą regułę mode_rewrite-a.

                Załóżmy, że kod jest zdefiniowany w /project/app/templatetags/helpers.py

                from django import template
                register = template.Library()
                import os, re
                 
                STATIC_PATH="/path/to/templates/"
                version_cache = {}
                 
                rx = re.compile(r"^(.*)\.(.*?)$")
                def version(path_string):
                    try:
                        if path_string in version_cache:
                            mtime = version_cache[path_string]
                        else:
                            mtime = os.path.getmtime('%s%s' % (STATIC_PATH, path_string,))
                            version_cache[path_string] = mtime
                 
                        return rx.sub(r"\1.%d.\2" % mtime, path_string)
                    except:
                        return path_string
                 
                register.simple_tag(version)

                i użycie w szablonie

                {% load helpers %}
                <link rel="stylesheet" type="text/css" href="{% version '/static/css/style.css' %}">

                UWAGA! Dodanie parametru GET do nazwy pliku nie jest rekomendowanym sposobem odświeżania plików statycznych. Niektóre przeglądarki (Opera, Safari) i serwery pośredniczące nie cache-ują takich obiektów, inne z kolei (Firefox i IE) ignorują parametry.

                Najmniej inwazyjne, niewymagające interwencji ze strony programisty jest użycie mod_pagespeed, który na poziomie Apache podmienia odsyłacze do wszelkich mediów a w przypadku zmiany w pliku sam ją wykrywa i generuje inny adres.

                Czy łączyć pliki JavaScript w jeden duży plik?

                Każdy serwer ma swoją przepustowość i może przyjąć ograniczoną liczbę żądań (ang request). Żądanie http – nawet jednopikselowego obrazka – wymaga nawiązania połączenia pomiędzy przeglądarką a serwerem, przesłania danych na serwer definiujących treść żądania, rozpoznanie czy dane żądanie może być spełnione czy też nie, a następnie zwrócenie do przeglądarki żądanego zasobu lub też nagłówków z odpowiednim kodem błędu. Wszystko to absorbuje czas i angażuje zasoby serwera. Wziąwszy pod uwagę, że pobraniu każdego dokumentu HTML towarzyszą dodatkowe zapytania o tzw. media czyli grafiki, video, pliki css, js, fonty, filmy flash itd., trzeba sobie uświadomić, że wpisanie jednego krótkiego url-a na pasku adresu przeglądarki i wciśnięcie entera uruchamia przy bardziej rozbudowanych stronach całą lawinę zapytań.

                To zagadnienie zaczyna być istotne dopiero, kiedy liczba unikalnych wizyt na Twojej stronie składa się przynajmniej z pięciu cyfr. Zatem jeśli nie masz przynajmniej 10 tys. unikalnych odwiedzin lub też znacząca ich liczba jest wygenerowana przez narzędzie do fałszowania statystyk, to możesz odłożyć przeczytanie tego rozdziału na później.

                Ograniczenie liczby requestów najłatwiej osiągnąć poprzez połącznie kilku plików w jeden. Jeden plik js to rozwiązanie teoretycznie idealne, ale niepraktyczne. Jeśli na stronie używamy, jQuery, tinymce z dużą liczbą pluginów, do tego chcielibyśmy mieć fancyboxa i parę widgetów z jquery.UI i jeszcze kilka innych ciekawych bibliotek to rozmiar pliku byłby niepokojąco duży nawet gdyby miał się ładować tylko raz a potem został zbuforowany.

                Osobiście uważam, że łączenie plików w jeden większy sprawdza się dla małych skryptów podobnie jak to się ma w przypadku zestawu małych ikonek połączonych w jedną grafikę i wyświetlanych za pomocą techniki CSS Sprite polegającej na manipulacji parametrami position-background.

                Warto też rozważyć połączenie skryptów, które są używane na większości, lub też często odwiedzanych podstronach. W dużych serwisach nigdy nie ma tak, aby 100% kodu JavaScript było wykorzystywane na każdej podstronie witryny. Poza tym część programów JavaScript urychamianych jest np. w części administracyjnej serwisu. Nie ma sensu łączyć kodu uniwersalnego z kodem uruchamianym dla wąskiego grona użytkowników.

                Kodem logicznie podzielonym między pliki łatwiej się zarządza dlatego warto pomyśleć nad rozwiązaniami atomatycznie łączącymi i kompresującymi pliki JavaScript takimi jak np. jsmin

                require 'jsmin.php';
                // Output a minified version of example.js.
                echo JSMin::minify(file_get_contents('example.js') . file_get_contents('example2.js'));

                Dla Django mamy szeroki wybór asset-manager-ów, ale możemy też skorzystać z algorytmu Douglas Crockford-a, przepisanego w języku Python i zaprezentowanego w jednej z recept w serwisie stackoverflow.

                #!/usr/bin/python
                # -*- coding: utf-8 -*-
                 
                import os, os.path, shutil
                 
                # This code is original from jsmin by Douglas Crockford, it was translated to
                # Python by Baruch Even. The original code had the following copyright and
                # license.
                #
                # /* jsmin.c
                #    2007-05-22
                #
                # Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
                #
                # Permission is hereby granted, free of charge, to any person obtaining a copy of
                # this software and associated documentation files (the "Software"), to deal in
                # the Software without restriction, including without limitation the rights to
                # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
                # of the Software, and to permit persons to whom the Software is furnished to do
                # so, subject to the following conditions:
                #
                # The above copyright notice and this permission notice shall be included in all
                # copies or substantial portions of the Software.
                #
                # The Software shall be used for Good, not Evil.
                #
                # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                # SOFTWARE.
                # */
                 
                from StringIO import StringIO
                 
                def jsmin(js):
                    ins = StringIO(js)
                    outs = StringIO()
                    JavascriptMinify().minify(ins, outs)
                    str = outs.getvalue()
                    if len(str) > 0 and str[0] == '\n':
                        str = str[1:]
                    return str
                 
                def isAlphanum(c):
                    """return true if the character is a letter, digit, underscore,
                           dollar sign, or non-ASCII character.
                    """
                    return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
                            (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
                 
                class UnterminatedComment(Exception):
                    pass
                 
                class UnterminatedStringLiteral(Exception):
                    pass
                 
                class UnterminatedRegularExpression(Exception):
                    pass
                 
                class JavascriptMinify(object):
                 
                    def _outA(self):
                        self.outstream.write(self.theA)
                    def _outB(self):
                        self.outstream.write(self.theB)
                 
                    def _get(self):
                        """return the next character from stdin. Watch out for lookahead. If
                           the character is a control character, translate it to a space or
                           linefeed.
                        """
                        c = self.theLookahead
                        self.theLookahead = None
                        if c == None:
                            c = self.instream.read(1)
                        if c >= ' ' or c == '\n':
                            return c
                        if c == '': # EOF
                            return '\000'
                        if c == '\r':
                            return '\n'
                        return ' '
                 
                    def _peek(self):
                        self.theLookahead = self._get()
                        return self.theLookahead
                 
                    def _next(self):
                        """get the next character, excluding comments. peek() is used to see
                           if an unescaped '/' is followed by a '/' or '*'.
                        """
                        c = self._get()
                        if c == '/' and self.theA != '\\':
                            p = self._peek()
                            if p == '/':
                                c = self._get()
                                while c > '\n':
                                    c = self._get()
                                return c
                            if p == '*':
                                c = self._get()
                                while 1:
                                    c = self._get()
                                    if c == '*':
                                        if self._peek() == '/':
                                            self._get()
                                            return ' '
                                    if c == '\000':
                                        raise UnterminatedComment()
                 
                        return c
                 
                    def _action(self, action):
                        """do something! What you do is determined by the argument:
                           1   Output A. Copy B to A. Get the next B.
                           2   Copy B to A. Get the next B. (Delete A).
                           3   Get the next B. (Delete B).
                           action treats a string as a single character. Wow!
                           action recognizes a regular expression if it is preceded by ( or , or =.
                        """
                        if action <= 1:
                            self._outA()
                 
                        if action <= 2:
                            self.theA = self.theB
                            if self.theA == "'" or self.theA == '"':
                                while 1:
                                    self._outA()
                                    self.theA = self._get()
                                    if self.theA == self.theB:
                                        break
                                    if self.theA <= '\n':
                                        raise UnterminatedStringLiteral()
                                    if self.theA == '\\':
                                        self._outA()
                                        self.theA = self._get()
                 
                 
                        if action <= 3:
                            self.theB = self._next()
                            if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
                                                     self.theA == '=' or self.theA == ':' or
                                                     self.theA == '[' or self.theA == '?' or
                                                     self.theA == '!' or self.theA == '&' or
                                                     self.theA == '|' or self.theA == ';' or
                                                     self.theA == '{' or self.theA == '}' or
                                                     self.theA == '\n'):
                                self._outA()
                                self._outB()
                                while 1:
                                    self.theA = self._get()
                                    if self.theA == '/':
                                        break
                                    elif self.theA == '\\':
                                        self._outA()
                                        self.theA = self._get()
                                    elif self.theA <= '\n':
                                        raise UnterminatedRegularExpression()
                                    self._outA()
                                self.theB = self._next()
                 
                 
                    def _jsmin(self):
                        """Copy the input to the output, deleting the characters which are
                           insignificant to JavaScript. Comments will be removed. Tabs will be
                           replaced with spaces. Carriage returns will be replaced with linefeeds.
                           Most spaces and linefeeds will be removed.
                        """
                        self.theA = '\n'
                        self._action(3)
                 
                        while self.theA != '\000':
                            if self.theA == ' ':
                                if isAlphanum(self.theB):
                                    self._action(1)
                                else:
                                    self._action(2)
                            elif self.theA == '\n':
                                if self.theB in ['{', '[', '(', '+', '-']:
                                    self._action(1)
                                elif self.theB == ' ':
                                    self._action(3)
                                else:
                                    if isAlphanum(self.theB):
                                        self._action(1)
                                    else:
                                        self._action(2)
                            else:
                                if self.theB == ' ':
                                    if isAlphanum(self.theA):
                                        self._action(1)
                                    else:
                                        self._action(3)
                                elif self.theB == '\n':
                                    if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
                                        self._action(1)
                                    else:
                                        if isAlphanum(self.theA):
                                            self._action(1)
                                        else:
                                            self._action(3)
                                else:
                                    self._action(1)
                 
                    def minify(self, instream, outstream):
                        self.instream = instream
                        self.outstream = outstream
                        self.theA = '\n'
                        self.theB = None
                        self.theLookahead = None
                 
                        self._jsmin()
                        self.instream.close()
                 
                def compress(in_files, out_file, in_type='js', verbose=False,
                             temp_file='.temp'):
                    temp = open(temp_file, 'w')
                    for f in in_files:
                        fh = open(f)
                        data = fh.read() + '\n'
                        fh.close()
                 
                        temp.write(data)
                 
                        print ' + %s' % f
                    temp.close()
                 
                    out = open(out_file, 'w')
                 
                    jsm = JavascriptMinify()
                    jsm.minify(open(temp_file,'r'), out)
                 
                    out.close()
                 
                    org_size = os.path.getsize(temp_file)
                    new_size = os.path.getsize(out_file)
                 
                    print '=> %s' % out_file
                    print 'Original: %.2f kB' % (org_size / 1024.0)
                    print 'Compressed: %.2f kB' % (new_size / 1024.0)
                    print 'Reduction: %.1f%%' % (float(org_size - new_size) / org_size * 100)
                    print ''
                 
                    os.remove(temp_file)
                 
                if __name__ == '__main__':
                    compress(['script1.src.js','script2.src.js'], 'script.min.js')

                UWAGA! Dodatkową zaletą obfuskacji – czyli zaciemniania kodu – jest jego mniejsza objętość. Dlatego też zachęcam do tego „procederu” nie tyle w celu ochrony własności intelektualnej co minimalizacji plików js.

                Czy zaciągać pliki JavaScript z zewnętrznych serwerów?

                Istnieje wiele serwisów społecznościowych czy też wyspecjalizowanych w określonej tematyce, które oferują funkcjonalności mogące w uatrakcyjnić naszą własną stronę www. Poprzez integrację z usługą uwierzytelniania użytkowników za pośrednictwem Facebooka, wykorzystując api Soundcoluda do udostępniania muzyki lub choćby osadzając mapy Googla wzbogacamy nasz serwis o elementy, które samodzielnie byłoby o wiele trudniej zaimplementować. Większość zaawansowanych dodatków, jak również tych całkiem niepozornych jak Facebook Like wymaga dołączenia JavaScriptu. Okazuje się, że także duże javascriptowe frameworki możemy zaciągać z zewnętrznego serwera.

                Google hostuje wybrane biblioteki JavaScript i udostępnia je w wielu wersjach zachęcając do osadzenia na swoich stronach.

                Js frameworki hostowane przez google

                Jeśli spojrzymy na oficjalną stronę jQuery zobaczymy w jej źródle.

                <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>

                Można się zastanawiać nad zaletami i wadami takiej praktyki. Niewątpliwie warto mieć kopię podstawowej biblioteki – używanej w serwisie jako bazę i szkielet wszelkich skryptów – na swoim serwerze. Nie przeszkadza to równocześnie używać kopii tej biblioteki udostępnianej z innego serwera. Delegowanie części ruchu na zewnątrz z pewnością odciąży nieco naszego własnego Apacha. Poza tym trzeba wziąć pod uwagę, że serwer Googla jest zoptymalizowany dla treści statycznych, co gwarantuje szybkie i niezawodne działanie.

                Temat dotyczący serwerów plików statycznych CDN-ów (Content Delivery Network) jest bardzo ciekawy, ale wykracza nieco poza ramy tego opracowania, które traktuje raczej o tym co może zrobić programista i porusza sprawy całkiem podstawowe.

                Czy ładować pliki JavaScript head czy w body?

                Osadzając odnośniki do plików JavaScript w HTML-u w sposób tradycyjny …

                <script type="text/javascript" src="scripts_1.js"></script>
                <script type="text/javascript" src="scripts_2.js"></script>

                … skrypty ładowane są synchronicznie czyli po kolei. Co więcej – do czasu załadowania pliku js wstrzymywane jest renderowanie strony. Można to zasymulować wywołując w skrypcie funkcję stop, która przerywa całkowicie dalsze ładowanie HTML-a, a co za tym idzie pozostałych, osadzonych w kodzie obiektów.

                stop();

                Z tego powodu pliki JavaScript mogą stać się tzw. SPOF (Single Point Of Failure) czyli pojedynczym elementem zaburzającym działanie całości. W praktyce ma to miejsce wtedy kiedy plik JavaScript z powodu wielkości lub kłopotów na łączach długo się ładuje. Najczęściej spowalnia to po prostu załadowanie całej witryny. W najgorszym przypdaku serwer plików nie odpowiada, a przeglądarka czaka w nieskończoność na zasób lub też kod błędu.

                Testowanie SPOF na stronie WWW spowodowanego ładowaniem JavaScript

                Zob. Testing for Frontend SPOF

                Programiści często obchodzą ten problem ładując skrypty nie w nagłówku strony (czyli w head) tylko w ciele strony (w body) i to najlepiej na samym dole. Wychodzą z założenia, że jeśli nawet sypnie się JavaScript to przynajmniej cała treść, style i obrazki się załadują.

                UWAGA! Ładowanie skryptów w body jest absolutnie niepotrzebne. Wystarczy odroczyć pobieranie skryptów do czasu sparsowania całej strony. Służy do tego atrybut defer (od ang. deferred – odroczony).

                <script type="text/javascript" src="script.js" defer="defer"></script>

                Teraz funkcja strop nie spowoduje przerwania renderowania strony ani ładowania innych skryptów.

                Czy ładować pliki JavaScript synchronicznie czy asynchronicznie?

                HTML5 przyniósł wiele ciekawych innowacji także w sprawie ładowania skryptów pojawiło się coś nowego. Tag script wzbogacił się o atrybut async, który determinuje ładowanie asynchroniczne.

                <script async="async" src="scripts_1.js"></script>
                <script async="async" src="scripts_2.js"></script>

                Oba skrypty będą pobierane niezależnie od siebie i od dokumentu HTML dlatego jeśli nawet w pierwszym skrypcie wywołamy funkcję „stop” nie spowoduje to przerwania renderowania strony ani ładowania drugiego skryptu. Przewagą trybu asynchronicznego nad odraczaniem ładowania do czasu sparsowania strony HTML (patrz. atrybut defer), że skrypt może być pobierany równocześnie z samym dokumentem.

                UWAGA! Jeśli drugi skrypt będzie pobierany w trybie synchrnicznym to pierwszy także przełączy się w ten tryb. Mówiąc inaczej skrypty będą pobierane asynchronicznie jeśli mają ustawiony parametr async na true oraz wszystkie później rozpoczynane pobrania także odbywają sie w tym trybie.

                Ładowanie asynchroniczne jest idealne do inicjowania na stronie dodatkowych usług takich jak np. statystyki:

                  var _gaq = _gaq || [];
                  _gaq.push(['_setAccount', 'UA-XXXXX-Y']);
                  _gaq.push(['_trackPageview']);
                 
                  (function() {
                    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
                  })();

                lub facebook connect

                $(document).ready(function() {
                 
                    window.fbAsyncInit = function() {
                        // kod realizujący logowanie na stronie via facebook
                    };
                    (function(d){
                        // nadanie id zapobiega wielokrotnemu ładowaniu skryptu
                        var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
                        js = d.createElement('script'); js.id = id; js.async = true;
                        // brak protokołu nie jest błędem
                        js.src = "//connect.facebook.net/pl_PL/all.js";
                        d.getElementsByTagName('head')[0].appendChild(js);
                    }(document));
                });

                UWAGA W powyższym przykładzie warto zwrócić uwagę na brak protokołu w adresie skryptu. Zostanie użyty taki sam protokół jaki został wywołany dla strony www. Zapobiega to zgłaszaniu ostrzeżeń przez przeglądarki internetowe, które reagują, kiedy strona idzie po SSL-u (https) a media są dostarczane do przeglądarki bez szyfrowania (http).

                W przypadku plików doładowywanych asynchronicznie trzeba czekać aż zostaną w całości pobrane i będą gotowe do pracy. Jeśli jesteś autorem oprogramowania możesz przyjąć, że w kodzie strony zostaną zdefiniowane jakieś zmienne (np. _gaq), albo funkcje (np. window.fbAsyncInit), które po załadowaniu pliku js zostaną w nim użyte. Tej strategi nie można użyć dla ładowanych asynchronicznie bibliotek takich jak jQuery. To nie jQuery wywoła zdefiniowany przez Ciebie kod tylko Ty chcesz użyć metod oferowanych przez ten rozbudowany framework. Dlatego musisz wykryć moment załadowania pliku i uruchomić swoje funkcje dopiero wtedy kiedy dostępny będzie obiekt jQuery.

                JavaScript jest językiem zorientowanym na zdarzenia. Między innymi obsługuje też zdarzenia związane z ładowaniem obrazków czy plików js. Także w tej kwestii IE wyróżnia się spośród innych przeglądarek autorskimi rozwiązaniami i zamiast zdarzenia load ma swoje onreadystatechange. Tak czy owak można to wykorzystać do napisania funkcji umożliwiającej dynamiczne ładowanie skryptów.

                (function(global){
                    var cache = {}
                    var call = function(url, callback) {
                        if (typeof(callback) == "function") {
                            cache[url].callbacks.push(callback);
                        }
                        // run only if loaded
                        if (cache[url].status == 'loaded') {
                            for (i in cache[url].callbacks) {
                                cache[url].callbacks[i]();
                            }
                            // reset callbacks
                            cache[url].callbacks = [];
                        }
                    }
                    global.loadScript = function(url, callback){
                        if (url in cache) {
                            call(url, callback);
                            return;
                        } else {
                            cache[url] = {'status':'init','callbacks':[]};
                        }
                        var script = document.createElement("script")
                        script.type = "text/javascript";
                        script.async = true;
                        if (script.readyState){ //IE
                            script.onreadystatechange = function(){
                                if (script.readyState == "loaded" ||
                                    script.readyState == "complete"){
                                    script.onreadystatechange = null;
                                    cache[url].status = 'loaded';
                                    call(url, callback);
                                }
                            };
                        } else { //Others
                            script.onload = function(){
                                cache[url].status = 'loaded';
                                call(url, callback);
                            };
                        }
                        script.src = url;
                        document.getElementsByTagName("head")[0].appendChild(script);
                    }
                })(this);
                 
                 
                loadScript('//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js'
                    , function(){
                        $('p').attr('style', 'border: 1px solid red');
                    });

                Funkcja ta choć prosta ma dwie przewagi nad jquerową metodą $.getScript, a mianowicie nie wymaga jQuery (nie używa też do ładowania ajaxa) i dba o to aby każdy skrypt był ładowany tylko raz. Ma to duże znaczenie w dynamicznie bydowanych aplikacjach wykorzystujących wzorzec projektowy opóźnionego ładowania (ang. lazy loading pattern), którego idea zasadza się na myśli, że zasób pobieramy dopiero w chwili kiedy go potrzebujemy.

                Koncepcję wzorca opóźnionego ładowania dla języka używanego w przeglądarce trzeba nieco zmodyfikować. W końcu nie wyobrażam sobie aby pobierać TinyMce wraz z jego wszystkimi pluginami dopiero w chwili kiedy użytkownik kliknie w pole tekstowe formularza. Choć jest to technicznie możliwe to z punktu widzenia funkcjonalności byłoby z uwagi na opóźnienia totalnym nieporozumieniem. Chodzi tu raczej o dołączanie do poszczególnych podstron HTML skryptów js tylko tam gdzie są faktycznie potrzebne.

                Rozbudowane aplikację mają to do siebie, że składają się z wielu klocków, z których jeden nie musi wiedzieć nic o innym, a przynajmniej nie musi widzieć czy inny moduł załadował już wymagane biblioteki JavaScript. Co z tego, że główny kontent strony stanowi formularz, rejestracji nowego użytkownika a w nagłówku strony umieszczono formularza logowania, który prezentowany jest zresztą standardowo na wszystkich podstronach serwisu. Oba formularze nie mają ze sobą nic wspólnego. Dla każdego z nich można podjąć próbę załadowania jquery.validate. Jeśli skorzysta się z loadScript można mieć pewność, że plugin zostanie pobrany tylko raz.

                loadScript('jquery.validate.js', function(){loginFormValidation();});
                loadScript('jquery.validate.js', function(){registerFormValidation();});

                Kwestia wielokrotnego ładowania tego samego pliku została rozpracowana, jednak mamy do rozwiązania jeszcze jedną zagwozdkę, a mianowicie zależności

                Fancybox jest dodatkiem, który świetnie sprawdza się do prezentacji zdjęć na stronach internetowych. Opcjonalnie można go używać z pluginem jquery.mousewheel umożliwiającym przechodzenie pomiędzy zdjęciami w galerii poprzez ruch rolki w myszce. Zanim zainicjujemy fancyboksa byłoby dobrze aby oba pliki były już gotowe do użycia, z kolei oba pluginy wymagają wczesniejszego załadowania podstawowej biblioteki jQuery. Kolejność ładowania skryptów jest zatem istotna.

                LABjs udostępnia przyjemny interfejs zoptymalizowany dla celu asynchronicznego, łańcuchowego ładowania skryptów.

                <script type="text/javascript" src="script.js"></script>
                <script type="text/javascript">
                $LAB
                    .script("jquery.js")
                    .script("jquery.mousewheel.js")
                    .script("jquery.fancybox.js")
                    .wait(function(){
                        $('a.img').fancybox();
                    });
                </script>

                albo jeszcze prościej

                $LAB
                    .script("jquery.js", "jquery.mousewheel.js", "jquery.fancybox.js")
                    .wait(function(){
                        $('a.img').fancybox();
                    });

                Przy użyciu funkcji loadScript trzebaby to było zrobić w mniej zgrabny sposób.

                loadScript('jquery.js', function(){
                    loadScript('jquery.mousewheel.js', function(){
                        loadScript('jquery.fancybox.js', function(){
                            $('a.img').fancybox();
                    });
                    });
                });

                LABjs ma wszystkie zalety loadScript i dokłada do tego jeszcze trochę od siebie. Szczerze polecam ją do zastosowań profesjonalnych. We wpisie Sposoby wczytywania JavaScript przeczytałem o jeszcze innych podobnych narzędziach.

                Podsumowanie

                Opisałem zaledwie kilka i to tych podstawowych aspektów pracy z JavaScriptem w projekcie aplikacji internetowej. Jak widać jest on ściśle powiązany z technologiami z którymi koegzystuje i już sam proces jego dołączania może mieć wpływ na funkcjonowanie całości jaką jest strona www.

                Wszystkich zachęcam jeszcze raz do przeczytania – przynajmniej najważniejszych moim zdaniem – twierdzeń czy wskazówek zawartych w tym artykule. Wytłuściłem je wszystkie i poprzedziłem słowem UWAGA!, tak więc nie powinno być problemu z ich odnalezieniem.

                Podziel się z innymi!

                  Domyślne uruchamianie Windowsa za pomocą GRUB boot loadera

                  Podziel się z innymi!

                    Jakiś czas temu kupiłem nowego kompa i stary w schedzie przypadł mojej żonie. Mimo moich usilnych działań uświadamiających nadal przedkłada ona Windowsa ponad Linuxa więc jak tylko dostała laptopa w swoje władanie wierciła mi dziurę w brzuchu aby usunąć z dysku Ubuntu, a przynajmniej sprawić aby domyślnie uruchamiał się Windows 7.

                    Zmiana kolejności uruchamiania w GRUB boot loaderze jest dość prostą czynnością wielokrotnie opisywaną w necie. Należy jednak zwrócić uwagę na wersję GRUB-a, gdyż w przypadku np. najnowszego Ubuntu używany jest GRUB2, w którym zmianę kolejności bootowania robi się nieco inaczej.

                    Przede wszystkim należy wyedytować odpowiedni plik

                    sudo /etc/default/grub

                    i odnaleźć w nim opcję GRUB_DEFAULT domyślnie ustawioną na 0

                    GRUB_DEFAULT=0

                    Liczba odnosi się do pozycji w menu bootowania. Taka lista wpisów może wyglądać następująco:

                    Ubuntu, Linux 3.0.0-17-generic
                    Ubuntu, Linux 3.0.0-17-generic (recovery mode)
                    memory test (memtest86+)
                    memory test (memtest86+, serial console 115200)
                    Windows 7 (loader) (on /dev/sda1)

                    Tak więc aby ustawić Windowsa jako domyślnie uruchamiany system należało by opcji GRUB_DEFAULT przypisać 4

                    GRUB_DEFAULT=4

                    Na koniec niezbędnym jest jeszcze aktualizacja ustawień GRUBA2 poprzez wykonanie polecenia

                    sudo update-grub

                    To wystarcza by zmienić kolejność bootowania. Jednak jest mały problem. Polega na tym, że przy aktualizacji kernela do nowej wersji do menu dodawane są nowe linie i wpis dotyczący Windowsa przesuwa się na dalszą pozycję. Można oczywiście każdorazowo aktualizować ustawienia GRUB2-a jednak jest lepsze rozwiązanie.

                    W pliku /boot/grub/grub.cfg odszukujemy sekcję odpowiedzialną za uruchamianie Windowsa

                    menuentry "Windows 7 (loader) (on /dev/sda1)" --class windows --class os {
                    	insmod part_msdos
                    	insmod ntfs
                    	set root='(hd0,msdos1)'
                    	search --no-floppy --fs-uuid --set=root 24B2367EB2365510
                    	chainloader +1
                    }

                    i do opcji GRUB_DEFAULT przypisujemy tytuł wpisu zamiast jego numeru

                    GRUB_DEFAULT="Windows 7 (loader) (on /dev/sda1)"

                    W ten sposób co prawda pozycja wpisu w menu się nie zmieni, ale za to przy inicjalizacji domyślnie zaznaczona będzie pozycja w menu dotycząca Windowsa i jeżeli nie zareagujemy to po czasie określonym w opcji GRUB_TIMEOUT uruchomi się Windows.

                    Jak już pisałem aktualizację ustawień GRUB2 kończy polecenie sudo update-grub. Bez jego wykonania zmiany w ustawieniach nie zostaną uwzględnione.

                    Podziel się z innymi!

                      Facebook – dodawanie aplikacji do fanpage-a

                      Podziel się z innymi!

                        Facebook to dla developera wieczne utrapienie. Ciągłe zmiany w interfejsie, w API, dodawanie coraz to nowych funkcjonalności czy choćby permanentny redesign zmuszają do bezustannego poprawiania napisanych aplikacji. Pisząc aplikacje na facebook średnio raz na pół roku muszę być przygotowany na to, że połowę rzeczy, które nauczyłem się ostatnio implementować teraz będę musiał zrobić w zupełnie inny sposób. Czytanie tutoriali, czy wskazówek na blogach często nie ma sensu gdyż zamieszczone porady są już dawno nieaktualne. Łapię się na tym, że napisanie średnio rozbudowanej aplikacji zajmuje mniej czasu niż opublikowanie i zintegrowanie jej z facebookiem.

                        Dzisiaj klient zgłosił mi, że nie można dodać aplikacji do fanpage-a gdyż nigdzie nie ma przycisku „Add to my page”. Zawsze jak się wchodziło na stronę aplikacji była możliwość dodania jej do fanpage-a za pomocą jednego kliknięcia, a później ewentualnie skonfigurowanie jej tak aby wyświetlała się w zakładce, a teraz nie ma. I co? I zaczęło się rycie w dokumentacji i googlach.

                        W końcu znalazłem przepis na to jak dodać analogiczny przycisk do kodu samej aplikacji

                        <html xmlns="http://www.w3.org/1999/xhtml"
                          xmlns:fb="https://www.facebook.com/2008/fbml">
                          <head>
                            <title>My Add to Page Dialog Page</title>
                          </head>
                          <body>
                            <div id='fb-root'></div>
                            <script src='http://connect.facebook.net/en_US/all.js'></script>
                            <p><a onclick='addToPage(); return false;'>Add to Page</a></p>
                            <p id='msg'></p>
                         
                            <script> 
                              FB.init({appId: "YOUR_APP_ID", status: true, cookie: true});
                         
                              function addToPage() {
                         
                                // calling the API ...
                                var obj = {
                                  method: 'pagetab',
                                  redirect_uri: 'YOUR_URL',
                                };
                         
                                FB.ui(obj);
                              }
                         
                            </script>
                          </body>
                        </html>

                        Irytuje mnie ta polityka Facebooka niemiłosiernie ponieważ, kiedy coś przestaje działać, albo wyglądać na facebooku klient ma pretensje do mnie. Ja z kolei nie mam ochoty poprawek i modyfikacji wynikających ze zmiany flow na facebooku robić w ramach gwarancji bo to często nie są sprawy 5 minutowe.

                        Podziel się z innymi!

                          Ad ACTA – o prawie autorskim i prawach pokrewnych

                          Podziel się z innymi!

                            „Nie istnieje człowiek, sprawa, zjawisko, a nawet żadna rzecz, dopóty, dopóki w sposób swoisty nie zostały nazwane. Władzą jest więc moc swoistego nazywania ludzi, spraw, zjawisk i rzeczy, tak aby te określenia przyjęły się powszechnie. Władza nazywa, co jest dobre, a co złe, co jest białe, a co czarne, co jest ładne, a co brzydkie, bohaterskie lub zdradzieckie; co służy ludowi i państwu, a co lud i państwo rujnuje; co jest po lewej ręce, a co po prawej, co jest z przodu, a co z tyłu. Władza określa nawet, który bóg jest silny, a który słaby, co należy wywyższać, a co poniżać.”

                            Tymi słowy rozpoczął Zbigniew Nienacki jedną ze swoich powieści będącą wizją powstania państwa polskiego. Wątki i postaci przedstawione w książkach z serii „Dagome iudex” są wyimaginowane choć tło historyczne oraz miejsca mają odniesienie do rzeczywistości. Celne są też spostrzeżenia, jak i przemyślenia przemycane czytelnikom pod postacią wypowiedzi bohaterów, czy też cytatów z rzekomych dzieł, jak choćby przytoczony wyżej fragment rozdziału „O sztuce rządzenia ludźmi” z Księgi Grzmotów i Błyskawic.

                            Zatem nazwano nas piratami.

                            Kiedy pewien rolnik wymyślił kosę pomyślał sobie – Będę teraz rżnął żyto dwa razy szybciej od mojego sąsiada, który używa sierpa, a do tego ominą mnie bóle krzyża. Jakież musiało być jego rozczarowanie, kiedy sąsiad zrobił sobie takie samo narzędzie. Rolnik poszedł do sąsiada i powiedział.

                            – Coś mi się chyba należy za to, że ułatwiłem Ci pracę?

                            – Bóg zapłać – usłyszał w odpowiedzi.

                            A gdyby chroniły go prawa własności intelektualnej to rolnik mógłby odpowiedzieć:

                            – Boga zostaw w spokoju. Zapłać sam.

                            I albo sąsiad musiałby dalej kosić w kucki, albo wynalazca żyłby z licencji jak Microsoft, który zarabia więcej na patentach niż na bieżącej „produkcji”. Nie wiem jak szybko rozwinęło by się rolnictwo ale najbogatszym darmozjadem na ziemi byłby spadkobierca wynalazcy koła.

                            Życie bardów nie było łatwe ale odmieniło się wraz z wynalazkiem pozwalającym utrwalić dźwięk. Wystarczyło raz zaśpiewać i można to było sprzedać wielokrotnie. Co za pech!!! Że też ten rozwój technologiczny nie zatrzymał się na etapie płyt gramofonowych? Kto potrafił takowe kopiować? Taśmy magnetofonowe to nieszczęście ale mp3 i internet to już dla muzyków prawdziwa katastrofa – o wytwórniach już nie wspomnę. Nie dość, że trzeba znowu zacząć dawać koncerty to jeszcze stale wymyślać nowe utwory bo w końcu ile można słuchać „Somebody That I Used To Know” (może dla odmiany cover)?

                            Ale niektórzy muzycy tak nie chcą.

                            Czyż nie byłoby to wspaniałe gdybym – będąc kierowcą – mógł raz przejechać trasę autobusem, a potem autobus sam już by jeździł z zaprogramowanym wirtualnym kierowcą, a organizacje typu MPK (Miejskie Przedsiębiorstwo Komunikacyjne) zajmowało by się ochroną moich praw i kasowało każdego, kto chciałby się ze mną przejechać? Na szczęście nie jestem kierowcą autobusu bo moje marzenie nigdy by się nie spełniło. Jestem za to programistą więc kto wie?

                            Artyści wmawiają nam, że ściągając plik z internetu okradamy ich. Jeśli ja ukradnę komuś rower to ten ktoś nie ma roweru, ale jeśli ja wysłucham płyty w internecie to czy grupa, która ją nagrała traci ją? Krawiec też chciałby uszyć koszulę sprzedać ją i mieć ją nadal aby móc znowu ją sprzedać. Jeżeli technologia sprzyja firmie fonograficznej – umożliwia powielanie utworów w nieskończoność w celu jego wielokrotnej sprzedaży – to takie działanie każe nam się postrzegać jako moralne, a jeśli ta sama technologia pozwala zaoszczędzić pieniądze przeciętnego użytkownika to wtedy piętnuje się go mianem złodzieja.

                            – Jaskier – westchnął wiedźmin, robiąc się naprawdę senny. – Jesteś cynik, świntuch, kurwiarz i kłamca. I nic, uwierz mi, nic nie ma w tym skomplikowanego.

                            Andrzej Sapkowski w sadze o Wiedźminie – „Miecz przeznaczenia”.

                            Cztery lata poświęcił Michał Anioł freskom plafonowym w Kaplicy Sykstyńskiej. Zapewne papież Juliusz II nieźle za to zapłacił, co się Michałowi niewątpliwie należało bo arcydzieło wielkie popełnił. Ja bym w każdym razie nie malował sufitu kościoła przez cztery lata za grosze. Produkcje Hollywood też kosztują majątek, a aktorstwo wielu gwiazd zasługuje by je docenić, ale czy trzeba od razu gażę liczyć w milionach? Czy pensja w wysokości nauczyciela lub pielęgniarki nie jest godziwa? Dobre filmy zwracają się błyskawicznie a najlepsze zarabiają krocie pomimo tego, albo może wręcz dlatego, że są powszechnie dostępne. Najwięcej zarabia się nie na emisji samego filmu ale na wszelkiego rodzaju związanych z nim gadżetach. Jeśli jesteś matką lub ojcem, a Twoje dziecko zakochało się w McQueenie albo jego ulubionym bohaterem jest Buzz Astral to policz ile Cię w sumie kosztowała przyjemność obejrzenia z dzieckiem „The Cars” lub „Toystory”.

                            W „Księciu” Niccolò Machiavelli napisał „Ludzie tak są prości i tak naginają się do chwilowych konieczności, że ten, kto oszukuje, znajdzie zawsze takiego, który da się oszukać.” Wielu jest takich, którzy bazując na ludzkiej ciekawości, naiwności i poczuciu winy nie dość, że namówią klienta do kupienia często kiepskiego (wirtualnego) produktu, to jeszcze przekonają go do tego, że jest on wart ceny, którą zapłacił. Z drugiej strony także Machiavelli stwierdził „Kto pragnie oszukać, zawsze spotka takiego, który oszukać w żaden sposób nie pozwoli.”

                            W czasach tak zwanej rewolucji przemysłowej miliony harowały na garstkę kapitalistów, którzy bogacili się, aż pewnego dnia do władzy doszedł lud i nazwał ich burżujami. Nie pragnę rewolucji bo jak pokazuje historia jedna patologia zmieniła się w drugą. Teraźniejszość jednak udowadnia, że trzeba być czujnym bo to co nas dziś niepokoi jutro będzie przerażać. Co was przeraża w tym artykule? Piractwo?

                            Nie podoba mi się pomysł podpisania ACTA bo nie służy ochronie naturalnych praw do godziwego wynagrodzenia tylko do utrwalenia wynaturzonych praw do wyzyskiwania innych. Prawo do nazywania siebie autorem przysługuje każdemu, kto jest faktycznym twórcą lub pomysłodawcą danego dzieła, jednak prawa pokrewne w tym wszelkiego rodzaju koncesje, licencje, patenty itp. muszą ulec przedefiniowaniu gdyż w obecnej formie służą jedynie produktywności prawników ścigających się z komornikami w ściąganiu haraczy.

                            I kto tu jest piratem?

                            Podziel się z innymi!

                              Lepszy var_dump czyli przyjemniejsze debugowanie PHP

                              Podziel się z innymi!

                                bigWeb/Debug/Dumper to narzędzie funkcjonalnie odpowiadające funkcji var_dump
                                Jego przewagą jest sposób prezentacji danych, a także dodatkowe informacje
                                ułatwiające debugowanie aplikacji.

                                Najpoważniejszą wadą Dumpera jest to, że jest on dość obciążający dla aplikacji
                                gdyż uzyskanie informacji o pliku i linii, w której dump został wywołany
                                wymaga każdorazowo rzucenia wyjątku. Dlatego też w wersji produkcyjnej
                                Dumper powinien być wyłączony

                                UWAGA! Biblioteka zaprezentowana w przykładach wymaga min PHP 5.3
                                z uwagi na użycie przestrzeni nazw.
                                Ponieważ jednak nie na wszystkich serwerach jest już PHP w wersji obsługującej
                                przestrzenie nazw przygotowałem także wersję Dumpera nie wymagającą ich użycia.
                                w takim przypadku wywołanie bigWeb\Debug\Dumper::factory(); należy zastąpić
                                poprzez wywołanie bigWeb_Debug_Dumper::factory(); i analogicznie w przypadku
                                innych klas. Wersja Dumpera dla PHP < 5.3 zawarta jest w archiwum zip bigWeb\Debug\Dumper do ściągnięcia.

                                Instalacja

                                include_once('./Debug.php');

                                Można też użyć autoloadera. Wszystkie klasy potrzebne do działania Dumpera
                                zdefiniowane są w pliku „Debug.php”. Wyjątkiem jest FirePHP, który należy
                                dodać osobno jeśli chcemy wyświetlać dane w konsoli javascript

                                include_once('./FirePHP.php');

                                Podstawowe użycie

                                dump('some data');

                                w wyniku otrzymamy:

                                TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 37 ET: 0 MU: 1.44 mb MPU: 1.67 mb

                                ‚some data’
                                string(9) "some data"
                                

                                Dumpa można wywołać z wieloma parametrami o różnych wartościach równocześnie.

                                $str = 'text';
                                $int = 7;
                                $arr = array('foo', 'bar');
                                $ob = new ArrayObject();
                                $bool = FALSE;
                                 
                                dump($str, $int, $arr, $ob, $bool);
                                TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 47 ET: 0.000904 MU: 1.47 mb MPU: 1.67 mb

                                $str
                                string(4) "text"
                                
                                $int
                                int(7)
                                
                                $arr
                                array(2) {
                                  [0]=>
                                  string(3) "foo"
                                  [1]=>
                                  string(3) "bar"
                                }
                                
                                $ob
                                object(ArrayObject)#3 (1) {
                                  ["storage":"ArrayObject":private]=>
                                  array(0) {
                                  }
                                }
                                
                                $bool
                                bool(false)
                                

                                Ukrywanie komunikatów

                                Aby zapobiec wyświetlaniu jakichkolwiek komunikatów przez dumpera należy go wyłączyć.

                                bigWeb\Debug\Dumper::setEnabled(FALSE);
                                dump('it should not show');

                                Aby ponownie włączyć:

                                bigWeb\Debug\Dumper::setEnabled(TRUE);

                                Definiowanie alternatywnych logerów

                                Domyślnie dumper do zrzutu danych używa wbudowanej funkcji var_dump jednak
                                klasa Dumpera jest zbudowana w oparciu o wzorzec projektowy Obserwator, gdzie
                                obserwatorami są wyspecjalizowane klasy do logowania i prezentacji zrzucanych danych.
                                Dzięki temu możemy wybierać sposób logowania informacji.

                                Możemy zapisywać dane w pliku – przydatne np. przy testowaniu przekierowań

                                $d = bigWeb\Debug\Dumper::factory();
                                $o = new bigWeb\Debug\Dumper\FileDump();

                                Koniecznym jest wskazanie katalogu w którym będą zapisywane logi.
                                Katalog ten musi mieć oczywiście ustawione prawa do zapisu

                                $o->setDir(dirname(__FILE__));
                                $d->attach($o);
                                $d->notify('foo');

                                wynik zostanie zapisany w pliku /home/www/bigWeb/Debug/2012-01-12.log.php

                                TIME: 20:46:18 FILE: /home/zh/www/Debug/example.php LINE: 71 ET: 0.001143 MU: 1.55 mb MPU: 1.67 mb
                                ----------| 'foo' |----------
                                foo
                                .----------------------------
                                

                                Można też skorzystać z dobrodziejstw FireBuga – dodatku do Firefoxa i
                                wyświetlać dane w konsoli javascript. W tym przypadku musimy najpierw
                                załadować bibliotekę FirePHP będącej „pomostem” pomiędzy PHP i FireBugiem.

                                include_once('./FirePHP.php');
                                 
                                $d = bigWeb\Debug\Dumper::factory();
                                $d->attach(new bigWeb\Debug\Dumper\FireDump());
                                $d->notify('bar');

                                wynik pokarze nam się w konsoli FireBug-a

                                Wynik Dumpera w konsoli FireBug

                                UWAGA! Ponieważ informacje do FireBuga są przesyłane za pomocą nagłówków HTTP
                                biblioteka FirePHP wymaga buforowania wyjścia (output bufering). Koniecznym
                                jest zatem użycie funkcji ob_start() w przeciwnym razie można się spotkać
                                z błędem „headers already sent error”

                                Łańcuch wywołań

                                Niekiedy chcemy wiedzieć jakie funkcje i metody zostały wywołane nim został
                                wykonany kod w danym miejscu. Aby zobaczyć cały łańcuch wywołań należy
                                ustawić flagę show_trace na TRUE.

                                function foo() {
                                    $d = bigWeb\Debug\Dumper::factory();
                                    $d->attach(new bigWeb\Debug\Dumper\VarDump());
                                    $d->setShowTrace(TRUE);
                                    $d->notify('Show chain requests');
                                }
                                 
                                function bar() {
                                    foo();
                                }
                                 
                                bar();
                                TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 97 ET: 0.005149 MU: 1.57 mb MPU: 1.67 mb

                                $d->notify(‚Show chain requests’);
                                string(19) "Show chain requests"
                                
                                Array
                                (
                                    [0] => Array
                                        (
                                            [file] => /home/www/bigWeb/Debug/example.php
                                            [line] => 97
                                            [function] => notify
                                            [class] => bigWeb\Debug\Dumper
                                            [type] => ->
                                            [args] => Array
                                                (
                                                    [0] => Show chain requests
                                                )
                                
                                        )
                                
                                    [1] => Array
                                        (
                                            [file] => /home/www/bigWeb/Debug/example.php
                                            [line] => 101
                                            [function] => foo
                                            [args] => Array
                                                (
                                                )
                                
                                        )
                                
                                    [2] => Array
                                        (
                                            [file] => /home/www/bigWeb/Debug/example.php
                                            [line] => 104
                                            [function] => bar
                                            [args] => Array
                                                (
                                                )
                                
                                        )
                                
                                )
                                

                                Praca na serwerze produkcyjnym

                                W zasadzie Dumper powinien być wyłączony na serwerze produkcyjnym bo obciąża
                                aplikację, a ponadto może wyświetlać dane wrażliwe. Nie mniej w pewnych
                                wyjątkowych okolicznościach możemy chcieć go użyć. Musimy wcześniej przewidzieć
                                taką sytuację i dodatkowo skonfigurować przynajmniej jedną z instancji Dumpera
                                ustawiając tak zwany secret_key

                                $d = bigWeb\Debug\Dumper::factory();
                                $d->attach(new bigWeb\Debug\Dumper\FireDump());
                                $d->setSecretKey('verysicretkey');

                                Aby te dane były widoczne należy wywołać url metodą GET z parametrem
                                secret_key=1. W tym przypadku będzie to

                                example.php?verysicretkey=1
                                
                                bigWeb\Debug\Dumper::setEnabled(FALSE);
                                $d->notify('Visible when set secret_key');

                                aby ponownie włączyć:

                                bigWeb\Debug\Dumper::setEnabled(TRUE);

                                Jeśli wywołamy url metodą GET z sekretnym kluczem, Dumper ustawia ciasteczko
                                debugcookie o wartości wywiedzionej z secret_key. Od tej pory nie trzeba już dodawać do
                                adresu żadnego specjalnego parametru. Jeśli jednak chcielibyśmy wyłączyć
                                debugowanie należy wywołać url z secret_key=0

                                example.php?verysicretkey=0
                                

                                Tworzenie funkcji pomocniczych

                                Tworzenie obiektu i dodawanie obserwatorów nie jest zbyt wygodnym rozwiązaniem.
                                Narzędzie do debugowania powinno być extremalnie proste i szybkie w użyciu.
                                Dlatego warto sobie zdefiniować funkcję pomocniczą – podobną do „debug();”

                                function dump_all() {
                                    $_args = func_get_args();
                                    static $d = null;
                                    if ( $d === null )
                                    {
                                        // Proszę zwrócić uwagę na wywołanie metody factory z parametrem $level = 3
                                        $d = bigWeb\Debug\Dumper::factory(3);
                                        $o = new bigWeb\Debug\Dumper\FileDump();
                                        $o->setDir(dirname(__FILE__));
                                        $d->attach($o);
                                        $d->attach(new bigWeb\Debug\Dumper\FireDump());
                                        $d->attach(new bigWeb\Debug\Dumper\VarDump());
                                 
                                    }
                                    call_user_func_array(array($d, 'notify'), $_args);
                                }
                                 
                                dump_all('foo bar');

                                wynik zostanie zapisany w pliku

                                TIME: 20:46:44 FILE: /home/zh/Praca/bigWeb/Debug/example.php LINE: 155 ET: 0.006137 MU: 1.59 mb MPU: 1.67 mb
                                ----------| 'foo bar' |----------
                                foo bar
                                .--------------------------------
                                

                                w konsoli FireBug-a

                                Wynik Dumpera w konsoli FireBug

                                oraz wyświetlony w przeglądarce:

                                TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 155 ET: 0.006137 MU: 1.59 mb MPU: 1.67 mb

                                ‚foo bar’
                                string(7) "foo bar"
                                

                                UWAGA! Od wartości level zależy prawidłowe wskazanie linii oraz pliku, w którym
                                wywołano dumpa. Domyślnie level = 1. Jeżeli metoda „notify” jest zagnieżdżona
                                w funkcji pomocniczej to wartość level powinna być inkrementowana (level = 2).
                                Jeśli dodatkowo metoda „notify” jest wywoływana za pośrednictwem funkcji
                                „call_user_func_array” to należy ustawić level = 3

                                $d = bigWeb\Debug\Dumper::factory(1);
                                 
                                function d1($param) {
                                    $d = bigWeb\Debug\Dumper::factory(2);
                                    // ...
                                    $d->notify($param);
                                }
                                 
                                function d2($param) {
                                    $_args = func_get_args();
                                    $d = bigWeb\Debug\Dumper::factory(3);
                                    // ...
                                    call_user_func_array(array($d, 'notify'), $_args);
                                }

                                UWAGA! Jeśli masz już zdefiniowaną funkcję „dump” w swojej aplikacji
                                to po dołączeniu kodu Dumpera otrzymasz wszystkomówiący wyjątek. Poinformuje
                                Cię on, że musisz zdefiniować sobie funkcję pomocniczą o innej nazwie.
                                Należy zakomentować kod wywołujący wyjątek lub też dołączyć kod Dumpera
                                w sposób umożliwiający przechwycenie wyjątka i utworzyć funkcję pomocniczą
                                np. o nazwie „d”.

                                try { include_once("Debug.php"); } catch (bigWeb\Debug\Exception $e) {
                                    function d() {
                                        $_args = func_get_args();
                                        static $d = null;
                                        if ( $d === null )
                                        {
                                            $d = bigWeb\Debug\Dumper::factory(3);
                                            $d->attach(new bigWeb\Debug\Dumper\VarDump());
                                 
                                        }
                                        call_user_func_array(array($d, "notify"), $_args);
                                    }
                                }

                                Napisana przeze mnie klasa nie zastąpi zaawansowanych i rozbudowanych narzędzi debugowania i profilowania aplikacji jednak jest prosta w użyciu nie związana stricte z żadnym frameworkiem, przez co łatwo ją zaadaptować zarówno do pracy z Zend Frameworkiem, Symfony, Kohaną czy jakimkolwiek innym – napisanym w PHP – skryptem. Biblioteka zwraca wyniki w postaci pokolorowanego kodu przy okazji pokazując czas wykonania oraz wielkość użytych zasobów. Zapomniany var_dump potrafi zmusić programistę do przeszukiwania plików projektu. W przypadku Dumpera nie ma takiego zagrożenia gdyż każdorazowo wskazuje on ścieżkę do pliku oraz nr linii, w której funkcja robiąca zrzut danych została wywołana.

                                Serdecznie zapraszam wszystkich do wypróbowania Dumpera oraz wszelkich uwag na temat wdrożeń i ewentualnych błędów.

                                Podziel się z innymi!

                                  Stan baterii w laptopie – linux

                                  Podziel się z innymi!

                                    Zaniepokoił mnie wskaźnik stanu naładowania baterii. Kilka godzin pracy na laptopie z podpiętym zasilaczem a 48% jak było tak było i nic nie chciało się ruszyć.

                                    Polecenie:

                                    cat /proc/acpi/battery/BAT1/info

                                    Dało wynik:

                                    present:                 yes
                                    design capacity:         39960 mWh
                                    last full capacity:      46320 mWh
                                    battery technology:      rechargeable
                                    design voltage:          11100 mV
                                    design capacity warning: 420 mWh
                                    design capacity low:     156 mWh
                                    cycle count:              0
                                    capacity granularity 1:  264 mWh
                                    capacity granularity 2:  3780 mWh
                                    model number:            PABAS024
                                    serial number:           3658Q
                                    battery type:            LION
                                    OEM info:                LG
                                    

                                    Czy to jest w ogóle możliwe aby projektowana pojemność baterii (design capacity) była mniejsza od ostatnio odnotowanej pełnej pojemności (last full capacity)? Mój laptop ma zaledwie dwa miesiące licząc od daty zakupu. Czy wartość pojemności projektowanej jest specjalnie zaniżana przez producentów, abym w przypadku sprawdzenia parametrów baterii – dajmy na to po roku użytkowania – czuł się bardziej komfortowo z powodu teoretycznie mniejszego zużycia baterii? A może te parametry są po prostu źle odczytywane?

                                    Tak czy inaczej nie zmienia to faktu, że pomimo podłączonego zasilacza moja bateria zwyczajnie się nie ładowała. Pod Windowsem dostałem przynajmniej czytelny komunikat, natomiast widżet w KDE zwyczajnie mnie okłamywał. Jak już jesteśmy przy KDE to przy okazji zakupu nowego laptopa zdecydowałem się powrócić do KDE po zawodzie jaki mi sprawiło Unity. Znajomy zapewniał mnie, że Plasma została już dopracowana – NIE ZOSTAŁA.

                                    Wracając jednak do baterii. Wykonałem kilkakrotnie polecenie:

                                    cat /proc/acpi/battery/BAT1/state

                                    Dało wynik:

                                    present:                 yes
                                    capacity state:          ok
                                    charging state:          charged
                                    present rate:            0 mW
                                    remaining capacity:      22230 mWh
                                    present voltage:         10924 mV
                                    

                                    Wartość remaining capacity nie ulegała zmianie. Postanowiłem rozładować całkowicie baterię, a później spróbować ją naładować od zera. Czekając na rozładowanie baterii znalazłem i trochę zmodyfikowałem skrypcik w bash-u pokazujący stan baterii – (może się komuś przyda).

                                    #!/bin/sh
                                     
                                    CUR=`cat /proc/acpi/battery/BAT*/state | grep remaining | awk {'print $3'}`
                                    STATE=`cat /proc/acpi/battery/BAT*/state | grep charging | awk {'print $3'}` 
                                    MAX=`cat /proc/acpi/battery/BAT*/info | grep full | awk {'print $4'}`
                                    DESIGN=`cat /proc/acpi/battery/BAT*/info | grep 'design capacity:' | awk {'print $3'}`
                                    PRC=0
                                     
                                    REAL_CAP=$(($MAX*100/$DESIGN))
                                    PRC=$(($CUR*100/$MAX))
                                    if [ $PRC -gt 100 ]; then
                                        PRC=100
                                    fi 
                                     
                                    echo 'State : '$STATE
                                    echo 'Design Battery Capacity : '$DESIGN' (100%)'
                                    echo 'Real Battery Capacity : '$MAX' ('$REAL_CAP'%)'
                                    echo 'Current Battery capacity : '$CUR' ('$PRC'%)'

                                    Po całkowitym rozładowaniu, przy wyłączonym komputerze podłączyłem zasilacz. Dioda zaczęła pokazywać, że bateria się ładuje. I rzeczywiście bateria naładowała mi się całkowicie. Nie jestem sprzętowcem i nie czytam fachowej prasy dlatego nie wiem czy to prawidłowe działanie – jakieś zabezpieczenie przed przedwczesnym zużyciem baterii czy też objaw uszkodzenia sprzętu? W innych laptopach, z którymi się spotkałem mogłem w każdej chwili doładować baterię.

                                    Podziel się z innymi!

                                      MySQL-owe widoki w Django

                                      Podziel się z innymi!

                                        Niniejszy artykuł traktuje o widokach w bazie danych (konkretnie w MySQL) i możliwości tworzenia modeli do tychże widoków z użyciem ORM-a framewokra Django. Zwracam na to uwagę aby nie pomylić – pomimo zbieżności nazw – widoków SQL-owych z widokami (views.py) Django.

                                        Czym są właściwie widoki w relacyjnej bazie danych i po co się je tworzy wyjaśnia – skądinąd świetnie napisany rozdział pod tytułem „Widoki” w przygotowanym przez Heliona kursie MySQL.

                                        W jednym z projektów, nad którymi pracuję stanąłem przed potrzebą stworzenia widoku w MySQL-u. Postanowiłem więc zdefiniować taki widok, stworzyć do niego model w Django i posługiwać się jak dowolnym innym modelem z tym, że tylko do odczytu. ORM w Django jest jednym z najbardziej rozbudowanych i zaawansowanych narzędzi tego typu mimo to ma swoje ograniczenia dlatego też nie byłem pewien czy uda mi się zrealizować to co sobie założyłem. Okazało się to możliwe aczkolwiek dopiero od wersji 1.3 frameworka Django.

                                        Poniżej prezentuję jak zdefiniować prawidłowo model dla widoku. Na potrzeby tego artykułu stworzyłem możliwie proste przykłady aby pokazać mechanizm. Skonstruowany przeze mnie widok był daleko bardziej rozbudowany.

                                        Załużmy, że mamy nowy projekt django a w nim aplikację do prezentacji newsów.

                                        news/models.py
                                        from django.db import models
                                        from django.contrib.auth.models import User
                                         
                                        class Article(models.Model):
                                            name = models.CharField(max_length=255)
                                            slug = models.SlugField(unique=True, max_length=100)
                                            text = models.TextField()
                                            added_by = models.ForeignKey(User)
                                            added_on = models.DateTimeField(auto_now_add=True)
                                            updated_on = models.DateTimeField(auto_now=True)
                                            is_active = models.BooleanField(default=True)
                                        news/admin.py
                                        from news.models Article
                                        from django.contrib import admin
                                         
                                        class ArticleAdmin(admin.ModelAdmin):
                                            list_filter = ['is_active']
                                            list_display = ('name', 'added_by', 'added_on', 'updated_on', 'is_active')
                                            prepopulated_fields = {'slug': ('name',)}
                                            search_fields = ('name', )
                                         
                                        admin.site.register(Article, ArticleAdmin)

                                        Z pewnych powodów potrzebujemy widoku, który prezentuje się następująco:

                                        DROP VIEW IF EXISTS `users_newsview`;
                                        CREATE VIEW `users_newsview` AS
                                            SELECT 0 AS id, n.id AS news_id, n.name AS news_name, n.added_by_id AS user_id, u.username
                                            FROM news_article AS n
                                            INNER JOIN auth_user AS u ON u.id = n.added_by_id;

                                        Tworzymy do niego model, który będzie tylko do odczytu.

                                        users/models.py
                                        from django.db import models
                                        from news.models import Article
                                        from django.contrib.auth.models import User
                                         
                                        class NewsView(models.Model):
                                         
                                            class Meta:
                                                managed = False
                                         
                                            news = models.ForeignKey(Article, null=True, blank=True, on_delete=models.DO_NOTHING)
                                            news_name = models.CharField(max_length=255, null=True, blank=True)
                                            user = models.ForeignKey(User, null=True, blank=True, on_delete=models.DO_NOTHING)
                                            username = models.CharField(max_length=255, null=True, blank=True)

                                        O tym, że obiekty modelu „NewsViews” są niemodyfikowalne decyduje Zdefniowany w podklasie „Meta” parametr „managed = False”. Innym bardzo istotnym elementem jest dodanie do definicji kluczy obcych „on_delete=models.DO_NOTHING”. Django w przypadku kluczy obcych domyślnie emuluje zachowanie „on delete cascade”. Możliwość zmiany tego zachowania Django wspiera dopiero od wersji 1.3. Jeśli nie zmienimy sposobu w jaki Django ma postępować z elementami powiązanymi w trakcie usuwania danego obiektu to przy próby usunięcia newsa, albo usera zostanie zgłoszony błąd gdyż próba usunięcia także rekordu widoku z oczywistych względów się niepowiedzie.

                                        W starszej wersji frameworka Django też można stworzyć model do widoku, ale trzeba w nim zrezygnować z tworzenia relacji do innych obiektów i zamiast pól „news” i „user” zdefiniować pola typu integer „news_id” oraz „user_id”. Jak się domyślacie rozwiązanie to jest o wiele mniej wygodne.

                                        Podziel się z innymi!