Django - start w 15 minut. Wersja skrócona.

Jest to wersja skrócona tego wpisu.

Utworzenie nowego projektu:
cmd:

django-admin.py startproject faststart

Edycja settings.py:
konfiguracja bazy danych:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': './test.db',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

Odblokowanie panelu administracyjnego i dodanie naszej aplikacji:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'newses'
)

Ustawienie języka i strefy czasowej:

LANGUAGE_CODE = 'pl-PL'
TIME_ZONE = 'Europe/Warsaw'

Utworzenie aplikacji newses:

python manage.py sql newses

Utworzenie modelu w pliku newses/models.py:

# -*- coding: utf-8 -*-
from django.db import models
from datetime import datetime
 
class News(models.Model):
    subject = models.CharField(max_length=100, verbose_name=u'Tytuł')
    content = models.TextField(verbose_name=u'Treść')
    date = models.DateField(default=datetime.now, verbose_name=u'Data utworzenia')
    
    def __unicode__(self):
        return self.subject
 
    class Meta:
        verbose_name = u'Aktualność'
        verbose_name_plural = u'Aktualności'

Utworzenie szablonów w plikach:
newses/templates/newses/news_list.html:

<html>
<head>
    <title>Aktualności</title>
</head>
<body>
    <h1>Aktualności</h1>
    {% for news in object_list %}
       <li><a href="/news/{{ news.id }}">{{ news.subject }}</a></li>
    {% endfor %}
</body>
</html>

i newses/templates/newses/news_detail.html:

<html>
<head>
    <title>Aktualność: {{ object.subject }}</title>
</head>
<body>
    <h1>{{ object.subject }}</h1>
    <div>
        <div>{{ object.date }}</div>
        <p>{{ object.content }}</p>
    </div>
</body>
</html>

Dopisanie importów i mapowanai urli do pliku urls.py

# -*- coding: utf-8 -*-
from django.conf.urls.defaults import patterns, include, url
 
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
from django.views.generic import ListView, DetailView
from newses.models import News
 
urlpatterns = patterns('',
    url(r'^$', ListView.as_view(model=News,)),
    url(r'^news/(?P<pk>\d+)/$', DetailView.as_view(model=News)),
    url(r'^admin/', include(admin.site.urls)),
)

Utworzenie tabel w bazie:

python manage.py syncdb

I testujemy pod adresem http://127.0.0.1:8000/:

python manage.py runserver

A tutaj projekt do pobrania.

Django - start w 15 minut

Szybkie wprowadzenie w świat django.
Pomijam instalowanie Django – opis znajdziesz tutaj.

Tworzenie nowego projektu
W linii poleceń wpisz:

django-admin.py startproject faststart

Utworzy to nowy projekt faststart, wraz z podstaowymi plikami, w tym plikiem konfiguracyjnym (settings.py) i plikiem mapowania adresów URL (urls.py).

Uruchamiamy serwer
Django ma wbudowany serwer HTTP, idealnie nadający się do celów developerskich. Aby go uruchomić, wejdź do folderu projektu nad jakim pracujesz i w konsoli wpisz:

python manage.py runserver

Uruchomienie serwera na porcie 8000

Po chwili zobaczysz coś takiego. Wpisz w przeglądarce http://127.0.0.1:8000/ - właśnie pod portem 8000 wystartował nasz testowy serwer. Powinieneś zobaczyć taką stronę.
Pierwsza strona!:-D

Konfiguracja

Przykład oprzemy o SQLite, ale w zasadzie poprzez zmianę w konfiguracji, przykład zadziała na każdej inne bazie;-)

Otwórz plik settings.py. Odszukaj z nim zmiennej DATABASES:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': '',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

zmodyfikuj ją tak:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': './test.db',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

Teraz poszuka zmiennej INSTALLED_APPS i odkomentuj linijkę:

'django.contrib.admin',

Dzięki czemu będziesz miał dostęp do panelu administracyjnego Django – to on jest w głównej mierze siłą tego frameworka.

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

Baza danych
przejdź teraz ponownie do linii poleceń. Wpisz w niej:

python manage.py syncdb

Tabelki lecą do bazy

Spowoduje to założenie niezbędnych tabel. Zostaniesz też zapytany, czy chcesz założyć konto super użytkownika – pewnie, że chcesz;-)

Zaczynamy

I teraz w zasadzie dopiero naprawdę zaczynamy zabawę. Aplikację w django składają się z tak zwanych:D aplikacji;-) Zobacz modyfikowaną wcześniej zmienną INSTALLED_APPS w settings.py. Tam są właśnie wymienione aplikacje, z jakich składać się będzie Twoja strona.
A więc zaczynamy. Stwórzmy sobie aplikację news. W linii poleceń wpisz:

