Django Essentials.

Стаття - додаток до доповіді “Django Essentials” за 11.03.2014 (Просимо вибачення за недосконалий вигляд статті на сайті, вона ще буду редагуватися найблищим часом, дякуємо за розуміння) Трошки кращу версію цієї статті ви можете переглянути тут - https://docs.google.com/document/d/1saXb6ZiXhQFQaMhLrnWTgiCN8QDreiQxnjHLOFRTgLg/edit?usp=sharing


Проект - створення персонального менеджера фільмів (для особистого використання). Функціонал: додати фільм; позначити, як переглянутий чи улюблений; оцінити фільм; переглянути список фільмів, розділених по статусу.

Створення та налаштування проекту.

Створюємо проект :

  1. Відкриваємо консоль.
  2. Вводимо наступну команду:

django-admin.py startproject mManager - результат її виконання - проект з наступною структурою:

  • manage.py - утиліта для взаємодії з проектом (дозволяє, наприклад, запустити сервер, синхронізувати базу даних та ін.) Детальніше - django-admin.py and manage.py(https://docs.djangoproject.com/en/1.6/ref/django-admin/).
  • mManager - внутрішня папка - папка проекту (проект може містити кілька пакетів, як створених самотужки так і завантажених з https://www.djangopackages.com/ або іншого джерела)
  • mManager/init.py - порожній файл, необхідний для ініціалізації проекту. Детальніше - http://docs.python.org/tutorial/modules.html#packages.
  • mManager/settings.py - налаштування проекту, як от часова зона (перелік можливих часових зон- http://en.wikipedia.org/wiki/List_of_tz_database_time_zones), мова, розташування папки для зберігання медіафайлів та ін. Детальніше - https://docs.djangoproject.com/en/1.6/topics/settings/.
  • mManager/urls.py - перелік адрес проекту. Детальніше - https://docs.djangoproject.com/en/1.6/topics/http/urls/.
  • mManager/wsgi.py - точка входу для WSGI-сумісного веб-сервера проекту. WSGI - це специфікація, що описує, як веб-сервер зв’язується з аплікаціями та як ці аплікації можуть бути привязані до обробки одного запиту. Детальніше можна прочитати у статті - https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/.
  1. Перевіримо чи все працює.
    1. - заходимо в папку з файлом manage.py
    2. - запускаємо сервер командою :

python manage.py runserver

  - - результат виконання команди - адреса запущеного сервера і порта
  - - вводимо в браузері відповідну адресу
- Налаштування бази даних.
- створимо базу даних (mysql або якусь іншу) і користувача
- заходимо до файла налаштувань і вносимо туди відповідні корективи

5) Синхронізуємо базу даних із проектом:

python manage.py syncdb
результат виконання команди

6) Створюємо власну аплікацію. python manage.py startapp manager

результат виконання команди: manager/init.py - порожній файл, необхідний для ініціалізації пакету. Детальніше - packages. manager/models.py - модельний файт, тобто файл, який, фактично, визначає стуктуру бази даних аплікації і деякі додаткові метадані. manager/tests.py - файл, який використовується для написання автоматизованих тестів. manager/views.py - файл для реалізації функціоналу(наприклад, генерації сторінок, користувацьких методів) на мові python

7) Підключаємо створену аплікацію до проекту: 8) Вдосконалимо структуру проекту:

  1. додамо папку для статичних файлів, як от файли зі стилями та картинки
  2. додамо папку для збереження темплейтів, html сторінок

В кожній аплікації можуть бути свої статичні файли та темплейти, при співпадіння стилів для елементів чи назв темплейтів вищий пріоритет мають стилі та темплейти аплікації.

  1. додамо шляхи до створених папок в налаштуваннях проекту
  2. також в налаштуваннях модемо змінити автоматично встановлену часову зону, обравши відповідну з переліку наведеного вище
  3. перевіримо чи правильно зазначено розміщення адрес сайту, має бути так:
  4. решту налаштувань залишаємо без змін.

9) Починаємо створення моделі бази даних.

  1. створимо для нашого проекту два словники: зі статусами та із жанрами фільмів.
  2. створимо модель фільму(система на основі цієї моделі створить таблицю) з полями: назва фільму, рік виходу на екрани, жанр, статус, особиста оцінка та зірки, що зіграли у фільмі, якщо не зазначено ключового поля, то система додасть його автоматично.

