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
urlpatterns = patterns('',
(r'^$',main_page),
(r'^register/$',register_page),
(r'^media/(?P.*)$','django.views.static.serve',{'document_root': media_dir}),
(r'^admin/$',admin_page),
...
# _*_ coding: utf-8 _*_
from django.http import HttpResponse,HttpResponseRedirect
...
from bookings.models import Apartment,User,Cena,TypCeny
from bookings.forms import *
...
@permission_required('user.is_superuser',"/login/")
def admin_page(request):
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)Powyższy kod zawiera dwie nowe tabele Cena i TypCeny, zostaną one opisane w
następnym rozdziale.
{% extends "base.html"%}
{% block title %} - strona administratora{% endblock %}
{% block content%}
<p>
<div style="text-align:center;"><a href="/logout">Wylogowanie</a></div>
</p>
<p>
<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>
</p>
<hr>
<p>
<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>
<hr>
<p>
<h2>Zarejestrowani użytkownicy</h2>
</p>
{% endblock %}
{% block footer %}
<p>
<div style="text-align:center;"><a href="/logout">Wylogowanie</a></div>
</p>
{% endblock %}
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).
{% 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:
<a href="/prices/?id={{apartment.id}}">Cennik</a>(r'^prices/$',prices_page),
def prices_page(request):
id_app = request.GET['id']
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)
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.
{% 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:
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.
def __init__(self,dictionary,control):
forms.Form.__init__(self,dictionary)
self.control = controlKażdy fomularz dziedziczący po forms.Form ma konstruktor jednoargumentowy. Tym argumentem jest słownik. Powyższy konstruktor
ma dodatkowy argument logiczny control decydujący czy funkcja clean_description wykonuje jakiekolwiek sprawdzanie.
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.
@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:
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.
class Cena(models.Model):
price = models.SmallIntegerField()
type = models.ForeignKey(TypCeny)
apartment = models.ForeignKey(Apartment)musimy jakoś zdobyć informacje, że powstały pola o nazwach type_id oraz apartment_id,
for typ in types:
try:
typ.price = Cena.objects.select_related().get(apartment=id_app,type=typ.id).price
except ObjectDoesNotExist:
typ.price = 0wykorzystuje możliwość dynamicznej zmiany struktury obiektu. Zmienna typ jest typu TypCeny, a w tej klasie nie
ma pola price.
<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.
{% 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.
@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.
<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>
(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".
{% 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.
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 dDwie 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.
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.
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.