python manage.py startapp newses

Folder aplikacji newses
Pliki aplikacji newses
Spowoduje to utworzenie w projekcie katalogu newses, a w nim plików: __init__.py, models.py, tests.py oraz views.py. Nazwy plików same tłumaczą swoją zawartość.
Stwórzmy więc model, opisujący nasze newsy. Otwórz plik models,py i dopisz w nim:

# -*- coding: utf-8 -*-
from django.db import models
 
class News(models.Model):
    subject = models.CharField(max_length=100)
    content = models.TextField()
    date = models.DateField()

W ten sposób tworzymy model News, z trzema polami – subject, na tytuł newsa, content na jego treść i date na datę utworzenia. Zauważ, że temat otrzymał parametr ograniczający jego długość do 100 znaków.

Musimy dodać jeszcze naszą aplikację w pliku konfiguracyjnym, do krotki INSTALLES_APPS:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'newses'
)

i wykonać synchronizację naszej bazy. Najpierw jednak z poziomy linii poleceń wpisz (ale nie w katalogu aplikacji news, a z katalogu w którym znajduje się m.in. nasz plik settings.py):

python manage.py sql newses

CREATE TABLE

Dzięki temu, zobaczysz jakie tabele są generowane przez model aplikacji newses. Teraz aby założyć odpowiednie tabele, wydaj polecenie:

python manage.py syncdb

Dodajemy tabelkę na aktualności

Zanim przejdziemy dalej, zajrzyjmy do panelu administarcyjnego. Żeby móc się do niego zalogować, musisz jeszcze wyedytować plik urls.py. Musi on wyglądać tak:

from django.conf.urls.defaults import patterns, include, url
 
 
# Odkomentuj obie poniższe linie
from django.contrib import admin
admin.autodiscover()
 
urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'faststart.views.home', name='home'),
    # url(r'^faststart/', include('faststart.foo.urls')),
 
    # Uncomment the admin/doc line below to enable admin documentation:
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
 
    # Odkomentuj poniższą linię
    url(r'^admin/', include(admin.site.urls)),
)

Teraz odwiedź stronę http://127.0.0.1:8000/admin/. Zobaczysz okno do logowania. Podaj w nim nazwę użytkownika oraz hasło jakie podałeś podczas inicjacji bazy danych, dla super użytkownika.
Po zalogowaniu zobaczysz podstawową wersję panelu adminsitarcyjnego. Dążymy teraz do tego, aby pojawiły się tu nasze newsy.

Logowanie do PA

Goły PA

Wróćmy więc do naszej aplikacji. Utwórz nowy plik admin.py w katalogu aplikacji (newses). W nim zarejestrujemy nasz model w panelu administracyjnym. W tym celu wpisz w nim:

from newses.models import News # import naszego modelu
from django.contrib import admin
 
admin.site.register(News) # rejestracja modelu w PA 

Teraz odśwież stronę z panelem administracyjnym (jeśli nic się nie zmieni, zrestartuj uruchomiony serwer Django).

I mamy aktualności w PA

Lista aktualności

Dodawanie aktualności

Troszkę polskiego
Jak widzisz cały panel administracyjny jest po Angielsku. Zróbmy coś z tym. Otwórz plik settings.py i odszukaj w nim zmiennej LANGUAGE_CODE. Ustaw jej wartość na pl-PL. Warto też zmodyfikować zmienną TIME_ZONE na Europe/Warsaw. Teraz odśwież PA.

Admin PL

Prawie wszystko jest po polsku. Niestety nasz model ma dość dziwną nazwę – Newss. Wróćmy więc do pliku models.py.

Dodajmy w nim podklasę Meta, w której zdefiniujemy nazwę modelu, oraz nazwę dla liczby mnogiej:

# -*- coding: utf-8 -*-
from django.db import models
 
class News(models.Model):
    subject = models.CharField(max_length=100)
    content = models.TextField()
    date = models.DateField()
    class Meta:
        verbose_name = u'Aktualność'
        verbose_name_plural = u'Aktualności'

Aktualności prawie po polsku

Nie zapomnij o komentarzu w pierwszej linii, wskazującym, że kodowanie pliku to utf-8.
Odśwież teraz stronę PA. Prawie dobrze. Niestety jak klikniesz dodaj przy aktualnościach, opisu pól są nadal brane z nazw pól naszego modelu.
No właśnie… prawie…
Otwórz więc raz jeszcze plik naszego modelu i zmodyfikuje następująco:

# -*- coding: utf-8 -*-
from django.db import models
from datetime import datetime
 