from django.db import models MOVIES_GENRE = (

  (1, 'Action'),     (2, 'Adventure'),    (3, 'Biopic'),    (4, 'Comedy'),    (5, 'Crime'),    (6, 'Fantasy'),    (7, 'Historical'),    (8, 'Horror'),    (9, 'Mystery'),    (10, 'Philosophical'),    (11, 'Romance'),    (12, 'Saga'),    (13, 'Science fiction'),    (14, 'Thriller')

)

STATUS_CHOICE = (

  ('w', 'Watched'),    ('p', 'Will watch'),    ('f', 'Favourite')

)

class Movie(models.Model):

 name = models.CharField(max_length=255)
 genre = models.PositiveSmallIntegerField(blank=True, choices=MOVIES_GENRE, default=1)
  publish_year = models.IntegerField()
  status = models.CharField(max_length=1, choices=STATUS_CHOICE, default='1')
  personal_rate = models.PositiveSmallIntegerField(default=10)
  stars = models.CharField(max_length=255)

10) Створимо таблицю відповідно до моделі. python manage.py syncdb результат виконання : Щоб переглянути sql - запит на створення таблиці вводимо наступну команду: python manage.py sql manager результат виконання команди: Реалізація проекту. Для стилістичної реалізації вигляду нашого сервісу будемо використовувати бібліотеку Bootstrap. 11) Створимо сторінку для відображення фільмів користувача, вона буде стартовою. Додамо також адресу цієї сторінки до файла з адресами проекту. urlpatterns = patterns('manager.views',

                     url(r'^$', 'my_movies'),
	…

12) Додамо простий метод для відображення сторінки. Запустивши сервер і відкривши в браузері 127.0.0.1:8000/, побачимо порожню сторінку. 13) На сторінці з фільмами ми будемо відображати список усіх фільмів користувача, розділений за статусами, для цього розширимо наш метод. Але, перш за все, давайте додамо метод, який повертатиме об’єкт “Фільм” у певному представленні. Однією з ключових переваг Django є можливість легко підключити адмінську частину сайту. Для неї дуже важливим є представлення обєкта. І не дивлячись на те, що зараз ми її використовувати не будемо, метод для визначення представлення обєкта потрібно створити. Він також зробить об’єкти більш читабельними для нас. Для початку давайте подивимось як обєкт виглядає зараз. Створимо кілька обєктів за допомогою консолі системи. [vira.lytvyn@localhost mManager]$ python manage.py shell Python 2.7.5 (default, Nov 12 2013, 16:18:42) [GCC 4.8.2 20131017 (Red Hat 4.8.2-1)] on linux2 Type “help”, “copyright”, “credits” or “license” for more information. (InteractiveConsole)

from manager.models import Movie
Movie.objects.all()

[]

m = Movie(name=“Titanic”, genre=1, publish_year=1997, status=“w”, personal_rate=9, stars=“Leonardo DiCaprio, Kate Winslet, Billy Zane”)
m.save()
m.id

1L

m.name

'Titanic'

m2 = Movie(name=“Titanic”, genre=3, publish_year=1953, status=“p”, personal_rate=, stars=“Clifton Webb, Barbara Stanwyck, Robert Wagner”)
m2.save()
m2.id

2L

Movie.objects.all()

[<Movie: Movie object>, <Movie: Movie object>]

Ось як обєкт представлено зараз: “<Movie: Movie object>” У файлі models.py в класі Movie додаємо вбулований метод unicode(для Python 3 потрібно str ). def unicode(self):

      return str(self.name) + '_' + str(self.publish_year)

Він має повернути обєкт у форматі Ім’я_рік_виходу_на екран. Перевіримо це, видрукувавши створені раніше обєкти. Для цього імпортуємо у view.py модель Movie і видрукуємо всі обєкти цієї моделі в консоль, при завантаженні сторінки. from models import Movie у методі :

print Movie.objects.all()

Тепер обєкти виглядають так: [<Movie: Titanic_1997>, <Movie: Titanic_1953>] 14) Давайте профільтруємо всі фільми за статусом. Для цього допишемо до методу my_movies у файлі views.py наступне:

  watched_list = []
  will_watch_list = []
  favourite_list = []
  for movie in Movie.objects.all():
      if movie.status == 'w':
          watched_list.append(movie)
      elif movie.status == 'p':
          will_watch_list.append(movie)
      elif movie.status == 'f':
          favourite_list.append(movie)

