Szukaj na tym blogu

Django - pierwsza strona z mysql cz.2

W poprzedniej części przygotowaliśmy projekt pod 'wygryzanko'. Udało się nam nawet wyświetlić Hello World :) Teraz czas na całą resztę - związaną z bazą MySQL i warstwą biznesową. Postanowiłem również wzbogacić naszą małą stronkę o obsługę szablonów, które oddzielają nieco wygląd od kodu więc powinno być bardziej przejrzyście. Zaczynamy.

W pliku strona/models.py powinniśmy dodać klasę reprezentującą tabelę w bazie MySQL:
from django.db import models
class Link(models.Model):
      url = models.CharField(max_length=200)
      opis = models.CharField(max_length=255)
      kiedy = models.DateTimeField()
Tak przygotowana klasa będzie odpowiadać za przechowywanie linków na naszej stronie. Pole ID jest dodawane automatycznie, także jest tutaj pominięte w kodzie. Żeby dodać zmiany do bazy MySQL należy dodać do sekcji INSTALLED_APPS w pliku settings.py naszą aplikację: 'wygryzanko.strona'. Następnie wywołać:
#python manage.py syncdb
Po czym powinna zostać dodana odpowiednia tabela do bazy - dla nas nie jest potrzebna jej znajomość, ponieważ będziemy operować bezpośrednio na obiektach, a Django zajmie się resztą.

Stwórzmy sobie katalog w projekcie o nazwie templates, a w nim plik strona.html o zawartości:
<html><head><title>{{ title }}</title></head><body><center><h1>{{ title }}</h1><hr>
<br>
<i>{% firstof info 'Przeglądasz wygryzarkę' %}</i>
<table style>
<tr style="background-color:gray">
<td>nr</td>
<td>url</td>
<td>opis</td>
<td>kiedy</td>
</tr>
{% for link in linki %}
<tr style="background-color:{% cycle '#917612' '#417682' %}">
<td>{{ forloop.counter }}</td>
<td>{% if forloop.first %}<a href="{{ link.url }}">{{ link.url }}</a>{% else %}{{ link.url }}{% endif %}</td>
<td>{{ link.opis }}</td>
<td>{{ link.kiedy|date:"d.m.Y H:i" }}</td>
</tr>
{% empty %}
<tr><td>brak wpisów - możesz być pierwszy</td></tr>
{% endfor %}
</table>
<br>
Dodaj własną stronę:
<form action="" method="post">
{% csrf_token %}
URL:<input type="text" name="url"><br>
Opis:<input type="text" name="opis"><br>
<input type="submit" value="Dodaj">
</form>
</center>
</body></html>

Omówię pokrótce tagi, które mogą być niezrozumiałe, jednak większość początkujący programiści powinni się domyśleć - w razie czego pytajcie w komentarzach.
{{ title }} - wyświetla zawartość przekazanej zmiennej 'title'
{% firstof info 'Przeglądasz wygryzarkę' %} - wyświetla zawartość pierwszej zmiennej, która nie jest nullem - jak widać, można podać też stałą wartość
{% cycle '#917612' '#417682' %} - za każdym odwołaniem wyświetla inną następną zmienną, gdy dojdzie do ostatniej - zaczyna od początku, powinno się używać wewnątrz pętli, inaczej będzie tylko jedno odwołanie, co mija się z celem tej funkcjonalności
{{ forloop.counter }} - wyświetla, w którym przebiegu pętli jesteśmy (zaczynamy od 1)
{{ link.kiedy|date:"d.m.Y H:i" }} - po | zaczynają się filtry. Tutaj akurat użyjemy filtru daty, podając jak mamy ją sformatować (d-dzień, m-miesiąc, Y-rok, H-godzina, i-minuta).
{% empty %} - co ma się dziać, gdy pętla for nie ma po czym iterować
{% csrf_token %} - specjalne pole typu hidden z unikalną wartością dla formularzy - jest to konieczne przy wysyłaniu POSTa z formularza osadzonego w szablonie.
Żeby można było zlokalizować szablon w projekcie, należy dodać odpowiedni wpis do settings.py, w sekcji TEMPLATE_DIRS. Uwaga ścieżka musi być absolutna - u mnie wygląda ona tak:
"/home/krik/wygryzanko/szablon"
Ostatnia czynnością, jaka nam została, by cieszyć się naszą nową stroną, to oprogramowanie całej logiki aplikacji :) Do tego celu edytujemy plik strona/views.py. Na początek dodajmy wszystkie importy, których będziemy używać:
from django.http import *
from django.template.loader import get_template #ładowanie szablonu z pliku
from django.template.context import RequestContext #Uzupełnianie szablonu
from models import * #możliwość używania naszej klasy Link z models.py
from django.utils.datetime_safe import datetime
import re
Dlaczego użyjemy RequestContext do uzupełniania treści szablonu, a nie Context? Ponieważ, nasz szablon ma w sobie formularz a klasa Context, nie poradzi sobie z funkcją {% csrf_token %} , wymaganą w formularzach.