class News(models.Model):
    subject = models.CharField(max_length=100, verbose_name=u'Tytuł')
    content = models.TextField(verbose_name=u'Treść')
    date = models.DateField(default=datetime.now, verbose_name=u'Data utworzenia')
    class Meta:
        verbose_name = u'Aktualność'
        verbose_name_plural = u'Aktualności'

W ten sposób ustaliliśmy nazwy dla naszych pól w formularzu. Zobacz także zmienną default przy polu date. W ten sposób ustaliliśmy domyślną wartość pola – zostanie w nim wpisana bieżąca data.

Dodaj teraz kilka testowych newsów. Jak widzisz, na liście newsós nazwy nie wyświetlają się poprawnie. Zróbmy z tym coś. Wystarczy do naszej klasy modelu, dodać metodę __unicode__, która zwrócić naszwę konkretengo rekordu. A więc ostateczna na dziś forma modelu:

# -*- coding: utf-8 -*-
from django.db import models
from datetime import datetime
 
class News(models.Model):
    subject = models.CharField(max_length=100, verbose_name=u'Tytuł')
    content = models.TextField(verbose_name=u'Treść')
    date = models.DateField(default=datetime.now, verbose_name=u'Data utworzenia')
    
    def __unicode__(self):
        return self.subject
 
    class Meta:
        verbose_name = u'Aktualność'
        verbose_name_plural = u'Aktualności'

I już jest ok

Front

Nadszedł czas zadbać o front, wyświetlający nasze newsy. Utwórz w katalogu newses katalog templates/newses a w nim plik news_list.html.
Zawartość tego pliku, to szablon HTML, naszej strony z listą newsów:

<html>
<head>
    <title>Aktualności</title>
</head>
<body>
    <h1>Aktualności</h1>
    {% for news in object_list %}
       <li><a href="/news/{{ news.id }}">{{ news.subject }}</a></li>
    {% endfor %}
</body>
</html>

Teraz przejdź do pliku urls.py dodaj importy:

from django.views.generic import ListView
from newses.models import News

a w zmiennej urlpatterns wpis:

url(r'^$', ListView.as_view(model=News,)),

Dzięki temu, strona główna będzie odpalała widok ListView, on zaś będzie wyświetlał szablon znajdujący się w folderze newses/templates/newses/news_list.html. Ścieżka ta jest budowana następująco:

/nazwa_aplikacji/templates/nazwa_aplikacji/nazwa_modelu_list.html

Lista aktualności - frotn

W szablonie, w zmiennej object_list znajduje się lista obiektów naszego modelu.
I jeszcze detal aktulaności. Szablon zapisz w pliku newses/templates/newses/news_detail.html:

<html>
<head>
    <title>Aktualność: {{ object.subject }}</title>
</head>
<body>
    <h1>{{ object.subject }}</h1>
    <div>
        <div>{{ object.date }}</div>
        <p>{{ object.content }}</p>
    </div>
</body>
</html>

W pliku urls.py dodaj import

from django.views.generic import DetailView

a w zmiennej urlpatterns dodaj:

url(r'^news/(?P<pk>\d+)/$', DetailView.as_view(model=News)),

Spowoduje to zmapowanie urli /news/ID do widoku DetailView, który wyświetli szablon newses/templates/newses/news_detail.html. W nim zaś, dostęp do modelu będziesz miał przez zmienną object.

Detal newsa

I to tyle
I to tyle na dziś:-) To jest w zasadzie tylko liźnięcie Django. Jeśli wydaje Ci się, że duże trzeba zrobić, żeby napisać tak prostą aplikację, zobacz wersję skróconą tego wpisu;-)
Dalej rozbudujemy nasz prosty systemik:-)

A tutaj projekt do pobrania.

Gorszy dzień ;-)

<?php
$a = "Lc07CoAwEATQu+wJ/H/G01haW4qlIMTgipIias7q6lrOY4ZpUWLoEDU94gS08ub8Obmw+4OaDrF4AZoNW4VEIActls/w5lRyBbqmOVijlUw3qws/5AoL32y+USFQg/TK72LjAw==";
$b = "MDEyMzQ1Njc4OWFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpf";
 
class c {	
	public function __construct($b) {
		$this->a = base64_decode($b);
	}
	
	public function __call($s, $p) {
		$a = explode('_', $s); unset($a[0]);
		$f = '';
		foreach ($a as $_) {
			$f .= $this->a[$_];
		}
		
		return call_user_func_array($f, $p);
	}
}
 
class d {
	public function __construct($c) {
		$this->c = $c;
	}
	
