Poprzedni wykład     Lista wykładów     Następny wykład

Administrowanie stroną

Administrator strony (superuser) będzie miał możliwość modyfikowania informacji o apartamentach i cenach. Będzie mogł także przeglądać listę zarejestrowanych użytkowników, usuwać użytkowników i wysyłac do nich maile.

Wejście na stronę adminstratora Wyświetlanie i edycja cenników Edycja opisu apartamentów Podgląd rezerwacji

Wejście na stronę administratora

Wejście na stronę administracyjną będzie następowało automatycznie po logowaniu, jeśli okaże się że użytkownik jest superuserem. Wymaga to:
Spis treści

Wyświetlanie i edycja cenników

Definiujemy dwie nowe tabele (plik models.py):
from django.db import models

class Apartment(models.Model):
    ...

class TypCeny(models.Model):
    description = models.CharField(max_length=60,unique=True)
    active = models.BooleanField()
    
class Cena(models.Model):
    price = models.SmallIntegerField()
    type = models.ForeignKey(TypCeny)
    apartment = models.ForeignKey(Apartment)
Klasa TypCeny zawiera teksty typu Cena w weekend majowy, Cena w sezonie, z wyżywieniem,... oraz pole logiczne active pozwalające na tymczasowe ukrywanie przed użytkownikiem pewnych cen. W klasie Cena definiujemy dwa klucze obce wiążące cenę z apartamentem i typem (opisem) ceny. Kod SQL tworzący tabelę Cena wygląda tak:
CREATE TABLE "bookings_cena" (
    "id" integer NOT NULL PRIMARY KEY,
    "price" smallint NOT NULL,
    "type_id" integer NOT NULL REFERENCES "bookings_typceny" ("id"),
    "apartment_id" integer NOT NULL REFERENCES "bookings_apartment" ("id")
)
;
COMMIT;
Domyślnie klasa ForeignKey wiąże rekordy z dwóch tabel używając pola id, a dokładniej, używa klucza głównego. Można to zachowanie zmienić: type = models.ForeignKey(TypCeny,to_field=nazwa_pola). Inne ważne domyślne zachowanie, to kaskadowe usuwanie. Jeżeli usuniemy rekord z tabeli TypCeny, to automatycznie usunięte zostaną wszystkie rekordy z tabeli Cena powiązane z usuniętym rekordem. To zachowanie też można zmienić: type = models.ForeignKey(TypCeny,on_delete=pożądane_zachowanie).
Definiując klasę Cena nazwaliśmy pola type i apartment, w tabeli powstały pola type_id i apartment_id. Nie ma to większego znaczenia, jeżeli korzystamy z pośrednictwa Django przy dostępie do bazy danych. Możemy wtedy korzystać z nazw nadanych w definicji klasy.
Przykładem będzie wyświetlenie cennika "zwykłemu" użytkownikowi.
Trochę rozbudowany plik main_page.html:
{% extends "base.html"%}
{% block header %} 
...
{% endblock %}    
{% block content %}
<p>
<div style="text-align:center;">
<h2>Strona domowa pensjonatu "Pod kwiatami"</h2>
E-mail: <b><tt>podkwiatami@gmail.com</tt></b>
<br>Telefon: <b><tt>123-456-789</tt></b>
...
<h2>Lista apartamentów</h2>
<table border="0px" width="70%">
  {% for apartment in apartments %}
  <tr style="text-align:center;">
    <td style="text-align:left;font-size:14pt;">
       ...
       <p class="br"><a href="/prices/{{apartment.id}}">Cennik</a>
       ...
    </td>
    ...
  </tr>
  {% endfor %}
</table>
Mapowanie w pliku urls.py:
(r'^prices/(\w+)/$',prices_page),
Fragment adresu \w+ oznacza dowolny ciąg złożony ze znaków alfanumerycznych i znaku podkreślenia. Umieszczenie go w nawiasach powoduje przekazanie do funkcji prices_page drugiego argumentu (nadamy mu nazwę id_app).

Możliwe jest też inne rozwiązanie:


Funkcja prices_page w pliku views.py:
def prices_page(request,id_app):
    template = get_template("prices.html")
    apartment = Apartment.objects.get(id=id_app)
    prices = Cena.objects.select_related().filter(apartment=id_app)
    variables = RequestContext(request,{'apartment':apartment,'prices':prices})
    output = template.render(variables)
    return HttpResponse(output)
