Skip to content

Commit

Permalink
Uzupełnienie scenariusza Django: modele i panel administracyjny.
Browse files Browse the repository at this point in the history
  • Loading branch information
xinulsw committed Dec 28, 2016
1 parent 9010846 commit eeb277d
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 88 deletions.
150 changes: 97 additions & 53 deletions docs/python/malybar/malybar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ aplikacji. W praktyce jest to nazwa katalogu, w którym znajduje się aplikacja,
oraz domyślna nazwa pliku konfiguracyjnego :file:`urls.py` bez rozszerzenia.
Wartość parametru ``namespace`` definiuje przestrzeń nazw, w której dostępne będą adresy używane w aplikacji.


Widok domyślny
==============

Expand Down Expand Up @@ -234,7 +235,6 @@ zdefiniujemy w pliku :file:`pizza/views.py`:
znaków, czy to w komentarzach czy w kodzie.



Nazwa funkcji – ``index()`` – jest umowna. Każdy widok otrzymuje szczegóły żądania wysłanego przez klienta
(obiekt typu ``HttpRequest``) i powinien zwrócić jakąś odpowiedź (``HttpResponse``).
W tym wypadku zwracamy funkcję ``render()`` wywołującą wskazany jako drugi parametr szablon,
Expand Down Expand Up @@ -286,96 +286,140 @@ W przypadku błędów Django wyświetla obszerne informacje, które na pierwszy
są bardzo skomplikowane. Nie musisz studiować całości, żeby zrozumieć, co poszło nie tak.
Skup się na początku komunikatu!


Model danych
============

Django jest wyposażone we własny system ORM (ang.), służący zarówno do definiowania,
jak i zarządzania źródłami danych.
Podstawą użytecznej aplikacji są dane. Django realizuje obiektowy wzorzec programowania,
więc dane definiujemy jako klasy opisujące tzw. modele.
**Model danych** – to kompletne źródło informacji o jakimś obiekcie, zawiera jego właściwości
(pola) oraz metody działań na nich.

W pliku :file:`moja_apl/models.py` definiujemy klasę(y) opisującą(e) źródła danych aplikacji.
Odpowiadają one tabelom w bazie danych. Każda klasa zawiera pola opisujące przechowywane w nich
informacje. Pola odpowiadają kolumnom w tabelach.
W pliku :file:`pizza/models.py` definiujemy klasy opisujące źródła danych naszej aplikacji:

Następnie tworzymy tzw. *migrację*, czyli informację o zmianie modelu naszej aplikacji.
.. raw:: html

.. code-block:: bash
<div class="code_no">Kod nr <script>var code_no = code_no || 1; document.write(code_no++);</script></div>

$ python manage.py make migrations moja_apl
$ python manage.py sqlmigrate moja_apl 0001
$ python manage.py migrate
.. highlight:: python
.. literalinclude:: pizza/models_01.py
:linenos:
:lineno-start: 1
:lines: 1-

Nazwa każdego modelu (klasy) powinna zaczynać się dużą literą. Każdy model jest potomkiem
klasy Models (dziedziczenie). Definicja każdej zmiennej (właściwości) zawiera wywołanie
metody tworzącej pole wymaganego typu. Za pomocą nazwanych argumentów określamy dodatkowe cechy pól.

Drugie opcjonalne polecenie pozwala zobaczyć klauzule SQL-a, które zostaną użyte do wprowadzenia
zdefiniowanych w modelu zmian do bazy. Ostatnie polecenie na podstawie migracji wszystkich
zarejestrowanych aplikacji tworzy bazę danych, a w niej odpowiednie tabele.
.. note::

Testowanie modelu
-----------------
Najpopularniejsze typy pól:

Po zdefiniowaniu modelu możemy go od przetestować w konsoli, zanim wykorzystamy
go w aplikacji.
- ``CharField`` – pole znakowe, przechowuje niezbyt długie napisy, np. nazwy;
- ``TextField`` – pole tekstowe, dla długich tekstów, np. opisów;
- ``DecimalField`` – pole dziesiętne, nadaje się do przechowywania liczb rzeczywistych, np. cen;
- ``Date(Time)Field`` – pole daty (i czasu);
- ``BooleanField`` – pole logiczne, przechowuje wartość ``True`` lub ``False``;
- ``ForeignKey`` – pole klucza obcego, czyli relacji; wymaga nazwy powiązanego modelu jako pierwszego argumentu.

.. code-block:: bash
Właściwości pól:

$ python manage.py shell
- ``verbose_name`` lub napis podany jako pierwszy argument – przyjazna nazwa pola;
- ``max_length`` – maksymalna długość pola znakowego;
- ``blank = True`` – pole może zawierać ciąg pusty;
- ``help_text`` – tekst podpowiedzi;
- ``max_digits``, ``decimal_places`` – określenie maksymalnej ilości cyfr i ilości miejsc po przecinku liczby rzeczywistej;
- ``auto_now_add = True`` – data (i czas) wstawione zostaną automatycznie;
- ``default`` – określenie wartości domyślnej pola;
- ``choices`` – wskazuje listę wartości dopuszczalnych dla danego pola.