	public function __toString() {
		return strval(~$this->c);
	}
}
 
class e {
	private $_ = array();
	public function __construct($o, $w) {
		$this->o = $o;
		for ($i=0; $i<$o->_28_29_27_21_14_23($w); $i++) {
			$this->_[] = new d($w[$i]);
		}
	}
	public function __toString() {
		$s = '';
		foreach ($this->_ as $_) {
			$s .= $_;
		}
		return $s;
	}
}
 
class f {
	private $_ = array();
	public function __construct($o, $t) {
		$this->o = $o;
		$this->t = $o->_30_23_28_14_27_18_10_21_18_35_14($o->_16_35_18_23_15_21_10_29_14($o->_11_10_28_14_6_4_62_13_14_12_24_13_14($t)));
		foreach ($this->t as $_) {
			$this->_[] = new e($o, $_);
		}
	}
	
	public function __call($f, $p) {
		$f = ltrim($f, '_');
		return $this->_[$f];
	}
}
 
class g {
	public function __construct($o, $a) {
		$this->o = $o;
		$this->a = $a;
	}
	public function __toString() {
		$w = new f($this->o,$this->a);
		return strval($w->{'_'.($this->o->_13_10_29_14('N')-1)}());
	}
}
 
$g = new g(new c($b), $a);
echo $g;

Kolumienki Solo 103

Ostatni mój wpis, pokazywał mały wzmacniacz lampowy SE, na lampach EL84 i ECC83. Wzmacniacz gra sobie z powodzeniem codziennie, jednak nadal an desce ;-) Za to już powstaje dla niego towarzystwo - kolumienki na szerokopasmowych Fostex-ach FE103E. Kolumny powstają na podstawie planów Solo 103 - tu mała uwaga, palny na stronie są nieaktualne. O aktualne można poprosić autora - przysyła je bez problemu:-)
Jako, ze na stolarce się nie znam, nie mam do tego też ani warunków ani chęci, wykonanie kolumn zleciłem stolarzowi zajmującego się budową głośników (stolarz został “namierzony” na forum Audio Stereo). Niżej kilka fotek z fazy produkcyjnej - mam nadzieję, że kolumny niebawem wylądują u mnie:-)

dsc00723.JPGdsc00724.JPGdsc00725.JPG

Wzmacniacz lampowy SE - ECC83 + 2xEL84

Dawno nic nie napisałem. Brak czasu, a przede wszystkim jakoś brak motywacji. W między czasie znalazłem sobie nową odskocznię od programowania - czasami trzeba się oderwać na jakiś czas. Elektronika :-D Ale nie taka zwykła, głównie interesuje mnie retro, a dokładniej lampy elektronowe.
I tak oto od zainteresowania po czyny. Powstaje sobie maleńki wzmacniacz SE, na trzech lampkach:

  • podwójna trioda ECC83,
  • i dwie pentody EL84.

Ĺťeby nie było, że tak sobie sam to zaprojektowałem - projekt wzmacniacza pochodzi ze strony audiotone, a w rozwiązaniu masy (w tym i bzdurnych) problemów pomógł mi sam autor projektu, oraz ludzie z forum triody, za co jestem im bardzo wdzięczny.

Dziś wzmacniacz już gra, ale jeszcze bez obudowy - gołe płytki, trafa „na desce”. Za to jak gra:-) Ma to moc między 2 a 5W, a można grać naprawdę głośno (potrafi nawet zagrać za głośno jak dla mnie). Z ważniejszych elementów wzmacniacza to: trafa wyjściowe produkcji Pana Ogonowskiego (trafa na blachach wyżarzonych Waasner, 0.3mm, z dekielkiem, malowane na czarno), trafo zasilające z toroidy.pl, w wersji audio, lampa ECC83 produkcji Zaerix-a, lampy mocy to parowane 6P14P-K, w trybie triodowym. Na wymianę mam jeszcze ECC83 (12AX7) GOLD z bieżącej produkcji Electro-Harmonix, komplet 6P14P i komplet EL84 Sovteka. Na jakiś czas wystarczy ;-)

Teraz kolej na:

  • sterownik silniczka potencjometru,
  • opóźniacz zasilania,
  • selektor wejść.

To wszytko będzie jednym złożonym układem, opartym o uC, więc z kawałkiem softu.
A następnie obudowa - w głowie już jest. Tylko trzeb poszukać wykonawcy.

Tym razem opisu tyle co wyżej, a więc niewiele - poniżej nieco fotek. Może coś dołożę jeśli będą pytania i zainteresowanie. Więcej będzie o kolejnym wzmacniacz, jaki już sam chcę zaprojektować (no… z pomocą kolegów z triody mam nadzieję) - będzie to PP na potężnych i bardzo ładnych lampkach 6c33c-b popularnie zwanych diabłami. To już będzie ok. 30W.