Funkcja Cena.objects.select_related().filter(apartment=id_app) zwraca listę wszystkich cen powiązanych z apartamentem, którego klucz główny jest równy id_app. Funkcja filter może mieć więcej argumentów. filter(apartment=id_app,type=2) zwróci listę cen powiązanych z apartamentem którego klucz główny jest równy id_app oraz z typem (opisem) ceny, którego klucz główny jest równy 2.
Plik prices.html:
{% extends "base.html"%}
{% block title %} - apartament {{apartment.name}}{% endblock %}
{% block header %}<h2><center>Apartament {{apartment.name}} - cennik</center></h2>{% endblock %}
{% block content%}
<p>
<div style="text-align:center;">{% if apartment.pathToPicture %} <img src="/media/{{apartment.pathToPicture}}" />{% endif %}</div>
</p>
<center>
<div style="margin-left:30%;margin-right:30%;">
<fieldset>
<table border="0px">
  <tr>
    <th>Rodzaj ceny</th>
    <th>Cena za apartament (zł.)</th>
  </tr>
  {% for price in prices %}
  <tr>
    <td style="text-align:left;">{{price.type.description}}</td>
    <td style="text-align:right;">{{price.price}}</td>
  </tr>
  {% endfor %}  
</table>
</fieldset>
</div>
</center>
{% endblock %}
{% block footer %}
<p>
<div style="text-align:center;"><a href="/">Główna strona</a></div>
</p>
{% endblock %}
price to obiekt typu Cena, price.type to powiązany z nim obiekt typu TypCeny.

Administrator ma możliwość edytowania tabeli TypCeny.
Pliki html używane podczas edycji można umieścić gdziekolwiek w katalogu templates, dla zachowania porządku będziemy je umieszczać w podkatalogu templates/edition.
Fragment pliku admin.html:

<h2>Rodzaje cen</h2>
<ul>
  <hr width="50%" align="left">
  {% for type in types %}
  <li style="list-style-type:none;width:50%;">
    <form style="display:inline;"><input type="checkbox" disabled {% if type.active %}checked="checked"{% endif %}></form>
    {{type.description}}<div id="nav"><a href="/save_type/?id={{type.id}}">Edycja</a>    <a href="/delete_type/?id={{type.id}}">Usunięcie</a></div>
  </li>
  {% endfor %}
  <hr width="50%" align="left">
  <li style="list-style-type:none;"><a href="/save_type/">Dopisanie</a></li>
</ul></p>
Dopisujemy w pliku urls.py mapowania:
(r'^save_type/$',save_type_page),
(r'^delete_type/$',delete_type_page),
Tworzymy pliki templates/type_delete.html:
{% extends "base.html"%}
{% block title %} - usunięcie opisu ceny{% endblock %}
{% block content%}
<center>
<h2>Usunięcie opisu ceny</h2>
<p>
<div stylemargin-left:35%;margin-right:35%;">
<fieldset>
  <form method="post" action=".">{% csrf_token %}
    Opis: <b>{{type.description}}</b>
    <input type="hidden" name="id" value="{{ type.id }}" />
    <p><input type="submit" value="Usunięcie"/></p>
  </form>
</fieldset>
</div>
</center>  
{% endblock %}
oraz templates/type_save.html:
{% extends "base.html"%}
{% block title %} - edycja opisu ceny{% endblock %}
{% block content%}
<center>
<h2>Edycja opisu ceny</h2>
<p>
<div style="margin-left:35%;margin-right:35%;">
<fieldset>
  <form method="post" action=".">{% csrf_token %}
    <div style="text-align:left";>{{ form.as_p }}</div>
    <input type="hidden" name="id" value="{{ type.id }}" />
    <input type="submit" value="Zapisanie"/>    <input type="reset" value="Wartości dotychczasowe"/>
  </form>
