Категории
Создание контроллера
Все контроллеры по умолчанию расположены в каталоге app/controllers
и Masonite продвигает идею
один контролер, один файл. Легко запомнить, где именно находится контроллер, потому что имя файла —
это контроллер.
Конечно, вы можете перемещать контроллеры куда угодно, но командна craft
по умолчанию поместит их в
отдельные файлы.
В большинстве случаев, вы можете создать контроллер с помощью команды craft
:
Выполним команду для создания контроллера категорий.
python craft controller Category
Команда craft
создаст контроллер app/controllers/CategoryController.py
, который выглядит следующим
образом:
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def show(self, view: View):
return view.render("")
|
Вы можете заметить, что в контроллере уже есть один метод, show()
. Данный метод мы рассмотрим позже.
Создание категорий
Добавим импорты и метод store()
, он будет использоваться для создания категорий:
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.request import Request
from masonite.response import Response
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def show(self, view: View):
return view.render("")
def store(self, request: Request, response: Response):
Category.create(name=request.input("name"))
return response.redirect('/')
|
Бизнес логика
Я считаю, что бизнес логика НЕ должна находиться внутри контроллера и должна быть вынесена в
отдельный сервис.
Но для упрощения примера и ознакомления с фреймворком Masonite, логику напишем в контроллере.
Service Container
Обратите внимание, что мы сейчас использовали request: Request
и response: Response
.
Это объекты Request и Response. В этом сила и красота Masonite, и ваше первое знакомство с
Service Container. Service Container — чрезвычайно мощная реализация, позволяющая запросить у
Masonite объект (в данном случае Request или Response) и получить этот объект. Это называется
«внедрением зависимостей», важная концепция для понимания, поэтому обязательно прочитайте
документацию.
С помощью метода create()
модели Category
создадим категорию. В create()
передаем название столбца и
данные которые хотим записать.
Для получения данных из запроса используем метод input()
.
Masonite не обращает внимание на методы запроса, поэтому для получения данных по запросу GET
, POST
и т.д. мы используем метод .input()
.
Валидация
В данный момент мы не проверяем пришедшие данные. Добавим валидацию.
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.request import Request
from masonite.response import Response
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def show(self, view: View):
return view.render("")
def store(self, request: Request, response: Response):
errors = request.validate({"name": "required"})
if errors:
return response.back()
Category.create(name=request.input("name"))
return response.redirect('/')
|
На 14-ой строке указываем, что name
обязателен. Более подробно про валидацию можете прочитать в
документации.
Далее если у нас есть ошибки, пользователь будет перенаправлен на страницу с которой был отправлен запрос.
View
Добавим еще один метод .create()
, данный метод будет рендерить шаблон добавления категории:
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.request import Request
from masonite.response import Response
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def show(self, view: View):
return view.render("")
def create(self, view: View):
return view.render("category.create")
def store(self, request: Request, response: Response):
errors = request.validate({"name": "required"})
if errors:
return response.back()
Category.create(name=request.input("name"))
return response.redirect('/')
|
Обратите внимание, что здесь мы также "типизируем" класс View
. Это то, что Masonite называет
"внедрением зависимостей", о чём говорилось ранее.
В метод .render()
передаем путь к шаблону через точку. Далее мы создадим этот шаблон и он будет
доступен по пути templates/category/create.html
.
Добавление routes
В файле routes/web.py
добавим два маршрута.
routes/web.py |
---|
| from masonite.routes import Route
ROUTES = [
Route.get("/create", "CategoryController@create"),
Route.post("/create", "CategoryController@store"),
]
|
При get
запросе будет вызван метод контроллера create()
и который отобразит страницу.
При post
запросе будет вызван метод контроллера store()
, который примет отправленные данные формы.
Создание html шаблона
Теперь в директории templates
создадим директорию category
, а в ней файл create.html.
В файл templates/category/create.html
добавим следующий код:
templates/category/create.html |
---|
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/static/style.css" rel="stylesheet">
<title>Title</title>
</head>
<body>
<section>
<h2>Создать категорию</h2>
<form action="/create" method="POST">
{{ csrf_field }}
<input type="text" name="name" placeholder="Название категории">
<button class="btn" type="submit">Создать</button>
</form>
</section>
</body>
</html>
|
Обратите внимание, здесь есть тег {{ csrf_field }}
под тегом <form>
. Masonite поставляется с
защитой от CSRF, поэтому нам нужен токен для отображения скрытого поля с CSRF токеном.
templates/category/create.html |
---|
| ...
<h2>Создать категорию</h2>
<form action="/create" method="POST">
{{ csrf_field }}
<input type="text" name="name" placeholder="Название категории">
<button class="btn" type="submit">Создать</button>
</form>
...
|
На 4-й строке в атрибуте action
указываем ссылку куда отправить данные из формы. Данную ссылку
мы описали ранее в routes/web.py
. Так же указали метод http запроса, используем POST
.
templates/category/create.html |
---|
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/static/style.css" rel="stylesheet">
<title>Title</title>
</head>
<body>
<section>
<h2>Создать категорию</h2>
<form action="/create" method="POST">
{{ csrf_field }}
<input type="text" name="name" placeholder="Название категории">
<button class="btn" type="submit">Создать</button>
</form>
</section>
</body>
</html>
|
Здесь указываем ссылку на файл стилей, ниже мы его создадим.
Статические файлы
В директории storage
создайте папку static
.
Создайте файл style.css
в директории storage/static/
и добавим в него следующий код.
storage/static/style.css |
---|
| section {
width: 700px;
margin: 0 auto;
padding: 1px 15px 30px 15px;
box-shadow: 0 3px 5px 0 grey;
text-align: center;
}
a {
text-decoration: none;
}
.list-item {
display: flex;
justify-content: space-between;
margin: 15px 0 5px 0;
padding: 5px 10px;
box-shadow: 1px 1px 4px grey;
}
.list-item:hover {
background-color: #f7f7f7;
}
.btn {
background-color: #199319;
color: white;
padding: 10px 10px;
text-decoration: none;
border: none;
cursor: pointer;
}
.btn:hover {
background-color: #223094;
}
.btn-del {
background-color: #e32323;
color: white;
padding: 10px 20px;
text-decoration: none;
}
.btn-del:hover {
background-color: #c91f1f;
}
.done::after {
content: '\2713';
color: #199319;
}
.work::after {
content: '\25CF';
color: #c91f1f;
}
|
Список категорий
Теперь выведем список категорий которые мы создаем.
Контроллер
В файле app/controllers/CategoryController.py
добавим следующий код:
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.request import Request
from masonite.response import Response
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def index(self, view: View):
categories = Category.all()
return view.render('category.list', {'categories': categories})
def show(self, view: View):
return view.render("")
def create(self, view: View):
return view.render("category.create")
def store(self, request: Request, response: Response):
errors = request.validate({"name": "required"})
if errors:
return response.back()
Category.create(name=request.input("name"))
return response.redirect('/')
|
Здесь мы получаем все категории из БД. Вызвав метод all()
модели, мы получим все записи из БД.
Затем указываем какой шаблон рендерить и передаем контекст в виде словаря. Таким образом передаем
данные в шаблон.
По ключу словаря будем обращаться к данным в самом шаблоне.
Routes
В файле routes/web.py
добавим маршрут для вывода списка категорий. Перейдя на главную страницу сайта
будем получать все категории.
routes/web.py |
---|
| from masonite.routes import Route
ROUTES = [
Route.get("/", "CategoryController@index"),
Route.get("/create", "CategoryController@create"),
Route.post("/create", "CategoryController@store"),
]
|
Шаблон html
В директории templates/category
создадим файл list.html
и добавим следующий код.
templates/category/list.html |
---|
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/static/style.css" rel="stylesheet">
<title>Категории</title>
</head>
<body>
<section>
<div class="center">
<h2>Список категорий</h2>
</div>
@for category in categories
<div class="list-item">
<a href="/task/{{category.id}}">{{category.name}}</a>
<a class="edit" href="/single/{{category.id}}">Редактировать</a>
</div>
@endfor
</section>
</body>
</html>
|
Masonite использует шаблонизатор Jinja2
, поэтому, если вы не понимаете этот шаблон, обязательно
ознакомьтесь с документацией.
Наследование шаблонов
Jinja2 поддерживает расширение (наследование) шаблонов, чтобы избежать повторения кода.
В Masonite уже предустановлен базовый шаблон, templates/base.html
.
В данном руководстве мы не будем использовать наследование шаблонов, для упрощения задачи.
templates/category/list.html |
---|
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/static/style.css" rel="stylesheet">
<title>Категории</title>
</head>
<body>
<section>
<div class="center">
<h2>Список категорий</h2>
</div>
@for category in categories
<div class="list-item">
<a href="/task/{{category.id}}">{{category.name}}</a>
<a class="edit" href="/single/{{category.id}}">Редактировать</a>
</div>
@endfor
</section>
</body>
</html>
|
Здесь с помощью шаблонизатора, описываем цикл for
. Который переберет все переданные категории.
В фигурных скобках указываем объект категории и через точку обращаемся к атрибуту (столбцу таблицы)
name
и id
.
На строке 17 указываем ссылку на список задач конкретной категории, передаем id
категории.
В данный момент у нас еще нет логики обработки этого url, реализуем позже.
На 18-й строке формируем ссылку на странице редактирования. Ниже реализуем данный функционал.
Запуск сервера разработки
Выполним команду для запуска сервера разработки:
И перейдите по адресу http://127.0.0.1:8000/create
Вы увидите форму, после отправки которой вас перенаправит на http://127.0.0.1:8000/
Чтение, редактирование и удаление категорий
Также сделаем, чтобы можно было редактировать и удалять категорию.
Вывод одной категории
Реализуем возможность просматривать одну категорию, чтобы иметь возможность
редактировать её и удалять.
Контроллер одной категории
Доработаем метод show()
контроллера CategoryController
.
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.request import Request
from masonite.response import Response
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def index(self, view: View):
categories = Category.all()
return view.render('category.list', {'categories': categories})
def show(self, view: View, request: Request):
category = Category.find_or_fail(request.param("id"))
return view.render('category.single', {'category': category})
def create(self, view: View):
return view.render("category.create")
def store(self, request: Request, response: Response):
errors = request.validate({"name": "required"})
if errors:
return response.back()
Category.create(name=request.input("name"))
return response.redirect('/')
|
В метод show()
добавил получение request
. Чтобы получить параметр из маршрута. Для этого
используем метод param()
с указанием имени параметра.
Затем ищем категорию по id
, который будет передаваться в url, например, /single/2
. Здесь 2
и
есть наш id
. Если такая категория не будет найдена, мы увидим ошибку 404. Если вы не хотите получать
эту ошибку, можете вызвать метод find()
у модели.
Затем мы указываем какой шаблон будем использовать и передаем в контекст объект категории.
Route одной категории
routes/web.py |
---|
| from masonite.routes import Route
ROUTES = [
Route.get("/", "CategoryController@index"),
Route.get("/create", "CategoryController@create"),
Route.post("/create", "CategoryController@store"),
Route.get("/single/@id", "CategoryController@show").name("category_single"),
]
|
Здесь в url указан параметр id
. Чтобы указать параметр, его нужно прикрепить к символу @
.
Также я указал имя маршрута, оно используется для получения информации о маршруте в других частях
проекта. Имя более статично, чем URL-адрес.
Шаблон одной категории
В директории templates/category
создадим файл single.html
.
templates/category/single.html |
---|
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/static/style.css" rel="stylesheet">
<title>Редактирование категории</title>
</head>
<body>
<section>
<h2>Редактирование категории</h2>
<form action="/update/{{category.id}}" method="POST">
{{ csrf_field }}
<input type="text" name="name" value="{{category.name}}">
<p>
<button class="btn" type="submit">Сохранить</button>
</p>
</form>
</section>
</body>
</html>
|
В форме используя атрибутaction
указываем url по которому будет идти отправка формы,
контролер и маршрут напишем позже.
В value
тега input
передаю значение имени категории. Таким образом форма будет заполнена.
Контроллер обновления категории
Добавим метод update()
в наш контроллер. Он будет отвечать за обновление категории.
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.request import Request
from masonite.response import Response
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def index(self, view: View):
categories = Category.all()
return view.render('category.list', {'categories': categories})
def show(self, view: View, request: Request):
category = Category.find_or_fail(request.param("id"))
return view.render('category.single', {'category': category})
def create(self, view: View):
return view.render("category.create")
def store(self, request: Request, response: Response):
errors = request.validate({"name": "required"})
if errors:
return response.back()
Category.create(name=request.input("name"))
return response.redirect('/')
def update(self, request: Request, response: Response):
category = Category.find_or_fail(request.param("id"))
errors = request.validate({"name": "required"})
if errors:
return response.redirect(name='category_single', params={"id": request.param("id")})
category.name = request.input('name')
category.save()
return response.redirect('/')
|
На 32-й строке указываем, что name
обязателен.
Если name
будет отсутствовать, то пользователь будет перенаправлен на ту же страницу.
Я здесь не использую back()
, для того чтобы показать как работать с redirect()
.
В метод redirect()
передаем имя маршрута и в параметрах виде словаря передаем id
категории.
Таким образом будет построен нужный нам url, на страницу редактирования категории.
app/controllers/CategoryController.py |
---|
| ...
class CategoryController(Controller):
...
def update(self, request: Request, response: Response):
category = Category.find_or_fail(request.param("id"))
errors = request.validate({"name": "required"})
if errors:
return response.redirect(name='category_single', params={"id": request.param("id")})
category.name = request.input('name')
category.save()
return response.redirect('/')
|
После получения объекта категории, атрибуту name
присваиваем полученное значение от
пользователя и сохраняем значение в БД.
Route обновления категории
routes/web.py |
---|
| from masonite.routes import Route
ROUTES = [
Route.get("/", "CategoryController@index"),
Route.get("/create", "CategoryController@create"),
Route.post("/create", "CategoryController@store"),
Route.get("/single/@id", "CategoryController@show").name("category_single"),
Route.post("/update/@id", "CategoryController@update").name("category_update"),
]
|
Для обновления категории добавляем новый маршрут и указываем метод контроллера update()
.
Здесь используем метод post
.
Запуск сервера
Теперь можно запустить сервер разработки и проверить как работает редактирование категории.
Перейдите по адресу http://localhost:8000. Если вы уже создавали категорию,
то кликнув по ссылке Редактировать вас перенаправит на страницу редактирования категории.
Или перейдите по ссылке
http://localhost:8000/single/1
У вас должна отобразиться форма с уже заполненными данными.
Если из поля ввода удалить текст и сохранить, вас должно перенаправить на эту же страницу.
Вы можете ввести валидные данные и нажать сохранить, категория измениться и вы будете перенаправлены
на главную страницу.
Удаление категории
Настало время реализовать удаление категории.
Контроллер удаления категории
В контроллер добавим метод destroy()
для удаления категории.
app/controllers/CategoryController.py |
---|
| from masonite.controllers import Controller
from masonite.request import Request
from masonite.response import Response
from masonite.views import View
from app.models.Category import Category
class CategoryController(Controller):
def index(self, view: View):
categories = Category.all()
return view.render('category.list', {'categories': categories})
def show(self, view: View, request: Request):
category = Category.find_or_fail(request.param("id"))
return view.render('category.single', {'category': category})
def create(self, view: View):
return view.render("category.create")
def store(self, request: Request, response: Response):
errors = request.validate({"name": "required"})
if errors:
return response.back()
Category.create(name=request.input("name"))
return response.redirect('/')
def update(self, request: Request, response: Response):
category = Category.find_or_fail(request.param("id"))
errors = request.validate({"name": "required"})
if errors:
return response.redirect(name='category_single', params={"id": request.param("id")})
category.name = request.input('name')
category.save()
return response.redirect('/')
def destroy(self, request: Request, response: Response):
post = Category.find_or_fail(request.param("id"))
post.delete()
return response.redirect('/')
|
Здесь ищем категорию и если она существует, то удаляем. Для этого используем метод delete()
.
Маршрут удаления категории
routes/web.py |
---|
| from masonite.routes import Route
ROUTES = [
Route.get("/", "CategoryController@index"),
Route.get("/create", "CategoryController@create"),
Route.post("/create", "CategoryController@store"),
Route.get("/single/@id", "CategoryController@show").name("category_single"),
Route.post("/update/@id", "CategoryController@update").name("category_update"),
Route.get("/delete/@id", "CategoryController@destroy"),
]
|
Добавляю еще один маршрут для удаления категории. Также будем передавать id
той категории которую
хотим удалить. Для удаления будем использовать http метод get
.
Шаблон удаления категории
В шаблоне templates/category/single.html
добавим ссылку на удаление.
templates/category/single.html |
---|
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/static/style.css" rel="stylesheet">
<title>Редактирование категории</title>
</head>
<body>
<section>
<h2>Редактирование категории</h2>
<form action="/update/{{category.id}}" method="POST">
{{ csrf_field }}
<input type="text" name="name" value="{{category.name}}">
<p>
<button class="btn" type="submit">Сохранить</button>
<a class="btn-del" href="/delete/{{category.id}}">Удалить</a>
</p>
</form>
</section>
</body>
</html>
|
Просто добавляем ссылку на удаления категории и подставляем id
категории.
После нажатия по ссылке, категория должна удалиться и вы будете перенаправлены на главную страницу.
Запустите сервер разработки, перейдите на страницу редактирования категории и попробуйте удалить
категорию.
Ссылка на создание категории
Доработаем шаблон списка категорий templates/category/list.html
и добавим ссылку на страницу создания категории.
templates/category/list.html |
---|
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="/static/style.css" rel="stylesheet">
<title>Категории</title>
</head>
<body>
<section>
<div class="center">
<h2>Список категорий</h2>
<a class="btn" href="{{ route('category_create') }}">Создать категорию</a>
</div>
@for category in categories
<div class="list-item">
<a href="/task/{{category.id}}">{{category.name}}</a>
<a class="edit" href="/single/{{category.id}}">Редактировать</a>
</div>
@endfor
</section>
</body>
</html>
|
Тут я также добавил просто ссылку, но для построения url использовал route()
.
В данный метод передаем имя маршрута в виде строки.
Чтобы данную ссылку Masonite смог построить, нужно добавить name
нашему маршруту создания категории.
routes/web.py |
---|
| from masonite.routes import Route
ROUTES = [
Route.get("/", "CategoryController@index"),
Route.get("/create", "CategoryController@create").name('category_create'),
Route.post("/create", "CategoryController@store"),
Route.get("/single/@id", "CategoryController@show").name("category_single"),
Route.post("/update/@id", "CategoryController@update").name("category_update"),
Route.get("/delete/@id", "CategoryController@destroy"),
]
|
Теперь можете запустить сервер и проверить как это работает.
У вас на главной странице должен выводиться список категорий. Вверху быть ссылка на создание категории.
При переходе на одну из категорий, у вас должна быть возможность ее отредактировать или удалить.
Часть 5