воскресенье, 17 июня 2007 г.

Пишем Tv программу на Django. Часть 3

как обычно код этого поста тут: http://tvwatcher.googlecode.com/svn/tags/post3

Наконец пришло время создать view. Вид. То, что будет видеть обычный пользователь когда зайдёт к нам на страничку.

В самом начале я задумывался о xml+xslt, но немножко подумав как это реализовывать пришёл к выводу, что в таком случае я потеряю очень мощный инструмент django. Его шаблоны. Поэтому будем делать на обыкновенном html4.

Первым делом я открываю Gimp и рисую макет будущей страницы... (чёрт, похоже я его уже потерял, вобщем поверьте на слово я его нарисовал)

Затем режу эту картинку и верстаю тестовую страничку. Привожу к виду как на картинке.

В результате оно стало выглядеть примерно так:



Да, не очень красиво. Особенно цвета. Ну да ладно...

Теперь нужно выделить основной код который будет повторяться на всех страницах. Это будет наш base.html, который мы закинем в ownproject/templates/tv/.

Если вы уже ознакомились с официальным туториалом, то знаете, что в шаблонах у джанго свой мини язык. Вот пользуясь им и создаём наш base.html

У меня получилась вот такая штука:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>

<head>

<title>{% block title %}Tv программа{% endblock %}</title>

<script type="text/javascript" src="/media/tv/css.js"></script>

<meta http-equiv="content-type" content="text/html; charset=UTF-8">

<link href='/media/tv.css' type='text/css' rel='stylesheet'>

</head>

<body>



<div id="upperbar">

    {% block upbar %}

    <div id="menu">{% if user %}

            <a href="">Настройки</a> | <a href="/tv/logout/">Выход</a>

        {% else %}

            <a href="/tv/login/?next=/tv">Войти</a> |

            <a href="/tv/registration">Регистрация</a>

        {% endif %}</div>

    <div id="hello">Здравствуйте, {% if user %}

        {{ user.username }}!

        {% else %}

        Гость!

        {% endif %} | {% now "d F Y H:i" %}</div>

    {% endblock %}

</div>



<div id="upmain">

    <h1 id="logo">Tv программа</h1>

</div>



{% block week %}

    {% load menu_ex %}

    {% menu display date chanal %}

{% endblock %}



    {% block content %}{% endblock %}

</div>



</body>

</html>>


Как видно в теге title написано {% block title %}Tv программа{% endblock %}. Это значит, что по умолчанию название страницы будет Tv программа, но мы можем его изменять в любом наследующем шаблоне, переопределив block title. То же самое происходит с upbar, week и content.

Так же на каждой странице будет отображаться верхний блок с днями недели и тп. Чтобы не таскать одно и то же из функции в функцию в view, я вынес их в отдельный шаблонный тег.

Для этого создаём папочку ownproject/tv/templatetags. В ней пустой файл __init__.py и menu_ex.py который собственно и является нашим тегом. Ничего особо интересного там нет. Так что кому интересно смотрим в svn. (вообще туда заглядывать просто обязательно! в самих постах очень многое пропущенно) Единственно что стоит отметить, это то как сказать django что это именно тег. Делается это примерно так:
from django import template
register = template.Library()
def menu(display=0, date=None, chanal=None):
...
register.inclusion_tag('tv/menu.html')(menu)