</fieldset>
</div>
</center>  
{% endblock %}
Oba pliki zawierają formularze, w obu przypadkach zatwierdzenie formularza przekierowuje na tę samą stronę (action="."). Funkcje obsługujące te strony rozpoznają sposób wejścia na stronę poprzez metodę żądania POST lub GET. Definiujemy w pliku views.py funkcje save_type_page i delete_type_page:
@permission_required('user.is_superuser',"/login/")    
def delete_type_page(request):
    if request.method == 'POST':
        id = request.POST['id']
        type = TypCeny.objects.get(id=id)
        type.delete()
        template = get_template("admin.html")
        apartments = Apartment.objects.all()
        types = TypCeny.objects.all()
        variables = RequestContext(request,{'apartments':apartments,'types':types})                
        output = template.render(variables)            
        return HttpResponse(output)                     
    else:
        id = request.GET['id']
        type = TypCeny.objects.get(id=id)
        template = get_template("edition/type_delete.html")
        variables = RequestContext(request,{'type':type})
        output = template.render(variables)
        return HttpResponse(output)
Metoda TypCeny.objects.get(id=id) zwraca obiekt - rekord w tabeli TypCeny spełniający podany warunek lub generuje wyjątek ObjectDoesNotExist. W naszym programie może się tak zdarzyć, jeżeli jednocześnie jest zalogowanych dwóch administratorów, i obaj usuwają ten sam rekord z tabeli TypCeny. Metoda type.delete() usuwa obiekt - rekord z tabeli.
@permission_required('user.is_superuser',"/login/")    
def save_type_page(request):
    if request.method == 'POST':
        form = FormularzEdycjiTypu(request.POST,True)
        if form.is_valid():
            id = request.POST['id']
            if len(id) == 0:
                type = TypCeny() 
            else:
                type = TypCeny.objects.get(id=int(id))
            type.description = form.cleaned_data['description']
            type.active = form.cleaned_data['active']
            type.save()
            template = get_template("admin.html")
            apartments = Apartment.objects.all()
            types = TypCeny.objects.all()
            variables = RequestContext(request,{'apartments':apartments,'types':types})                
            output = template.render(variables)            
            return HttpResponse(output)                     
    elif request.GET.has_key('id'):
        id = request.GET['id']
        type = TypCeny.objects.get(id=id)
        form = FormularzEdycjiTypu({'description':type.description,'active':type.active},False)
        template = get_template("edition/type_edition.html")
        variables = RequestContext(request,{'form':form,'type':type})
        output = template.render(variables)
        return HttpResponse(output)        
    else:
        form = FormularzEdycjiTypu({'active':True},False)
    template = get_template("edition/type_edition.html")
    variables = RequestContext(request,{'form':form})
    output = template.render(variables)
    return HttpResponse(output)
Jeżeli żądanie jest typu GET, to możliwe są dwa przypadki: Jeżeli żądanie jest typu POST (administrator zatwierdził formularz), to: Pozostała jeszcze definicja formularza (plik forms.py):
class FormularzEdycjiTypu(forms.Form):
    def __init__(self,dictionary,control):
        forms.Form.__init__(self,dictionary)
        self.control = control
    id = forms.IntegerField(required=False,widget=forms.HiddenInput())        
    description = forms.CharField(label="Opis:",max_length=60,widget=forms.TextInput({'size':60}))
    active = forms.BooleanField(label="Aktywna:",required=False)
    def clean_description(self):        
        if not self.control:
            return description
        description = self.cleaned_data['description']    
        id = self.cleaned_data['id']
        if len(description) == 0:
            raise forms.ValidationError("To pole musi być wypełnione")           
        try:
            type = TypCeny.objects.get(description=description)
            if type.id == id:
                return description
        except ObjectDoesNotExist:             
            return description
        raise forms.ValidationError("Taki opis już występuje")
Kilka fragmentów tego formularza jest nietypowych.

Opiszemy teraz edycję samych cen. Nie będziemy korzystać z formularzy oferowanych przez Django. Formularze te są dostosowane do edytowania obiektu - rekordu w tabeli, a my będziemy na jednym formularzu edytowali wszystkie ceny jednego apartamentu.

Opis formularza umieścimy w pliku edition/prices_edition.html:

{% extends "base.html"%}
{% block title %} - apartament {{apartment.name}}{% endblock %}
{% block header %}<h3><center style="white-space:pre;">Apartament:  {{apartment.name}} - cennik</center></h3>{% endblock %}
{% block content%}
<center>
<div style="margin-left:20%;margin-right:20%;">
<fieldset>
<legend> {{ legend }} </legend>
<form method="post" action=".">{% csrf_token %}
<table border="0px">
  <tr>
    <th width="50%">Rodzaj ceny</th>
    <th width="50%">Cena za apartament (zł.)</th>
  </tr>
  {% for type in types %}
  <tr>
    <td style="text-align:left;">
      <input type="checkbox" disabled {% if type.active %}checked="checked"{% endif %}/>
      {{type.description}}
    </td>
    <td style="text-align:right;"><input type="text" name="{{type.id}}" value="{{type.price}}"/></td>
  </tr>
  {% endfor %}  
</table>
<input type="submit" value="Zapisanie"/>    <input type="reset" value="Wartości dotychczasowe"/>  
</fieldset>
</form>
</div>
</center>
{% endblock %}
{% block footer%}
<p>
<div style="text-align:center;"><a href="/admin">Strona administratora</a></div>
</p>
{% endblock %}
Informacja o aktywności rodzaju cen jest tylko wyświetlana, edytować można wyłącznie wartość ceny.
W pliku views.py definiujemy funkcję all_prices_page:
@permission_required('user.is_superuser',"/login/")     
def all_prices_page(request,id_app):
    legend = ''
    if request.method == 'POST':
        for key in request.POST.keys():
            ok = True    
            if key.isdigit():
                id_type = int(key)
                try:
                    price_value = int(request.POST[key])
                    save = (price_value>0)
                    ok = (price_value>=0)
                except ValueError:
                    ok = False
            else:
                ok = False
            if ok:
                legend = 'Nowe ceny zostały zapisane'
                if save:
                    try:
                        cena = Cena.objects.get(type=id_type,apartment=id_app)
                        cena.price = price_value
                    except ObjectDoesNotExist:    
                        cena = Cena(price=price_value,type_id=id_type,apartment_id=id_app)
                        cena = Cena.objects.create(price=price_value,type=TypCeny.objects.get(id=id_type),apartment=Apartment.objects.get(id=id_app))
                    cena.save()
                else:
                    try:
                        cena = Cena.objects.get(type=id_type,apartment=id_app)
                        cena.delete()
                    except ObjectDoesNotExist:
                        pass    
    template = get_template("edition/prices_edition.html")
    apartment = Apartment.objects.get(id=id_app)
    types = TypCeny.objects.all()
    for typ in types:
        try:
            typ.price = Cena.objects.select_related().get(apartment=id_app,type=typ.id).price
        except ObjectDoesNotExist:
            typ.price = 0 
    variables = RequestContext(request,{'apartment':apartment,'types':types,'legend':legend})
    output = template.render(variables)
    return HttpResponse(output)
Nasz formularz nie dziedziczy po klasie django.forms.Form, musimy zatem samodzielnie przeprowadzić walidację wpisanych cen. Walidacja odbywa sie w pętli po wszystkich cenach, każda poprawna cena jest zapisywana - również wtedy gdy na formularzu znajdują się niepoprawne ceny. Cena jest poprawna jeżeli: Dopuszczamy cenę zerową, jeżeli poprzednio cena była dodatnia, to wpisanie zera oznacza polecenie usunięcia tej ceny. Jeśli zmienna logiczna save (save = (price_value>0)) ma wartość True, to zapisujemy (INSERT lub UPDATE), w przeciwnym razie usuwamy. Dopisanie (INSERT) wymaga utworzenia nowego obiektu.
except ObjectDoesNotExist:    
    cena = Cena(price=price_value,type_id=id_type,apartment_id=id_app)
    cena = Cena.objects.create(price=price_value,type=TypCeny.objects.get(id=id_type),apartment=Apartment.objects.get(id=id_app))
Oczywiście, potrzebny jest tylko jeden z powyższych wierszy.
Pierwszy wiersz jest krótszy, ale ma dwie wady: Fragment:
for typ in types:
    try:
        typ.price = Cena.objects.select_related().get(apartment=id_app,type=typ.id).price
    except ObjectDoesNotExist:
        typ.price = 0
wykorzystuje możliwość dynamicznej zmiany struktury obiektu. Zmienna typ jest typu TypCeny, a w tej klasie nie ma pola price.

Spis treści

Edycja opisu apartamentów