Вивівши їх в консоль, зможемо переконитися, що фільтрування працює коректно. 15) Тепер перейдемо до відображення результатів на веб-сторінці.

Для початку створимо в папці static папку зі стилями. Помістимо в неї папку з бібліотекою bootstrap.
Застосуємо для сторінки з фільмами стандартний темплейт цієї бібліотеки. Підключимо бібліотеку до сторінки. Для відображення фільмів з різним статусом використаємо структуру списків. Заповнимо сторінку тестовими даними.

Ми створили сторінку такого вигляду: 16) Заповнимо сторінку реальними даними. Для цього передамо відфільтровані списки на сторінку: return render(request, “manager/my_movies.html”, {

      'watched': watched_list, 'will_watch': will_watch_list, 'favourite': favourite_list
  })

А в my_movie.html : <ul class=“list-group”>

                          {% for movie in watched %}
                              <li class="list-group-item">
                                  <a href="/movie/{{ movie.id }}">{{ movie.name }}</a>
                                  ({{ movie.publish_year }})
                                  <span class="badge"> {{ movie.personal_rate }}</span> </li>
                          {% endfor %}
                      </ul>

аналогічно для двох інших списків. Тепер на нашій сторінці відображаються реальні дані. 17) Давайте створимо сторінку для відображення детальних відомостей про кожен фільм. Django дозволяє використовувати вбудовані конструкції, як от ListView чи DetailView, який ми б могли використати для таких цілей. Але зараз ми не будемо це застосовувати. Створимо новий темплейт movie.html та додамо адресу в urls.py. А також створимо метод для генерації цієї сторінки у view.py urlpatterns = patterns('manager.views',

                     (r'^$', 'my_movies'),
                     (r'^movie/(?P<film_id>\d+)/$', 'view_movie'),

def view_movie(request, film_id):

  movie = Movie.objects.get(id=film_id)
  return render(request, "manager/movie.html", {'movie': movie})

В темплейті використаємо структуру jumbotron для центрування контенту, а тоді в панель помістимо структуру з вкладками, щоб в майбутньому забезпечити на цій сторінці зміну оцінки та статусу фільму. Детальну інформацію про фільм відобразимо у псевдоформі. Можна використати будь-яку іншу структуру за смаком розробника.

                              <br>
                              <div class="input-group">
                                  <span class="input-group-addon">Publish year</span>
                                  <div class="form-control">{{ movie.publish_year }}</div>
                              </div>
                              <div class="input-group">
                                  <span class="input-group-addon">Movie genre</span>
                                  <div class="form-control">{{ movie.genre }}</div>
                              </div>
                              <div class="input-group">
                                  <span class="input-group-addon">Movie status</span>
                                  <div class="form-control">{{ movie.status }}</div>
                              </div>
                              <div class="input-group">
                                  <span class="input-group-addon">Personal rate</span>
                                  <div class="form-control">{{ movie.personal_rate }}</div>
                              </div>
                              <div class="input-group">
                                  <span class="input-group-addon">Stars</span>
                                  <div class="form-control">{{ movie.stars }}</div>
                              </div>
                          </div>

Як бачимо, дані про статус та жанр фільму відображають якісь незрозумілі значення. Виправимо це наступним чином:

  1. додамо два методи, що повертають за ключем його значення: для статусу і жанру відповідно
  2. викличемо ці методи прямо у темплейті.

def get_status_name(self):

      for key, value in STATUS_CHOICE:
          if self.status == key:
              return value
      return 'No status'
  def get_genre_name(self):
      for key, value in MOVIES_GENRE:
          if self.genre == key:
              return value
      return 'Unknown genre'
                                  <span class="input-group-addon">Movie genre</span>
                                  <div class="form-control">{{ movie.get_genre_name }}</div>
                              </div>
                              <div class="input-group">
                                  <span class="input-group-addon">Movie status</span>
                                  <div class="form-control">{{ movie.get_status_name }}</div>
                              </div>

Тепер всі дані відображаються коректно і сторінка виглядає наступним чином. 18) Тепер, коли сторінка готова, додамо можливість змінювати свою думку про фільм, тобто власну оцінку. А також додамо можливість міняти статус фільму, якщо користувач його вже перелянув. Для цього додамо дві відповідних вкладки: Змінити оцінку та Змінити статус. А до методу view_movie додамо наступне: if request.method == 'POST':

      if 'rate_range' in request.POST:
          movie.personal_rate = request.POST['rate_range']
      elif 'new_status' in request.POST:
          movie.status = request.POST['new_status']
      movie.save()