Po zaimportowaniu potrzebnych bibliotek, edytujmy pozostałości naszego poprzedniego "Hello World", teraz w funkcji index(req), umieścimy taki kod:
t=get_template('strona.html')
c=RequestContext(req,{"title":"Django - wygryzanko (krikusio.blogspot.com)"})
linki=Link.objects.order_by('kiedy').reverse()[0:10].all();
c["linki"]=linki
if (req.method=='POST'):
      c["info"]=nowy_wpis(req.POST)
return HttpResponse(t.render(c))
Krótkie wyjaśnienie co się stało właściwie stało. Otóż, najpierw ładujemy nasz szablon z pliku strona.html. Dzięki wpisowi w settings.py, Django wie, gdzie szukać plików z szablonami. Następnie stworzyliśmy obiekt RequestContext, z ustawioną zmienną title - jest ona wykorzystywana w szablonie. Teraz trochę wyższa szkoła jazdy- Link.objects.order_by('kiedy').reverse()[0:10].all();.
Ta linijka pobiera 10 ostatnio dodanych do bazy wpisów związanych z obiektem Link. Rozbiję te linijkę na mniejsze fragmenty:
Link - nasza klasa z models.py powiązana z bazą danych
objects - dobiera się do obiektów z bazy
order_by('kiedy') - sortowanie po dacie dodania (domyślnie rosnąco)
reverse() - ustawiamy sortowanie malejące
[0:10] - pobieramy 10 obiektów, jest to zamieniane w MySQLu na LIMIT 10
all() - zamienia to co pobrało na listę
Zamiast all() można było użyć get() - jednak get() podnośi wyjątek, gdy nie pobrało z bazy żadnych wpisów.

Po pobraniu danych z bazy, przypisujemy obiekty Link do utworzonego wcześniej RequestContext, by można było uzupełnić szablon. Innymi słowy - wyświetlić ostatnie 10 linków.

Teraz następuje sprawdzenie metody odwołania do strony przez przeglądarkę. Mamy do wyboru dwie POST i GET. Jeżeli jest to metoda POST, to wiemy, że użytkownik wypełnił i przesłał do nas formularz - jeżeli zaś GET, to jest to zwykłe odwołanie do strony. W przypadku metody POST (czyli wysłaniu formularza, co wiąże się z chęcią dodania nowego linku do naszej wygryzarki) powinniśmy przetworzyć wysłane dane z formularza. Tutaj odpowiada za to:
c["info"]=nowy_wpis(req.POST)
Przypiszemy informację (o powodzeniu lub niepowodzeniu) do RequestContext, a w celu lepszej przejżystości kodu, dane z POST zostaną przetworzone przez funkcję nowy_wpis, którą za chwilę utworzymy.

Ostatnim krokiem jest już wyrenderowanie z szablonu i danych, kodu HTML do wyświetlenia po stronie przeglądarki - za to odpowiada ostatnia linijka.

Teraz stworzymy funkcję nowy_wpis, odpowiedzialną za dodawanie nowych linków i wszystkich mechanizmów z tym związanych (tj. usuwanie niepotrzebnych wpisów itd.):
def nowy_wpis(post):
urlX=post.get('url','')
opisX=post.get('opis','')
if (urlX==''):
      return 'Brak podanego linku'