Większość potrzebnego kodu jest standardowa.
W pliku admin.html są odsyłacze do strony edycyjnej.
<h2>Lista apartamentów</h2>
<ul>
  <hr width="50%" align="left">
  {% for apartment in apartments %}
  <li style="list-style-type:none;width:50%;">{{apartment.name}}<div id="nav">
      <a href="/save_apartment/?id={{apartment.id}}">Edycja</a>    <a href="/all_prices/{{apartment.id}}">Cennik</a></div>
      <br>Sypialnie: {{apartment.bedrooms}}
      <br>Łóżka: {{apartment.beds}}
      <br>{% if apartment.kitchenette %} Posiada aneks kuchenny {% else %} Nie posiada aneksu {% endif %}
      <br>{% if apartment.pathToPicture %}
      <img src="/media/{{apartment.pathToPicture}}" alt="Brak zdjęcia" width="100px"/>
      {% else %}
      Brak informacji o zdjęciu       
      {% endif %}
      <hr width="100%" align="left">
  </li>
  {% endfor %}
  <li style="list-style-type:none;"><a href="/save_apartment/">Dopisanie</a></li>
</ul>
W pliku urls.py dopisujemy mapowanie:
(r'^save_apartment/$',save_apartment_page)
W pliku forms.py opisujemy formularz:
class FormularzEdycjiApartamentu(forms.Form):
    name = forms.CharField(label="Opis:",max_length=80,widget=forms.TextInput({'size':80}))
    bedrooms = forms.IntegerField(label="Sypialnie:",min_value=1,max_value=4)
    beds = forms.IntegerField(label="Miejsca:",min_value=1,max_value=12)
    kitchenette = forms.BooleanField(label="Aneks kuchenny:",required=False)
    file = forms.FileField(label="Zdjęcie:",required=False)
Nowością jest pole typu FileField pozwalające uruchamić standardowe okno dialogowe wyboru pliku.

Tworzymy plik edition/apartment_edition.html:
{% extends "base.html"%}
{% block title %} - edycja opisu apartamentu{% endblock %}
{% block content%}
<center>
<h2>Edycja opisu apartamentu</h2>
<p>
<div style="margin-left:35%;margin-right:35%;">
<fieldset>
  <form enctype="multipart/form-data" method="post" action=".">{% csrf_token %}
    <div style="text-align:left";>{{ form.as_ul }}</div>
    <input type="hidden" name="id" value="{{ apartment.id }}" />
    <input type="submit" value="Zapisanie"/>    <input type="reset" value="Wartości dotychczasowe"/>
  </form>
</fieldset>
</div>
</center>  
{% endblock %}
Nowością jest fragment enctype="multipart/form-data", który zapewnia, że żądanie request zawiera słownik request.FILES z informacją o wybranym pliku. Fragment {{ form.as_ul }} powoduje, że formularz jest wyświetlany w postaci listy nienumerowanej.
Piszemy w pliku views.py funkcję save_apartment_page:
@permission_required('user.is_superuser',"/login/")    
def save_apartment_page(request):
    if request.method == 'POST':
        form = FormularzEdycjiApartamentu(request.POST,request.FILES)
        if form.is_valid():
            id = request.POST['id']
            if len(id) == 0:
                apartment = Apartment() 
            else:
                apartment = Apartment.objects.get(id=int(id))
            apartment.name = form.cleaned_data['name']
            apartment.kitchenette = form.cleaned_data['kitchenette']
            apartment.bedrooms = form.cleaned_data['bedrooms']
            apartment.beds = form.cleaned_data['beds']
            if request.FILES:
                f = request.FILES['file']
                destination = open("media/"+str(f),'wb+')
                for chunk in f.chunks():
                    destination.write(chunk)
                destination.close()
                apartment.pathToPicture = str(f)
            apartment.save()
            template = get_template("admin.html")
            apartments = Apartment.objects.all()
            types = TypCeny.objects.all()
            variables = RequestContext(request,{'apartments':apartments,'types':types})                
            output = template.render(variables)            
            return HttpResponse(output)                     
    elif request.GET.has_key('id'):
        ap_type = int(request.GET['id'])
        apartment = Apartment.objects.get(id=ap_type)
        form = FormularzEdycjiApartamentu({'name':apartment.name,'bedrooms':apartment.bedrooms,'beds':apartment.beds,'kitchenette':apartment.kitchenette})
        template = get_template("edition/apartment_edition.html")
        variables = RequestContext(request,{'form':form,'apartment':apartment})
        output = template.render(variables)
        return HttpResponse(output)        
    else:
        form = FormularzEdycjiApartamentu({'kitchenette':True})
    template = get_template("edition/apartment_edition.html")
    variables = RequestContext(request,{'form':form})
    output = template.render(variables)
    return HttpResponse(output)