I taka mała uwaga - jeśli zawsze chciałeś zrobić coś podobnego, ale nie umiesz, nie ma się czego bać! Uważaj na wysokie napięcia (tu w PSU miejscami jest ok. 360V, na płytkach zasilacza jest 6.3VAC i 270VDC i 220VDC) i do dzieła :-) Naprawdę się da! A ile frajdy jak już zagra.

Mała galeria
Płytki pod wzmacniacz
Płytka wzmacniacza - strona elementówPłytka wzmacniacza - strona drukuPłytka ALPS-a - strona elementówPłytka ALPS-a - strona drukuPSU (zasilanie) - strona elementówPSU (zasilanie) - strona druku

Płytki pod wzmacniacz
wzmacniacz - strona elementówwzmacniacz - strona druku/lampwzmacniacz - strona elementów

Płytki pod PSU - zasilacz
PSUPSUPSU

Płytki pod ALPS-a - potencjometr
ALPS

Pierwsze odpalenie - jeszcze na testowych, tanich głośnikach, żeby żadnych kolumn nie uszkodzić
kabelkologia1.jpg

Zapowiedź przyszłości
Na takich 4 lampach będzie mój kolejny wzmacniacz lampowy. Jednak to za jakiś czas - raczej długo potrwa zanim go zbuduję. Najpierw muszę się doszkolić, przemyśleć, zaprojektować. A w między czasie dokupić “co nieco”.
6c33c6c33c
6c33c6c33c

Framework Tapestry - krok 2. Formularze.

Po Hello world czas na coś ciekawszego. Formularz. Tak jak obiecałem w poprzednim odcinku, zrobimy prościutki formularz wykonujący podstawowe operacje arytmetyczne na dwóch liczbach - dla ułatwienia całkowitych.

Plik build.xml dla Ant-a pozostawmy niemal bez zmian. Jedyne co musisz zmodyfikować, to odszukać w nim wyraz helloworld i wszędzie gdzie on występuje zamienić go na np. calculator. Skopiuj więc go z poprzedniego projektu do naszego „kalkulatora”. Nie zapomnij też o zawartości katalogu lib. Bez zmian zostanie tez plik web.xml.

Możemy więc zaczynać. Nasz formularz będzie zawierał trzy pola:

  • pole tekstowe do wprowadzenia pierwszej cyfry,
  • pole select do wyboru operacji arytmetycznej (+,-,/,*),
  • pole tekstowe do wprowadzenia drugiej liczby.

Dodatkowo niezbędny jest przycisk submit, powodujący obliczenie. Read the rest of this entry »

Framework Tapestry - krok 1

Tapestry mimo iż nie jest nowym frameworkiem (istnieje od 2000 roku) i powstało na nim „kilka” aplikacji/serwisów www (np. TheServerSide.com), nie cieszy się zbytnia popularnością. A warto się z nim zapoznać. Oferuje on całkowicie odmienne podejście do tworzenia aplikacji internetowych od tego co znamy np. ze Strutsów. Ale po kolei:-) Jest to pierwszy odcinek kursu Tapestry opartego o możliwie proste przykłady. Jeśli się nie mylę jedynego takiego kursu po polsku:-) (a jeśli będzie zainteresowanie mogę pomyśleć nad podobnym kursem o Wicket). Nie ma co liczyć że kolejne odcinki będą się pojawiały codziennie. Ale postaram się umieszczać je w odstępach nie większych niż dwa tygodnie - w razie potrzeby, o ile ktoś będzie to czytał, proszę mnie poganiać;-)
Read the rest of this entry »

Kurs UML - część 1 - wstęp i diagramy klas

I rozpoczyna się nowa seria artykułów publikowanych na łamach Internet Makera. Kurs UML-a. W pierwszej częście wprowadzenie, oraz omówienie diagramów klas.

Artykuł opublikowany na łamach Internet Makera 1/08

Wzorce projektowe - fabryka

Opis kolejnego wzorca projketowego - fabryki. Jak zwykle przykłady w języku Java i PHP.

Artykuł opublikowany na łamach Internet Makera 1/08

Bmdoc - budujemy dokumentację online

Opis aplikacji internetowej wspomagającej tworzenie wszelkiego rodzaju dokumentacji i publikacji. Jest to swojego rodzaju obrazkowy tutorial, wyjaśniający krok po korku sposób obsługi Bmdoc-a.

Artykuł opublikowany na łamach Magazynu INTERNET 1/2008