if re.match(r"https?://([^\.]+\.)+[a-z]{2,6}", urlX, re.IGNORECASE)==None:
      return urlX+' nie jest poprawnym adres strony'
if (opisX==''):
      return 'Brak podanego opisu'
if ( Link.objects.filter(url__istartswith=urlX).count()>0):
      return 'Taki wpis w bazie już istnieje - daj szansę innym'
nowy=Link(url=urlX,kiedy=datetime.now(),opis=opisX)
nowy.save()
linki=Link.objects.order_by('kiedy').reverse()[9:10].all();
if (len(linki)==1):
      Link.objects.filter(kiedy__lt=linki[0].kiedy).delete();
return 'Wpis został dodany do bazy'
Pierwsze pytanie jakie się nasuwa to dlaczego używam post.get('url','') zamiast post['url']? Otóż post['url'] wywoła wyjątek, gdy takie pole nie będzie przekazane przez formularz, natomiast post.get(), w przypadku nieprzekazania takiego pola, podstawi wartość z drugiego parametru - bardzo wygodne rozwiązanie. Następnie odbywa się, zwykła weryfikacja - czy pola nie są puste, oraz czy link jest w miarę poprawny. Funkcja zwraca za każdym razem String'a, który jest używany do wyświetlenia komunikatu po stronie przeglądarki.

Dokładniej skupię się na opisaniu jedynie weryfikacji, czy podobny link nie widnieje już przypadkiem w naszej bazie:
if ( Link.objects.filter(url__istartswith=urlX).count()>0):
Link.objects - obiekty typu Link z bazy danych
filter - ograniczenia co do obiektów, jakie mają być pobrane z bazy
url__istartswith=urlX - pole url, musi się zaczynać od wartości urlX (tej z formularza).
count() - ile pobrano obiektów z bazy
Skupmy się na chwilę na url__istartswith. Ogólna konwencja jest taka, że najpierw określamy pole jakie podlega filtrowaniu (tutaj url), a po dwóch znakach podkreślenia, określamy jaki rodzaj filtru zastosować (tutaj istartswith). Zamiast istartswith, moglibyśmy użyć startswith, jednak przedrostek "i", oznacza 'ignorecase' - czyli ignorowanie wielkości liter. Filtry są zamieniane na zapytanie zawierające "WHERE" a url__istartswith na "ILIKE 'napis%'. Natomiast count() to odpowiednik count(*) z SQL'a.

Jak można wywnioskować z kodu - bardzo prosto dodać nowe wpisy do bazy. Wystarczą dwie linijki:
nowy=Link(url=urlX,kiedy=datetime.now(),opis=opisX)
nowy.save()
W pierwszej tworzymy nowy obiekt, a w drugiej jest on dodawany do bazy. Co ciekawe po wywolaniu save() ten obiekt będzie posiadał wypełnione pole id, takie same jakie otrzymał w bazie danych.

Jako, że po wyświetlamy 10 ostatnich wpisów, przy dodawaniu można usunąć pozostałe, czyli:
linki=Link.objects.order_by('kiedy').reverse()[9:10].all();
if (len(linki)==1):
      Link.objects.filter(kiedy__lt=linki[0].kiedy).delete();
Z wcześniejszych wyjaśnień, wiemy już, że pobieramy ostatni, wyświetlany element (czyli 10). Metoda all(), zamienia nam pobrane dane na listę obiektów, toteż w następnej linijce sprawdzamy, czy coś zostało pobrane z bazy. Ostatnia linijka, może wymagać trochę więcej wyjaśnienia:
filter - użyjemy warunku "WHERE"
kiedy__lt - warunkiem będzie sprawdzenie czy pole 'kiedy', jest mniejsze niż linki[0].kiedy, czyli data dodania dziesiątego w kolejności wyświetlanai wpisu.
delete(); - oznacza, że obiekty te zostaną usunięte z bazy.
Prawda, że proste :D Przyznam, że ta koncepcja Django, ukrywania bazy przed programistą, wymaga przyzwyczajenia. Lecz podejście takie, po głębszym zbadaniu jest bardzo przyjazne. Powodzenia!

Brak komentarzy:

Prześlij komentarz