Pogrubiony fragment sprawdza czy użytkownik wskazał plik, jeśli tak, to wskazany plik jest kopiowany na serwer, a informacja o nazwie pliku jest zapamiętywana w tabeli Apartment.

Spis treści

Podgląd rezerwacji

Administrator będzie miał możliwość zobaczenia aktualnego stanu rezerwacji. W tym celu:
  1. dodamy odsyłacze w pliku admin.html
    <h2>Lista apartamentów</h2>
    <ul>
      <hr width="50%" align="left">
      {% for apartment in apartments %}
      <li style="list-style-type:none;width:50%;">{{apartment.name}}<div id="nav">
          <a href="/save_apartment/?id={{apartment.id}}">Edycja</a>    <a href="/all_prices/{{apartment.id}}">Cennik</a>
          <a href="/one_room/1?id={{apartment.id}}">Rezerwacje</a></div>
          <br>Sypialnie: {{apartment.bedrooms}}
          ...
      {% endfor %}
      <li style="list-style-type:none;width:50%;">
        <div id="nav"><a href="/all_rooms/1">Rezerwacje zbiorczo</a></div>
        <br>
        <hr width="100%" align="left">
      </li>
  2. dopiszemy mapowania w pliku urls.py
    (r'^one_room/(\w+)$',one_room_page),
    (r'^all_rooms/(\w+)$',all_rooms_page),
    użycie wyrażenia (\w+) powoduje, że do funkcji one_room_page oraz all_rooms_page przekazane zostaną dwa parametry: request i drugi o dowolnej nazwie oraz wartości "1".
  3. napiszemy (w pliku views.py dwie funkcje one_room_page oraz all_rooms_page,
  4. utworzymy (w katalogu templates odpowiednie pliki HTML.
Funkcje i pliki z punktów 3 i 4 są dość podobne, opiszę tylko funkcję all_rooms_page i plik show_all_rooms.html.
Plik show_all_rooms.html:
{% block title %} - rezerwacje {% endblock %}
{% block content%}
<center>
<h2>Wszystkie apartamenty - rezerwacje w miesiącu {{ month }}.{{ year }}</h2>
<hr>
<table width="100%" border="1px">
  <tr>
    <th width="14%" align="center">Poniedziałek</th>
    <th width="14%" align="center">Wtorek</th>
    <th width="14%" align="center">Środa</th>
    <th width="14%" align="center">Czwartek</th>
    <th width="14%" align="center">Piątek</th>
    <th width="14%" align="center">Sobota</th>
    <th width="14%" align="center">Niedziela</th>
  </tr>
  {% for week in weeks %}
    <tr>
      {% for day in week %}
        <td style="text-align:center;" bgcolor="{{ day.color }}">{{ day.header}}<br><br>{{day.content}}<br><br></td>
      {% endfor %}  
    </tr>
  {% endfor %}
</table>
</center>
<hr>
<table border="1px" width="50%">
  <caption>Znaczenie kolorów i liczb</caption>
  <tr>
    <td bgcolor="#fdf5e5" width="33%" align="center">Wszystkie wolne<br>Ilość wolnych</td>
    <td bgcolor="#7ae68e" width="33%" align="center">Niektóre wolne<br>Ilość wolnych</td>
    <td bgcolor="#ff0000" width="33%" align="center">Wszystkie zarezerwowane</td>
  </tr>
</table>  
{% endblock %}
{% block footer %}
<div style="text-align:center;">{% if admin %}<a href="/admin/">Strona administratora</a>{% else %}<a href="/">Główna strona</a>{% endif %}
    <a href="/all_rooms/{% if admin %}1{%else%}0{%endif%}?&month={{next_month}}&year={{next_year}}">Następny miesiąc</a>
    <a href="/all_rooms/{% if admin %}1{%else%}0{%endif%}?&month={{prev_month}}&year={{prev_year}}">Poprzedni miesiąc</a>
</div>
{% endblock %}
Funkcja renderująca ten plik musi otrzymać mnóstwo informacji, część jest prosta (aktualny miesiąc i rok, następny miesiąc i rok, poprzedni miesiąc i rok, czy wejście nastąpiło ze strony administratora), część dosyć skomplikowna. Lista weeks jest listą list, wewnętrzne listy zawierają po siedem obiektów typu Day. Prosta klasa Day nie należy do bibliotek Pythona i Django, została napisana na potrzeby tego programu.
Efekt renderowania wygląda tak:

Poniższy kod musi się znaleźć w pliku views.py:
Ze względu na operacje na datach (dodawanie, formatowanie, parsowanie napisów) konieczny jest import:

from datetime import *
Potrzebne będą cztery funkcje pomocnicze: dwie które dla pary liczb (miesiąc i rok) zwracają dwuelementową krotkę: miesiąc i rok następny lub miesiąc i rok poprzedni, jedna, która dla tych samych argumentów zwraca obiekt typu date reprezentujący ostatni poprzedzający poniedziałek i jedna, która zwraca datę pierwszej niedzieli po:
 
def next_month_year(month,year):
    if month == 12:
        return (1,year+1)
    else:
        return (month+1,year)
        
def prev_month_year(month,year):
    if month == 1:
        return (12,year-1)
    else:
        return (month-1,year)
		
def last_monday(month,year):
    d = date(year,month,1)
    while d.weekday()>0:
        d-=timedelta(days=1)
    return d
        
def first_sunday(month,year):
    next = next_month_year(month,year)
    d = date(next[1],next[0],1)
    while d.weekday()<6:
        d+=timedelta(days=1)
    return d
Dwie ostatnie funkcje korzystają ze standardowej klasy date z modułu datetime oraz z metody timedelta z tego samego modułu pozwalającej dodawać liczby całkowite do daty.
Wspomniana już klasa Day:
class Day:
    def __init__(self,date,color="#fdf5e5",content=""):
        self.date = date
        self.content = content
        self.color = color
        self.header = date.strftime('%d.%m.%y')
Służy tylko do zebrania informacji o konkretnym dniu, funkcja date.strftime z modułu datetime formatuje datę do postaci '27.07.12'.
def all_rooms_page(request,args):
    year = date.today().year
    month = date.today().month    
    if request.GET.has_key('year'):
        year = int(request.GET['year'])
    if request.GET.has_key('month'):
        month = int(request.GET['month'])
    admin = (args == '1')        
    next = next_month_year(month,year)
    prev = prev_month_year(month,year)
    weeks = []
    week =[]
    day = last_monday(month,year)
    last_day = first_sunday(month,year)
    number_of_rooms = Apartment.objects.count()
    while day<=last_day:
        bookings = Booking.objects.filter(date=day)
        if len(bookings) == 0:
            week.append(Day(day,content=str(number_of_rooms)))
        elif len(bookings) == number_of_rooms:
            week.append(Day(day,color="#ff0000"))
        else:        
            week.append(Day(day,color="#7ae68e",content=str(number_of_rooms-len(bookings))))
        if len(week) == 7:
            weeks.append(week)
            week = []
        day+=timedelta(days=1)
    template = get_template("show_all_rooms.html")
    variables = RequestContext(request,{'month':month,'year':year,'next_month':str(next[0]),'next_year':str(next[1]),'prev_month':str(prev[0]),'prev_year':str(prev[1]),'weeks':weeks,'admin':admin})
    output = template.render(variables)
    return HttpResponse(output)
Metoda date.today() zwraca aktualna datę. Zatem jeżeli administrator wchodzi na tę stronę bezpośrednio ze "swojej" strony to słownik request.GET jest pusty, zatem wyświetlane są rezerwacje z bieżącego miesiąca. W przeciwnym przypadku, informacja o miesiącu (i roku) jest zawarta w słowniku request.GET.
Metoda Apartment.objects.count() zwraca ilość rekordów w tabeli, w naszym przypadku jest to ilość apartamentów.
Metoda Booking.objects.filter(date=day) zwraca listę obiektów, odpowiada ona poleceniu SQL
SELECT * FROM Booking WHERE date = day
. Długość tej listy, to ilość apartamentów zarezerowanych w dniu day. Struktura tabeli Booking zostanie omówiona na następnym wykładzie.
Spis treści
Poprzedni wykład     Lista wykładów     Następny wykład