8.1. Czat (cz. 1)

Zastosowanie Pythona i frameworka Django do stworzenia aplikacji internetowej Czat; prostego czata, w którym zarejestrowani użytkownicy będą mogli wymieniać się krótkimi wiadomościami.

Uwaga

Wymagane oprogramowanie:

  • Python v. 3.x
  • Django v. 1.11.2
  • Interpreter bazy SQLite3

8.1.1. Środowisko

W katalogu domowym tworzymy wirtualne środowisko Pythona:

Terminal nr
~$ virtualenv -p python3 pve3
~$ source pve3/bin/activate
(pve3) ~$ pip install Django==1.11.2

Ostrzeżenie

Polecenie source pve3/bin/activate aktywuje wirtualne środowisko Pythona. Zawsze wydajemy je przed rozpoczęciem pracy nad projektem. Innymi słowy w terminalu ścieżka katalogu musi być poprzedzona prefiksem wirtualnego środowiska: (pve3).

8.1.2. Projekt i aplikacja

Utworzymy nowy projekt Django. Wydajemy polecenia:

Terminal nr
(pve3) ~/$ django-admin.py startproject czat1
(pve3) ~$ cd czat1
(pve3) ~$ python manage.py migrate
  • startproject – tworzy katalog czat1 z podkatalogiem ustawień projektu o takiej samej nazwie (czat1),
  • migrate – tworzy inicjalną bazę danych z tabelami wykorzystywanymi przez Django.

Struktura plików projektu – w terminalu wydajemy jedno z poleceń:

(.pve) ~/czat1$ tree -L 2
[lub]
(.pve) ~/czat1$ ls -R
../../_images/django_projekt.jpg

Zewnętrzny katalog czat1 to tylko pojemnik na projekt, jego nazwę można zmieniać. Zawiera on:

  • manage.py – skrypt Pythona do zarządzania projektem;
  • db.sqlite3 – bazę danych w domyślnym formacie SQLite3.

Katlog projektu czat1/czat1 zawiera:

  • settings.py – konfiguracja projektu;
  • urls.py – lista obsługiwanych adresów URL;
  • wsgi.py – plik konfiguracyjny wykorzystywany przez serwery WWW.

Plik __init__.py obecny w danym katalogu wskazuje, że dany katalog jest modułem Pythona.

8.1.3. Serwer deweloperski

Serwer uruchamiamy poleceniem w terminalu:

(pve3) ~/czat1$ python manage.py runserver

Łączymy się z serwerem wpisując w przeglądarce adres: 127.0.0.1:8000. W terminalu możemy obserwować żądania obsługiwane przez serwer. Większość zmian w kodzie nie wymaga restartowania serwera. Serwer zatrzymujemy naciskając w terminalu skrót CTRL+C.

../../_images/django_it_worked.jpg

8.1.4. Aplikacja

W ramach jednego projektu (serwisu internetowego) może działać wiele aplikacji. Utworzymy teraz aplikację czat i zbadamy jej strukturę plików:

(.pve) ~/czat1$ python manage.py startapp czat
(.pve) ~/czat1$ tree czat
lub:
(.pve) ~/czat1$ ls -R czat
../../_images/django_aplikacja.jpg

Katalog aplikacji czat1/czat zawiera:

  • apps.py – ustawienia aplikacji;
  • admin.py – konfigurację panelu administracyjnego;
  • models.py – plik definiujący modele danych przechowywanych w bazie;
  • views.py – plik zawierający funkcje lub klasy definiujące tzw. widoki (ang. views), obsługujące żądania klienta przychodzące do serwera.

8.1.5. Ustawienia projektu

Dostosujemy ustawienia projektu: zarejestrujemy aplikację w projekcie, ustawimy polską wersję językową oraz zlokalizujemy datę i czas. Edytujemy plik czat1/settings.py:

Plik settings.pyKod nr
# czat1/settings.py