Powyższe polecenie uruchamia konsolę Pythona (rozszerzoną, jeżeli jest dostępna) i tworzy
środowisko testowe. Zobaczmy, jak za pomocą bazodanowego API zarządzać danymi.
Na początku **tworzenie danych** (ang. *create*):
W bazie chcemy przechowywać dane o pizzach. Każda z nich składać się może z wielu składników.
Tak więc między modelami `Pizza` i `Skladnik` istnieje relacja jeden-do-wielu.

Po dokonaniu zmian w bazie tworzymy tzw. *migrację*, w terminalu wydajemy polecenia:

.. code-block:: bash
$ pytanie = Pytanie(tresc="Jak się nazywasz?")
$ pytanie.save()
$ print pytanie.id, pytanie.tresc, pytanie.data_pub
~/dj_10_4/malybar$ python manage.py make migrations pizza
~/dj_10_4/malybar$ python manage.py migrate
[zrzut]
**Migracja** – tworzona przez pierwsze polecenie, to informacje o zmianie w bazy danych zapisywana
przez Django w języku SQL w katalogu :file:`pizza/migrations`.

.. code-block:: bash
Drugie polecenie na podstawie migracji wszystkich zarejestrowanych aplikacji (w tym domyślnych)
buduje lub aktualizuje bazę danych. Z nazw modeli Django utworzy odpowiednie tabele, w oparciu o zdefiniowane
właściwości – odpowiednie kolumny.

$ from django.utils import timezone
$ pytanie = Pytanie(tresc="Gdzie mieszkasz??", data_pub=timezone.now())
$ pytanie.save()
$ print pytanie.id, pytanie.tresc, pytanie.data_pub

Przećwiczmy też **wydobywanie danych** (ang. *read*) z bazy:
Zmiany modeli
-------------

.. code-block:: bash
1. Do modelu `Pizza` dodamy pole przechowujące użytkownika, który dodał ją do bazy.

- przed definicjami klas dodaj import ``from django.contrib.auth.models import User``
- dodaj klucz obcy o nazwie ``autor`` wskazujący na model ``User``

2. Dodamy możliwość "autoprezentacji" modeli, czyli wyświetlania ich znakowej reprezentacji.

- do każdej klasy dodaj następującą metodę:

.. code-block:: python
def __unicode__(self):
return u'%s' % (self.nazwa)
3. W panelu administracyjnym przydatna jest forma liczby mnogiej służąca nazywaniu egzemplarzy danego modelu.

- w każdym modelu umieść dodatkową klasę `Meta` z odpowiednią formą liczby mnogiej, np.:

.. code-block:: python
class Meta:
verbose_name_plural = 'pizze'
4. Na koniec utwórz migrację aplikacji i zaktualizuj bazę danych!

$ pytania = Pytanie.objects.all()
$ print pytania
$ print pytania[0].id, pytania[0].tresc, pytania[0].data_pub
$ pytanie = Pytanie.objects.get(pk=2)
$ print pytanie.tresc
$ Pytanie.objects.filter(data_pub__year=timezone.now().year)
$ Pytanie.objects.filter(tresc__startswith='Kiedy')
$ Pytanie.objects.count()

Strona administracyjna
======================

Zarządzanie treściami czy użytkownikami wymaga panelu administracyjnego, Django dostarcza nam
go automatycznie. Na początku tworzymy konto administratora:
go automatycznie.

Tworzymy konto administratora, wydając w terminalu polecenie:

.. code-block:: bash
$ python manage.py createsuperuser
~/dj_10_4/malybar$ python manage.py createsuperuser
Django zapyta o nazwę, e-mail i hasło. Podajemy: `admin`, `""` (pomijamy), `q1w2e3r4`.

Django zapyta o nazwę, e-mail i hasło. Podajemy: “admin”, “” (pomijamy), “admin”.
Jeżeli chcemy mieć możliwość dodawania treści, w pliku :file:`pizza/admin.py`
importujemy nasz(e) model(e) i rejestrujemy go(je):
Aplikacja w panelu administratora: uzupełniamy plik :file:`pizza/admin.py`:

.. highlight:: python
.. literalinclude:: pizza/admin_01.py
:linenos:
:lineno-start: 1
:lines: 1-

**Ćwiczenie**
Po zaimportowaniu modeli danych rejestrujemy je w panelu, dzięki temu będziemy mogli dodawać
i modyfikować dane użytkowników i aplikacji.

Zarządzanie danymi
------------------