Вкладка Змінити оцінку:

Код темплейту цієї вкладки:

    <br>
    <div class="panel panel-default">
           <div class="panel-heading">Personal rate for " {{ movie.name }} "</div>
           <div class="panel-body">
                 <form class="form-inline" role="form" name="change_personal_rate" method="POST">
                        <div class="form-group">
                              <input type="range" max="10" min="0" name="rate_range" style="display: inline-block"  value="{{ movie.personal_rate }}" oninput="new_range.value=this.value" >
                               <output name="new_range" for="rate_range" style="display: inline-block">{{ movie.personal_rate }}</output>
                         </div>
                          <button type="submit" class="btn btn-default">Rate</button>
                    </form>
              </div>
       </div>

Вкладка Змінити статус: Для того, щоб не потрібно було змінювати код цього темплейта щоразу, як ми змінимо кількість статусів чи їх назву, ми передамо сюди список всіх статусів і будуватимемо панель статусів динамічно. Для цього у view.py : from models import Movie, STATUS_CHOICE У view_movie: statuses = STATUS_CHOICE return render(request, “manager/movie.html”, {'movie': movie, 'status_choice': statuses}) Код темплейта цієї вкладки:

                              <br>
                              <div class="panel panel-default">
                                  <div class="panel-heading">Status of " {{ movie.name|title }} "</div>
                                  <div class="panel-body">
                                      <form class="form-inline" role="form" method="POST">
                                          <div class="btn-group" data-toggle="buttons">
                                              {% for key,value in status_choice %}
                                                   <label class="btn btn-default 

{% if movie.status == key %} active {% endif %}”>

                                                          <input type="radio" name="new_status" value="{{ key }}">

value

                                                   </label>
                                              {% endfor %}
                                          </div>
                                          <button type="submit" class="btn btn-default">Save status</button>
                                      </form>
                                  </div>
                              </div>
                          </div>

19) Як бачимо, в нас немає можливості просто перейти до списку усіх фільмів, щоб це зробити доводиться міняти адресу сторінки, що спричиняє додатковий дискомфорт. Доцільно було б зробити головне меню сайту, але на даному етапі ми обмежимось “хлібними крихтами”, оскільки поки не плануємо сильно розширювати функціонал цього сервісу. Отож додамо до кожної сторінки стандартні(з Bootstrap) “хлібні крихти”. На сторінці із усіма фільмами це виглядає так: А на сторінці конкретного фільму: Код таких “крихт” поміщених у структуру “well”:

              <ol class="breadcrumb">
                  <li><a href="/">Movies</a></li>
                  <li class="active">{{ movie.name|capfirst }}</li>
              </ol>
          </div>

20) Остання можливість, яку ми реалізуємо у цій лекції - додавання нового фільму до колекції. Додамо до нашого імпровізованого головного меню кнопку “Додати фільм”.

<a class=“btn btn-primary” href=”/add_movie”>Add movie</a>

Створимо новий темплейт із формою додавання фільму. Та додамо нову адресу до urls.py. (r'^add_movie/$', 'add_movie'), Темплейт складається з уже звичних “хлібних крихт” та форми. Форма є прикладом використання Bootstrap форм і кожен новий рядок оздоблений “addon” із одним чи кількома “glyphicon”. Для генерації цієї сторінки у view.py пишемо метод, який нам передаватиме в темплейт жанри фільму(це єдині дані з сервера, які нам потрібні, щоб намалювати цю сторінку). def add_movie(request):

  return render(request, "manager/add_movie.html", {'genres': MOVIES_GENRE})