INSTALLED_APPS = [
    'czat.apps.CzatConfig', # rejestrujemy aplikacje czat
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

LANGUAGE_CODE = 'pl'  # ustawienie jezyka

TIME_ZONE = 'Europe/Warsaw'  # ustawienie strefy czasowej

Uruchom ponownie serwer deweloperski i sprawdź w przeglądarce, jak wygląda strona powitalna.

../../_images/django_zadzialalo.jpg

8.1.6. Model danych

Budowanie aplikacji w Django nawiązuje do wzorca projektowego MVC, czyli Model-Widok-Kontroler. Więcej informacji na ten temat umieściliśmy w osobnym materiale MVC.

Zaczynamy więc od zdefiniowania modelu (zob. model), czyli klasy opisującej tabelę zawierającą wiadomości. Atrybuty klasy odpowiadają polom tabeli. Instancje tej klasy będą reprezentować wiadomości utworzone przez użytkowników, czyli rekordy tabeli. Każda wiadomość będzie zwierała treść, datę dodania oraz wskazanie autora (użytkownika).

W pliku czat/models.py wpisujemy:

Plik models.pyKod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# -*- coding: utf-8 -*-
# czat/models.py

from django.db import models
from django.contrib.auth.models import User


class Wiadomosc(models.Model):

    """Klasa reprezentująca wiadomość w systemie"""
    tekst = models.CharField('treść wiadomości', max_length=250)
    data_pub = models.DateTimeField('data publikacji', auto_now_add=True)
    autor = models.ForeignKey(User)

Opisując klasę Wiadomosc podajemy nazwy poszczególnych właściwości (pól) oraz typy przechowywanych w nich danych.

Informacja

Typy pól:

  • CharField – pole znakowe, przechowuje niezbyt długie napisy, np. nazwy;
  • Date(Time)Field – pole daty (i czasu);
  • ForeignKey – pole klucza obcego, czyli relacji; wymaga nazwy powiązanego modelu jako pierwszego argumentu.

Właściwości pól:

  • verbose_name lub napis podany jako pierwszy argument – przyjazna nazwa pola;
  • max_length – maksymalna długość pola znakowego;
  • help_text – tekst podpowiedzi;
  • auto_now_add=True – data (i czas) wstawione zostaną automatycznie.

Utworzenie migracji – po dodaniu lub zmianie modelu należy zaktualizować bazę danych, tworząc tzw. migrację, czyli zapis zmian:

Terminal nr
(.pve) ~/czat1$ python manage.py makemigrations czat
(.pve) ~/czat1$ python manage.py migrate
../../_images/django_migrations.jpg

Informacja

Domyślnie Django korzysta z bazy SQLite zapisanej w pliku db.sqlite3. Warto zobaczyć, jak wygląda. W terminalu wydajemy polecenie python manage.py dbshell, które otworzy bazę w interpreterze sqlite3. Następnie: * .tables - pokaże listę tabel; * .schema czat_wiadomosc - pokaże instrukcje SQL-a użyte do utworzenia podanej tabeli * .quit - wyjście z interpretera.

8.1.7. Panel administracyjny

Panel administratora pozwala dodawać użytkowników i wprowadzać dane. W pliku czat/admin.py umieszczamy kod:

Plik admin.pyKod nr
1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
# czatpro/czat/admin.py

from django.contrib import admin
from czat import models  # importujemy nasz model

# rejestrujemy model Wiadomosc w panelu administracyjnym
admin.site.register(models.Wiadomosc)

Po zaimportowaniu modelu rejestrujemy go w panelu: admin.site.register(models.Wiadomosc).

Informacja

Warto zapamiętać, że każdy model, funkcję, formularz czy widok, których chcemy użyć, musimy najpierw zaimportować za pomocą klauzuli typu from <skąd> import <co>.

Konto administratora tworzymy wydając w terminalu polecenie:

Terminal nr
(.pve) ~/czat1$ python manage.py createsuperuser

– na pytanie o nazwę, email i hasło administratora, podajemy: “admin”, “”, “zaq1@WSX”.

8.1.7.1. Ćwiczenie

  1. Uruchom/zrestartuj serwer, w przeglądarce wpisz adres 127.0.0.1:8000/admin/ i zaloguj się na konto administratora.
../../_images/django_admin.jpg
  1. Dodaj użytkowników “adam” i “ewa” z hasłami “zaq1@WSX”.

    Na stronie, która wyświetla się po utworzeniu konta, zaznacz opcję “W zespole”. W sekcji “Dostępne uprawnienia” zaznacz prawa dodawania (add), zmieniania (change) oraz usuwania (del) wiadomości (wpisy typu: “czat | wiadomosc | Can add wiadomosc”) i przypisz je użytkownikowi naciskając strzałkę w prawo.

../../_images/django_admin_uprawnienia.jpg
  1. Z konta “adam” dodaj dwie przykładowe wiadomości, a z konta “ewa” – jedną.
../../_images/django_admin_wiadomosci1.jpg

8.1.8. Uzupełnienie modelu

W formularzu dodawania wiadomości widać, że etykiety opisujące nasz model nie są spolszczone. Uzupełniamy więc plik czat/models.py:

Plik models.pyKod nr
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Wiadomosc(models.Model):

    """Klasa reprezentująca wiadomość w systemie"""
    tekst = models.CharField('treść wiadomości', max_length=250)
    data_pub = models.DateTimeField('data publikacji', auto_now_add=True)
    autor = models.ForeignKey(User)

    class Meta:  # ustawienia dodatkowe
        verbose_name = u'wiadomość'  # nazwa obiektu w języku polskim
        verbose_name_plural = u'wiadomości'  # nazwa obiektów w l.m.
        ordering = ['data_pub']  # domyślne porządkowanie danych

    def __str__(self):
        return self.tekst  # "autoprezentacja"

Podklasa Meta pozwala zdefiniować formy liczby pojedynczej i mnogiej oraz domyślny sposób sortowania wiadomości (ordering = ['data_pub']). Zadaniem funkcji __str__() jest “autoprezentacja” klasy, czyli w naszym wypadku wyświetlenie treści wiadomości.

Odśwież panel administracyjny (np. klawiszem F5).

../../_images/django_admin_wiadomosci2.jpg

8.1.9. Strona główna

Aby utworzyć stronę główną, zakodujemy pierwszy widok (zob. więcej »»»), czyli funkcję o zwyczajowej nazwie index(). W pliku views.py umieszczamy:

Plik views.pyKod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# -*- coding: utf-8 -*-
# czat/views.py

from django.shortcuts import render
from django.http import HttpResponse


def index(request):
    """Strona główna aplikacji."""
    return HttpResponse("Witaj w aplikacji Czat!")

Najprostszy widok zwraca do klienta (przeglądarki) jakiś tekst: return HttpResponse("Witaj w aplikacji Czat!").

Adresy URL, które ma obsługiwać nasza aplikacja, definiujemy w pliku czat/urls.py. Tworzymy nowy plik i uzupełniamy go kodem:

Plik urls.pyKod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# -*- coding: utf-8 -*-
# czat/urls.py

from django.conf.urls import url
from . import views  # import widoków aplikacji

app_name = 'czat'  # przestrzeń nazw aplikacji
urlpatterns = [
    url(r'^$', views.index, name='index'),
]
  • app_name = 'czat' – określamy przestrzeń nazw, w której dostępne będą mapowania między adresami url a widokami naszej aplikacji,
  • url() – funkcja, która wiąże zdefiniowany adres URL z widokiem,
  • r'^$' – wyrażenie regularne opisujące adres URL, symbol ^ to początek, $ – koniec łańcucha. Zapis r'^$' to adres główny serwera;
  • views.index – przykładowy widok, czyli funkcja zdefiniowana w pliku czat/views.py;
  • name='index' – nazwa, która pozwoli na generowanie adresów url dla linków w kodzie HTML.

Konfigurację adresów URL naszej aplikacji musimy włączyć do konfiguracji adresów URL projektu. W pliku czat1/urls.py dopisujemy:

Plik urls.pyKod nr
16
17
18
19
20
21
22
23
from django.conf.urls import url, include
from django.contrib import admin


urlpatterns = [
    url(r'^', include('czat.urls')),
    url(r'^admin/', admin.site.urls),
]
  • include() – funkcja pozwala na import adresów URL wskazanej aplikacji,
  • 'czat.urls' – plik konfiguracyjny aplikacji.

Przetestuj stronę główną wywołując adres 127.0.0.1:8000.

../../_images/django_index1.jpg

8.1.10. Widoki i szablony

Typową odpowiedzią na wywołanie jakiegoś adresu URL są strony zapisane w języku HTML. Szablony takich stron umieszczamy w podkatalogu aplikacja/templates/aplikacja. Tworzymy więc katalog:

Terminal nr
(pve3) ~/czat1$ mkdir -p czat/templates/czat

Następnie tworzymy szablon templates/czat/index.html, który zawiera:

Plik index.html. Kod nr
1
2
3
4
5
6
7
<!-- templates/czat/index.html -->
<html>
  <head></head>
  <body>
    <h1>Witaj w aplikacji Czat!</h1>
  </body>
</html>

W pliku views.py zmieniamy instrukcję odpowiedzi:

Plik views.pyKod nr
 4
 5
 6
 7
 8
 9
10
11
from django.shortcuts import render
# from django.http import HttpResponse


def index(request):
    """Strona główna aplikacji."""
    # return HttpResponse("Witaj w aplikacji Czat!")
    return render(request, 'czat/index.html')

Funkcja render() jako pierwszy parametr pobiera obiekt typu HttpRequest zawierający informacje o żądaniu, jako drugi nazwę szablonu z katalogiem nadrzędnym.

Po uruchomieniu serwera i wpisaniu adresu 127.0.0.1:8000 zobaczymy tekst, który umieściliśmy w szablonie:

../../_images/django_index2.jpg

8.1.11. (Wy)logowanie

Udostępnimy użytkownikom możliwość logowania i wylogowywania się, aby mogli dodawać i przeglądać wiadomości.

Na początku w pliku views.py, dopisujemy importy wymaganych obiektów, później dodajemy widoki loguj() i wyloguj():

Plik views.pyKod nr
6
7
8
9
from django.contrib.auth import login, logout
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from django.contrib import messages
Plik views.pyKod nr
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def loguj(request):
    """Logowanie użytkownika"""
    from django.contrib.auth.forms import AuthenticationForm
    if request.method == 'POST':
        form = AuthenticationForm(request, request.POST)
        if form.is_valid():
            login(request, form.get_user())
            messages.success(request, "Zostałeś zalogowany!")
            return redirect(reverse('czat:index'))

    kontekst = {'form': AuthenticationForm()}
    return render(request, 'czat/loguj.html', kontekst)


def wyloguj(request):
    """Wylogowanie użytkownika"""
    logout(request)
    messages.info(request, "Zostałeś wylogowany!")
    return redirect(reverse('czat:index'))

Logowanie rozpoczyna się od wyświetlenia odpowiedniej strony – to żądanie typu GET. Widok logowania zwraca wtedy szablon: return render(request, 'czat/loguj.html', kontekst). Parametr kontekst to słownik, który pod kluczem form zawiera pusty formularz logowania utworzony w instrukcji AuthenticationForm().

Wypełnienie formularza danymi i przesłanie ich na serwer to żądanie typu POST. Wykrywamy je w instrukcji if request.method == 'POST':. Następnie tworzymy instancję formularza wypełnioną przesłanymi danymi: form = AuthenticationForm(request, request.POST). Jeżeli dane są poprawne if form.is_valid():, możemy zalogować użytkownika za pomocą funkcji login(request, form.get_user()).

Tworzymy również informację zwrotną dla użytkownika, wykorzystując system komunikatów: messages.error(request, "..."). Tak utworzone komunikaty możemy odczytać w każdym szablonie ze zmiennej messages.

Wylogowanie polega na użyciu funkcji logout(request) – wyloguje ona użytkownika, którego dane zapisane są w przesłanym żądaniu. Po utworzeniu informacji zwrotnej podobnie jak po udanym logowaniu przekierowujemy użytkownika na stronę główną (return redirect(reverse('index'))) z żądaniem jej wyświetlenia (typu GET).

Szablon logowania templates/czat/loguj.html zawiera kod:

Plik loguj.html Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- templates/czat/loguj.html -->
<html>
  <head></head>
  <body>
    <h1>Witaj w aplikacji Czat!</h1>

    <h2>Logowanie użytkownika</h2>
    {% if not user.is_authenticated %}
      <form action="." method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Zaloguj</button>
      </form>
    {% else %}
      <p>Jesteś już zalogowany jako {{ user.username }}</p>
      <ul>
        <li><a href="{% url 'czat:index'%}">Strona główna</a></li>
      </ul>
    {% endif %}

  </body>
</html>

W szablonach wykorzystujemy tagi dwóch rodzajów:

  • {% instrukcja %} – pozwalają używać instrukcji sterujących, np. warunkowych lub pętli,
  • {{ zmienna }} – służą wyświetlaniu wartości zmiennych lub wywoływaniu metod obiektów przekazanych do szablonu.
  • {% if not user.is_authenticated %} – instrukcja sprawdza, czy aktualny użytkownik jest zalogowany,
  • {% csrf_token %} – zabezpieczenie formularza przed atakiem typu csrf,
  • {{ form.as_p }} – automatyczne wyświetlenie pól formularza w akapitach,
  • {% url 'czat:index' %} – wstawienie adresu do odnośnika: w cudzysłowach podajemy przestrzeń nazw naszej aplikacji (app_name), a później nazwę widoku (name) zdefiniowane w pliku czat/urls.py,
  • {{ user.username }} – tak wyświetlamy nazwę zalogowanego użytkownika.

Komunikaty zwrotne przygotowane dla użytkownika w widokach wyświetlimy po uzupełnieniu szablonu index.html. Po znaczniku <h1> wstawiamy poniższy kod:

Plik index.html Kod nr
 7
 8
 9
10
11
12
13
      {% if messages %}
        <ul>
        {% for komunikat in messages %}
           <li>{{ komunikat|capfirst }}</li>
        {% endfor %}
        </ul>
      {% endif %}
  • {% if messages %} – sprawdzamy, czy mamy jakieś komunikaty,
  • {% for komunikat in messages %} – w pętli pobieramy kolejne komunikaty...
  • {{ komunikat|capfirst }} – i wyświetlamy z dużej litery za pomocą filtra.

Mapowanie adresów URL na widoki – w pliku czat/urls.py dopisujemy reguły:

Plik urls.pyKod nr
10
11
    url(r'^loguj/$', views.loguj, name='loguj'),
    url(r'^wyloguj/$', views.wyloguj, name='wyloguj'),

Działanie dodanych funkcji testujemy pod adresami: 127.0.0.1:8000/loguj i 127.0.0.1:8000/wyloguj. Używamy nazw i haseł utworzonych wcześniej użytkowników. Przykładowy formularz wygląda tak:

../../_images/django_loguj_form.jpg

8.1.11.1. Ćwiczenie

Adresów logowania i wylogowywania nikt nie wpisuje ręcznie. Wstaw kod generujący odpowiednie linki do szablonu strony głównej po bloku wyświetlającym komunikaty. Użytkownik niezalogowany powinien zobaczyć odnośnik Zaloguj, użytkownik zalogowany – Wyloguj. Przykładowe działanie stron może wyglądać tak:

../../_images/django_zalogowany.jpg
../../_images/django_wylogowany.jpg

8.1.12. Dodawanie wiadomości

Chcemy, by zalogowani użytkownicy mogli dodawać wiadomości, a także przeglądać wiadomości innych.

Zaczynamy od dodania widoku o nazwie np. wiadomosci(). Do pliku views.py dodajemy import i kod funkcji:

Plik views.pyKod nr
10
from czat.models import Wiadomosc
Plik views.pyKod nr
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def wiadomosci(request):
    """Dodawanie i wyświetlanie wiadomości"""
    if request.method == 'POST':
        tekst = request.POST.get('tekst', '')
        if not 0 < len(tekst) <= 250:
            messages.error(
                request,
                "Wiadomość nie może być pusta, może mieć maks. 250 znaków!")
        else:
            wiadomosc = Wiadomosc(
                tekst=tekst,
                autor=request.user)
            wiadomosc.save()
            return redirect(reverse('czat:wiadomosci'))

    wiadomosci = Wiadomosc.objects.all()
    kontekst = {'wiadomosci': wiadomosci}
    return render(request, 'czat/wiadomosci.html', kontekst)

Obsługa żądania typu GET (wyświetlenie wiadomości i formularza):

  • wiadomosci = Wiadomosc.objects.all() – pobieramy wszystkie wiadomości z bazy, używając wbudowanego w Django systemu ORM.
  • return render(request, 'czat/wiadomosci.html', kontekst) – zwracamy szablon, któremu przekazujemy słownik kontekst zawierający wiadomości.

Obsługa żądania typu POST (przesłanie danych z formularza):

  • tekst = request.POST.get('tekst', '') – wiadomość pobieramy ze słownika request.POST za pomocą metody get('tekst', ''), pierwszy argument to nazwa pola formularza użytego w szablonie, drugi argument to wartość domyślna, jeśli pole będzie niedostępne.
  • if not 0 < len(tekst) <= 250: – sprawdzenie minimalnej i maksymalnej długości wiadomości,
  • Wiadomosc(tekst=tekst, autor=request.user) – utworzenie instancji wiadomości za pomocą konstruktora modelu, któremu przekazujemy wartości wymaganych pól,
  • wiadomosc.save() – zapisanie nowej wiadomości w bazie.

Szablon zapisany w pliku templates/czat/wiadomosci.html będzie wyświetlał komunikaty zwrotne, np. błędy, a także formularz dodawania i listę wiadomości:

Plik wiadomosci.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- templates/czat/wiadomosci.html -->
<html>
  <head></head>
  <body>
    <h1>Witaj w aplikacji Czat!</h1>

    {% if messages %}
      <ul>
        {% for komunikat in messages %}
           <li>{{ komunikat|capfirst }}</li>
        {% endfor %}
        </ul>
    {% endif %}

    <h2>Dodaj wiadomość</h2>
    <form action="." method="POST">
      {% csrf_token %}
      <input type="text" name="tekst" />
      <input type="submit" value="Zapisz" />
    </form>

    <h2>Lista wiadomości:</h2>
    <ol>
      {% for wiadomosc in wiadomosci %}
        <li>
          <strong>{{ wiadomosc.autor.username }}</strong> ({{ wiadomosc.data_pub }}):
          <br /> {{ wiadomosc.tekst }}
        </li>
      {% endfor %}
    </ol>

  </body>
</html>
  • <input type="text" name="tekst" /> – “ręczne” przygotowanie formularza, czyli wstawienie kodu HTML pola do wprowadzania tekstu wiadomości,
  • {{ wiadomosc.tekst }} – wyświetlenie właściwości obiektu przekazanego w kontekście.

Adres URL, obsługiwany przez widok wiadomosci(), definiujemy w pliku czat/urls.py, nadając mu nazwę wiadomosci:

Plik urls.pyKod nr
12
    url(r'^wiadomosci/$', views.wiadomosci, name='wiadomosci'),

8.1.12.1. Ćwiczenie

  • W szablonie widoku strony głównej dodaj link “Dodaj wiadomość” dla zalogowanych użytkowników.
  • W szablonie wiadomości dodaj link “Strona główna”.
  • Zaloguj się i przetestuj wyświetlanie [1] i dodawanie wiadomości pod adresem 127.0.0.1:8000/wiadomosci/. Sprawdź, co się stanie po wysłaniu pustej wiadomości.
[1]Jeżeli nie dodałeś do tej pory żadnej wiadomości, lista na początku będzie pusta.

Poniższe zrzuty prezentują efekty naszej pracy:

../../_images/django_final1.jpg
../../_images/django_final2.jpg

8.1.13. Materiały

  1. O Django http://pl.wikipedia.org/wiki/Django_(informatyka)
  2. Strona projektu Django https://www.djangoproject.com/
  3. Co to jest framework? http://pl.wikipedia.org/wiki/Framework
  4. Co nieco o HTTP i żądaniach GET i POST http://pl.wikipedia.org/wiki/Http

Źródła:


Licencja Creative Commons Materiały Python 101 udostępniane przez Centrum Edukacji Obywatelskiej na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.

Utworzony:2017-09-08 o 18:17 w Sphinx 1.5.3
Autorzy:Patrz plik “Autorzy”