1. Uruchom serwer i wywołaj w przeglądarce adres: ``127.0.0.1:8000/admin``.
2. Zaloguj się jako administrator, dodaj dwie pizze i przynajmniej po jednym składniku do każdej.
3. Utwórz konto dla użytkownika "uczen" z hasłem "q1w2e3r4". Przydziel mu prawa do dodawania, modyfikowania i usuwania pizz i składników. Uwaga: nie zapomnij zaznaczyć opcji "W zespole"!
4. Zaloguj się na konto "uczen" i dodaj jeszcze jedną pizzę z co najmniej jednym składnikiem.

.. note::

Obsługa panelu administracyjnego jest dobrą okazją, żeby zobaczyć jak wygląda komunikacja
między klientem a serwerem w aplikacjach sieciowych wykorzystujących protokół http.
Serwer testowy wyświetla pełen zapis sesji w oknie terminala.

[zrzut]

Uruchom serwer, a w przeglądarce wpisz adres: ``127.0.0.1:8000/admin``.
Po zalogowaniu się dodaj dwa pytania. Następnie utwórz konto dla użytkownika "uczen"
z hasłem "q1w2e3". Przydziel mu prawa do dodawania, modyfikowania i usuwania pytań.
Przeloguj się na konto "uczen" i dodaj jeszcze dwa pytania.

[zrzut]

38 changes: 3 additions & 35 deletions docs/python/malybar/pizza/admin_01.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,7 @@
# -*- coding: utf-8 -*-
from django.contrib import admin

# Register your models here.
from .models import Pizza, Skladnik
from django.forms import Textarea
from django.db import models


class SkladnikInline(admin.TabularInline):
model = Skladnik
max_num = 6
extra = 3
fields = ['nazwa', 'jarski']


class PizzaAdmin(admin.ModelAdmin):
# fields = ['przedmiot', 'kategoria', 'typ', 'polecenie']
exclude = ('autor',)
# obiekty zależne do wyświetlenia
inlines = [SkladnikInline]
# nagłówki do wyświetlenia
# list_display = ('question_text', 'pub_date', 'was_published_recently')
# wyszukiwanie
search_fields = ['nazwa']
list_per_page = 10

formfield_overrides = {
models.TextField: {'widget': Textarea(attrs={'rows': 2, 'cols': 100})},
}

def save_model(self, request, obj, form, change):
if not change:
obj.autor = request.user
obj.save()


# rejestrujemy model Pizza w panelu administracyjnym
admin.site.register(Pizza, PizzaAdmin)
# rejestrujemy modele w panelu administracyjnym
admin.site.register(Pizza)
admin.site.register(Skladnik)
30 changes: 30 additions & 0 deletions docs/python/malybar/pizza/models_01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models


class Pizza(models.Model):
LARGE = 'L'
MEDIUM = 'M'
SMALL = 'S'
ROZMIARY = (
(LARGE, 'duża'),
(MEDIUM, 'średnia'),
(SMALL, 'mała'),
)
nazwa = models.CharField(verbose_name='Pizza', max_length=30)
opis = models.TextField(blank=True, help_text='Opis Pizzy')
rozmiar = models.CharField(max_length=1, choices=ROZMIARY, default=LARGE)
cena = models.DecimalField(max_digits=5, decimal_places=2)
data = models.DateField('dodano', auto_now_add=True)


class Skladnik(models.Model):
pizza = models.ForeignKey(Pizza, related_name='skladniki')
nazwa = models.CharField(verbose_name=u"składnik", max_length=30)
jarski = models.BooleanField(
default=False,
verbose_name=u"jarski?",
help_text=u"Zaznacz, jeżeli składnik jest odpowiedni dla"
u" wegetarian")
48 changes: 48 additions & 0 deletions docs/python/malybar/pizza/models_02.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

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


class Pizza(models.Model):
LARGE = 'L'
MEDIUM = 'M'
SMALL = 'S'
ROZMIARY = (
(LARGE, 'duża'),
(MEDIUM, 'średnia'),
(SMALL, 'mała'),
)
nazwa = models.CharField(verbose_name='Pizza', max_length=30)
opis = models.TextField(blank=True, help_text='Opis Pizzy')
rozmiar = models.CharField(max_length=1, choices=ROZMIARY, default=LARGE)
cena = models.DecimalField(max_digits=5, decimal_places=2)
data = models.DateField('dodano', auto_now_add=True)
autor = models.ForeignKey(User)

def __unicode__(self):
return u'%s' % (self.nazwa)

class Meta:
verbose_name_plural = 'pizze'


class Skladnik(models.Model):
pizza = models.ForeignKey('Pizza', related_name='skladniki')
nazwa = models.CharField(verbose_name=u"składnik", max_length=30)
jarski = models.BooleanField(
default=False,
verbose_name=u"jarski?",
help_text=u"Zaznacz, jeżeli składnik jest odpowiedni dla"
u" wegetarian")

def is_jarski(self):
return self.jarski

def __unicode__(self):
return u'%s' % (self.nazwa)

class Meta:
verbose_name = "składnik"
verbose_name_plural = "składniki"

0 comments on commit eeb277d

Please sign in to comment.