Zarządzanie użytkownikami Rezerwowanie noclegów Źródła opisywanego programu
def admin_page(request): template = get_template("admin.html") apartments = Apartment.objects.all() types = TypCeny.objects.all() users = User.objects.order_by('username') if request.method == 'POST' and request.POST.has_key('first'): first = int(request.POST['first']) if request.POST.has_key('back'): first = first - 10 if request.POST.has_key('forward'): first = first + 10 else: first = 0 variables = RequestContext(request,{'apartments':apartments,'types':types,'users':users[first:first+10],'number_of_users':len(users),'first':first,'end':(first+10>=len(users))}) output = template.render(variables) return HttpResponse(output)Metoda User.objects.order_by('username') zwraca listę wszystkich użytkowników uporządkowana wg wartości pola username, do funkcji renderującej przekazywany jest tylko 10-elementowy fragment tej listy ('users':users[first:first+10]), przekazywana jest również informacja czy jest to początkowy fragment (klucz 'first' w słowniku) i czy jest to końcowy fragment (klucz 'end'). Wartość klucza 'first' jest numeryczna, wartośc klucza klucz 'end' jest logiczna. Ta różnica nie ma to znaczenia ze względu na niejawne konwersje int => bool.
Fragment pliku admin.html odpowiedzialny za wyświetlanie użytkowników i (warunkowe) umieszczenie przycisków Wcześniejsi i Następni:
<h2>Zarejestrowani użytkownicy ({{number_of_users}})</h2> <ul> {% if first %}<form style="display:inline;" method="post" action="/admin/">{% csrf_token %}<button type="submit" value="">Wcześniejsi<br><img src="/media/back.gif" width="16px"></button> <input type="hidden" name="back"><input type="hidden" name="first" value="{{first}}"></form>{% endif %} <hr width="50%" align="left"> {% for user in users %} <li style="list-style-type:none;width:50%;"> <form style="display:inline;"><input type="checkbox" disabled {% if user.is_superuser %}checked="checked"{% endif %}></form> {{user.username}} {{user.email}} <div id="nav"><a href="/save_user/{{user.id}}">Edycja</a> {% if not user.is_superuser %} <a href="/delete_user/{{user.id}}">Usunięcie</a>{% endif %} {% if user.email %} <a href="/send_mail/?id={{user.id}}">Wysłanie maila</a>{% endif %}</div> </li> {% endfor %} <hr width="50%" align="left"> {% if not end %}<form style="display:inline;" method="post" action=".">{% csrf_token %}<button type="submit" value="">Następni<br><img src="/media/forward.gif" width="16px"></button> <input type="hidden" name="forward"><input type="hidden" name="first" value="{{first}}"></form>{% endif %} </ul>Niemożliwe jest usunięcie administratora, wysłanie maila jest możliwe tylko wtedy gdy użytkownik podał adres pocztowy.
@permission_required('user.is_superuser',"/login/") def delete_user_page(request,args): if request.method == 'POST': ... else: id = args user = User.objects.get(id=id) day = date.today() number_of_bookings = Booking.objects.filter(who=user,date__gte=day).count() template = get_template("edition/user_delete.html") variables = RequestContext(request,{'user':user,'bookings':number_of_bookings}) output = template.render(variables) return HttpResponse(output)Pogrubiony wiersz odpowiada takiemu zapytaniu SQL:
SELECT COUNT(*) FROM Booking WHERE who = user AND date >= dayPrzy budowaniu filtru możemy sie posługiwać zmienionymi nazwami kolumn, zmiana polega na dopisaniu podwójnego znaku podkreślenia ('__') oraz pewnego słowa kluczowego. Pełną liste słów kluczowych można znaleźć tutaj (gte = greater than or equal to).
Trochę dokładniej opiszemy wysyłanie maila.
(r'^send_mail/$',send_mail_page)
class FormularzPocztowy(forms.Form): subject = forms.CharField(label="Temat (min. 10 znaków):",min_length=10,max_length=100,widget=forms.TextInput({'size':100})) body = forms.CharField(label="Treść:",widget=forms.Textarea({'rows':10,'cols':60}))
{% extends "base.html"%} {% block title %} - {{user.username}}{% endblock %} {% block header %}<h2><center>Administrator: {{sender.username}} - wysyłanie maila do: {{recipient.username}}</center></h2>{% endblock %} {% block content%} <center> <div style="margin-left:25%;margin-right:25%;"> <fieldset> <form method="post" action=".">{% csrf_token %} <div style="text-align:left";>{{ form.as_p }}</div> <input type="hidden" name="recipient" value="{{ recipient.id }}" /> <input type="hidden" name="sender" value="{{ sender.id }}" /> <input type="submit" value="Wysłanie"/> <input type="reset" value="Wartości domyślne"/> </form> </fieldset> </div> </center> {% endblock %} {% block footer%} <p> <div style="text-align:center;"><a href="/admin">Strona administratora</a></div> </p> {% endblock %}
from django.core.mail import send_mail,BadHeaderErrorPiszemy funkcję send_mail_page:
@permission_required('user.is_superuser',"/login/") def send_mail_page(request): template = get_template("send_mail.html") if request.method == 'POST': id = request.POST['sender'] sender = User.objects.get(id=id) id = request.POST['recipient'] recipient = User.objects.get(id=id) form = FormularzPocztowy(request.POST) if form.is_valid(): try: send_mail(form.cleaned_data['subject'],form.cleaned_data['body'],sender.email,[recipient.email]) return HttpResponseRedirect("/admin/") except BadHeaderError: pass else: id = request.GET['id'] user = User.objects.get(id=id) form = FormularzPocztowy() variables = RequestContext(request,{'form':form,'recipient':user}) output = template.render(variables) return HttpResponse(output)Wysłanie maila, to jeden wiersz w kodzie (plus wymagana obsługa błędów). Kolejne argumenty funkcji send_mail, to temat maila, jego treść, adres nadawcy, lista adresów odbiorców. W powyższym przykładzie, lista odbiorców jest jednoelementowa.
Zaczniemy od utworzenia nowej tabeli Booking. Wpierw opisujemy tabelę w pliku models.py:
class Booking(models.Model): apartment = models.ForeignKey(Apartment) who = models.ForeignKey(User) date = models.DateField(db_index=True) def __str__(self): return "Data: "+self.date.strftime('%d.%m.%y')+" Id rezerwującego: "+str(self.who_id)+" Id apartamentu: "+str(self.apartment_id)a potem otwieramy konsolę i wpisujemy polecenia:
python manage.py syncdb #utworzona zostanie nowa tabela python manage.py sqlindexes bookings #w tabeli zostaną utworzone indeksyParametr db_index=True powoduje, że zostanie utworzony indeks na pole date. Pozostałe czynności są dosyć standardowe.
(r'^booking/(\w+)$',booking_page),
class FormularzRezerwacji(forms.Form): from_day = forms.DateField(label="Od dnia (format dd.mm.rr):",input_formats=['%d.%m.%y'],widget=forms.DateInput(format='%d.%m.%y')) to_day = forms.DateField(label="Do dnia (format dd.mm.rr):",input_formats=['%d.%m.%y'],widget=forms.DateInput(format='%d.%m.%y')) def clean_to_day(self): from_day = self.cleaned_data['from_day'] to_day = self.cleaned_data['to_day'] if from_day<=to_day: return to_day raise forms.ValidationError("Koniec jest wcześniejszy niż początek")
{% extends "base.html" %} {% block title %} - rezerwowanie noclegów {% endblock %} {% block content%} <center> <h2>Apartament: {{ apartment.name }} - rezerwowanie noclegów</h2> <p> <div style="margin-left:30%;margin-right:30%;"> <fieldset> <form method="post" action="/booking/{{ apartment.id }}?id={{id}}">{% csrf_token %} <div style="text-align:left";>{{ form.as_p }}</div> <input type="hidden" name="id" value="{{ apartment.id }}" /> <input type="submit" value="Rezerwacja"/> </form> </fieldset> </div> {% endblock %} {% block footer %} <div style="text-align:center;"><a href="/">Główna strona</a> </div> {% endblock %}
def free_room(apartment,first,last): day = first while day<=last: try: Booking.objects.get(apartment=apartment,date=day) return False except ObjectDoesNotExist: day+=timedelta(days=1) return TruePiszemy funkcję save_new_booking zapisującą nową rezerwację. Każda rezerwacja tworzy tyle zapisów w tabeli Booking ile jest w dni we wskazanym okresie.
def save_new_booking(apartment,first,last,user): day = first while day<=last: ap = Apartment.objects.get(id=apartment) us = User.objects.get(id=user) booking = Booking.objects.create(apartment=ap,date=day,who=us) booking.save() day+=timedelta(days=1) return TrueObecna wersja tej funkcji jest mocno niedoskonała, nie ma żadnego sprawdzania czy zapis sie powiódł.
@login_required def booking_page(request,args): id = int(args) id_user = int(request.GET['id']) if request.method == 'POST': form = FormularzRezerwacji(request.POST) if form.is_valid(): first_day = datetime.strptime(request.POST['from_day'],'%d.%m.%y') last_day = datetime.strptime(request.POST['to_day'],'%d.%m.%y') if free_room(id,first_day,last_day): if save_new_booking(id,first_day,last_day,id_user): template = get_template("edition/booking_success.html") variables = RequestContext(request) output = template.render(variables) return HttpResponse(output) else: apartment = Apartment.objects.get(id=id) template = get_template("edition/booking_failure.html") variables = RequestContext(request,{'apartment':apartment,'id':id_user,'from':request.POST['from_day'],'to':request.POST['to_day']}) output = template.render(variables) return HttpResponse(output) else: form = FormularzRezerwacji() apartment = Apartment.objects.get(id=id) template = get_template("edition/booking.html") variables = RequestContext(request,{'apartment':apartment,'form':form,'id':id_user}) output = template.render(variables) return HttpResponse(output)
python manage.py createsuperuser python manage.py runserverMerytorycznie kolejność tych poleceń nie ma znaczenia, podana wyżej jest wygodniejsza - nie wymaga dwukrotnego otwierania konsoli. Pierwsze z tych poleceń dodaje do tabeli User administratora, bez dodania administratora program też będzie działał.
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'pensjonat', 'USER': 'postgres', 'PORT': 5432, 'PASSWORD': '***', 'HOST':'', } }Jeżeli podczas instalacji programu Postgre zostaną zaakceptowane paramtery domyślne, to wystarczy zmienić hasło.
python manage.py syncdbZostaną utworzone wymagane tabele, na końcu padnie pytanie czy utworzyć administratora (superuser w terminologii Django). Odpowiadamy twierdząco. Wpisujemy polecenie:
python manage.py runserver