Код сторінки з формою:

      <div class="well well-sm">
          <ol class="breadcrumb">
              <li><a href="/">Movies</a></li>
              <li class="active">New movie</li>
          </ol>
      </div>
      <div class="panel panel-default">
          <div class="panel-heading">Please, fill next fields to add new movie.</div>
          <div class="jumbotron" style="font-size: 14px;">
              <form role="form" name="add_movie_form" method="POST">
                  <br>
                  <div class="input-group">
                      <span class="input-group-addon"><span class="glyphicon glyphicon-film"></span></span>
                      <input type="text" class="form-control" placeholder="Film name" name="name">
                  </div>
                  <br>
                  <div class="input-group">
                      <span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
                      <input type="number" min="1920" max="2015" class="form-control" placeholder="year" name="publish_year">
                  </div>
                  <br>
                  <div class="input-group">
                      <span class="input-group-addon"><span class="glyphicon glyphicon-bookmark"></span></span>
                      <select class="form-control" name="genre">
                          {% for key, value in genres %}
                              <option value="{{ key }}">{{ value }}</option>
                          {% endfor %}
                      </select>
                  </div>
                  <br>
                  <div class="input-group">
                      <span class="input-group-addon">
                          <span class="glyphicon glyphicon-eye-open"></span>
                          <span class="glyphicon glyphicon-eye-close"></span>
                          <span class="glyphicon glyphicon-certificate"></span>
                      </span>
                      <div class="form-control">
                          <label class="radio-inline">
                              <input type="radio" name="status" value="w">Watched &nbsp;&nbsp;&nbsp;
                          </label>
                          <label class="radio-inline">
                              <input type="radio" name="status" value="p">Will watch &nbsp;&nbsp;&nbsp;
                          </label>
                          <label class="radio-inline">
                              <input type="radio" name="status" value="f">Favourite &nbsp;&nbsp;&nbsp;
                          </label>
                      </div>
                  </div>
                  <br>
                  <div class="input-group">
                      <span class="input-group-addon">
                          <span class="glyphicon glyphicon-thumbs-up"></span>
                          <span class="glyphicon glyphicon-thumbs-down"></span>
                      </span>
                      <div class="form-control">
                          <input type="range" max="10" min="0" name="rate" style="display: inline-block" oninput="range.value=this.value">
                          <output name="range" for="rate_range" style="display: inline-block">5</output>
                      </div>
                  </div>
                  <br>
                  <div class="input-group">
                      <span class="input-group-addon">

</span>

                      <textarea class="form-control" maxlength="255" rows="2" 

style=“resize: none;” name=“stars”></textarea>

                  </div><br>
                  <div class="input-group">
                      <span class="input-group-addon">

</span>

                      <div class="form-control disabled">Please, check if all fields is correct! </div>
                      <span class="input-group-btn">
                          <button class="btn btn-default" type="submit">Save</button>
                      </span>
                  </div>
              </form>
          </div>
      </div>
  </div>

Форма відправляє дані на сервер. З метою спрощення ми не додавали жодних перевірок, будемо вважати, що користувач дуже уважний і всі дані ввів правильно. Тепер доповнимо метод, щоб він додавав до бази новий фільм. Якщо зберегти фільм не вдасться, то ми побачимо помилку в консолі, для цього ми додатково підключили модуль sys.

def add_movie(request):

  if request.method == 'POST':
      try:
          new_movie = Movie(
              name=request.POST['name'],
              publish_year=request.POST['publish_year'],
              genre=request.POST['genre'],
              status=request.POST['status'],
              personal_rate=request.POST['rate'],
              stars=request.POST['stars']
          )
          new_movie.save()
          return HttpResponseRedirect('/')
      except:
          print "Error: Cannot save the movie - " + str(sys.exc_info()[0])
  else:
      return render(request, "manager/add_movie.html", {'genres': MOVIES_GENRE})

Якщо ж фільм буде збережено успішно, то користувача буде перенаправлено на сторінку з усіма фільмами. Думаю, доцільно буде виправити цей метод, щоб користувача перенаправляло безпосередньо на сторінку щойнододаного фільму. Зробимо це замінивши адресу сторінки, на яку роблять перепосилання. return HttpResponseRedirect('/movie/' + str(new_movie.pk) +'/') Все, сервіс готовий до використання, з усім зазначеним на початку лекції функціоналом. Let’s Join!

В останнє змінено: 2015/02/12 10:45