Таблица стилей и яваскрипт не случайно положены в папку media. Дело в том, что встроенный веб сервер django не умеет просто так отдавать статику. Поэтому все картинки, js, css и тп обычно скидывается в какую-нибудь папку, например ownproject/media. Затем открывается ownproject/urls.py и дописывается такая строчка:
(r'^media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),


В ownproject/settings.py определяем переменную MEDIA_ROOT. В моём случае:
MEDIA_ROOT = '/mnt/H/My Developement/django/mysite/media/'


И если директория была названа media, то необходимо изменить ADMIN_MEDIA_PREFIX, например на amedia. (всё это в том же файле settings)

Ну вот. Теперь можно попробовать начать создавать нашу главную страницу.
Создадим файл /ownproject/tv/urls.py и пропишем там:

from django.conf.urls.defaults import *
from django.conf import settings
from tv import views

urlpatterns = patterns('',
(r'^$', views.index),
}


Отлично. Теперь при попытке зайти на http://127.0.0.1:8000/tv будет вызываться функция index из ownproject/tv/views.py. Правда её ещё нет. :)

Займёмся её написанием... Хотя нет. Стоп! Что такое главная страница? Это телепрограмма на сегодня. То есть это то же самое, что и по ссылкам Пн, Вт, Ср, Чт и тд. Только дата устанавливается автоматически. Логично было бы написать функцию которая будет работать с любой датой, а в index просто вызывать её с сегодняшней датой. Этим и займёмся.

def index(request):
return day(request, time.strftime("%Y-%m-%d"))


Нарисуем шаблон list.html, который будет выводить каналы.



def day(request, date):
if request.user.is_authenticated(): # Проверяем авторизирован ли юзер
user=request.user
setting = user.tv_Setting
display = tv_Setting.Mode # режим отображения берём из настроек
chanals = []
for place in setting.Chanals.order_by('N'): # Извлекаем из базы "места" сортираю по его номеру
chanals.append(place.Chanal) # и добавляем в массив связанные с ними каналы
else: # иначе юзер у нас None, а каналы просто первые четыре штуки
user = None
chanals = Chanal.objects.all()[:4]
display = request.GET.has_key("allday") # режим отображание смотрим по тому есть ли в адресе переменная allday
for chanal in chanals: # для каждого канала необходимо вызвать функцию get_chanal, чтобы добавить в них информацию из других таблиц
chanal = get_chanal(display, date, chanal)
c = { # именнованный массив который мы отдаём в шаблон
"chanals": chanals,
"date": date,
"display": display,
"user": user,
"chanal": None,
}
return render_to_response('tv/list.html', c)


Ну и посмотрим на ownproject/templates/tv/list.html. Там тоже ничего сложного:
{% extends "tv/base.html" %}



{% block content %}

    {% if user %}

        <div id="addchanal"><a href="/tv/addchanals">Добавить канал</a></div>

    {% endif %}



    {% if chanals %}

        {% for chanal in chanals %}

            <div class="chanal">

                {% if user %}

                    <div class="actions"><a href="/tv/delete/{{ chanal.id }}"><img src="/media/tv/icon_deletelink.gif" alt="удалить"></a></div>

                {% endif %}

                {% if chanal.Icon %}

                <img src="{{ chanal.Icon }}">

                {% endif %}

                <h2><a href="/tv/{{ chanal.Slug }}">{{ chanal.Title }}</a></h2>

                <ul>

                    {% if chanal.programs %}

                        {% for program in chanal.programs %}

                        <li>{{ program.Time }}

                        {% if program.description %}

                            <a href="/tv/description/{{ program.description.id }}">{{ program.Title }}</a>

                        {% else %}

                            {{ program.Title }}

                        {% endif %}

                        </li>

                        {% endfor %}

                    {% else %}

                        Нет телепрограмм

                    {% endif %}

                </ul>

            </div>

        {% endfor %}

    {% else %}

        Нет каналов.

    {% endif %}

    <script>

    checkBrowserWidth();

    </script>

{% endblock %}


Говорим, что мы потомок base.html и переопределяем block content.
Скрипт в конце необходим чтобы правельно выводить блоки с каналами.
Здесь вообще довольно интересная история произошла. Изначально я нарисовал 2 колонки с каналами и каналы в своих колонках расположены один под другим. Вообщем получается как в той же бумажной телепрограмки. И вроде всё было хорошо, пока я не растянул свой браузер на всю ширину монитора (1280px). появилось просто ужасающее колличество свободного места. Тогда я все каналы сделал float:left, и стал выводить... Но конечно же получилось очень страшно, т.к у них у всех различная высота. Думал, думал, думал чего делать. Но так ничего лучше яваскрипта не придумал. Он смотрит на ширину окна браузера и в зависимости от него каждому Nому каналу ставит свойство clear: left. В результате получилось не совсем так как я хотел, но вроде более или менее нормально.

И так вернёмся к нашим баранам. Выделив функцию day, мы автоматически создали обработчик других дней. Поэтому дописываем в urls
(r'^(?P\d{4}-\d{2}-\d{2})/$', views.day),


Теперь можно перейти на сайт и потыкать по ссылкам. Оно должно работать. :)

Создание страничек с каждым каналом в отдельности и описанием тоже не представляют ничего интересного, поэтому на них можно посмотреть в svn. С добавлением\удалением канала у юзера то же самое. Поэтому и смотрим там же.

Теперь нужно к этому всему подключить пользователей. Как видно я уже учёл различное поведение для анонимного пользователя и авторизированного в виде и шаблонах. Поэтому осталось только сделать механизм входа\выхода\регистрации. Так как почти всё это в будующем будет заменено ajax'ом, то по возможности используем встроенные в django функции или элементарные формочки.

Для входа и выхода уже написаны полностью рабочие функции, осталось лишь прописать их в urls.
    (r'^login/$', 'django.contrib.auth.views.login'),
(r'^logout/$', 'django.contrib.auth.views.logout'),


А вот регистрации готовой нет, поэтому набрасаем простенькую формочку в шаблоне.
Django освобождает нас от необходимости прописывать бесмысленные функции в view, которые занимаются только тем, что рендерят шаблон. Вместо этого используем django.views.generic.simple.direct_to_template.
В urls пропишем:
    (r'^registration/$', 'django.views.generic.simple.direct_to_template', {'template': 'tv/registration.html'}),


Форма после заполнения будет вызывать функцию createuser: (r'^createuser/$', views.createuser),

Она опять же очень эллементарна. Почти всё за нас делает django. Импортируем User из django.contrib.auth.models и authenticate, login из django.contrib.auth.

user = User.objects.create_user(request.POST['username'], '', request.POST['password']) # создаём пользователя
user.save() # сохраняем его
user = authenticate(username=request.POST['username'], password=request.POST['password'])
login(request, user) # сразу же авторизируем
return HttpResponseRedirect('/tv/') # и кидаем на главную


Ну вот наверное и всё. В результате мы получили почти рабочий сайт с телепрограммой.