Cinema website tutorial using Masonite framework
Tags: masonite, python, tutorial, framework
Published: 2019-12-15 02:33:07

### Intro

This tutorial will guide you how to create a fully working cinema website with repertoire. We will achieve it by using Python [framework](https://en.wikipedia.org/wiki/Software_framework) - [Masonite](https://github.com/MasoniteFramework/masonite), which allows us to easily and quickly deploy websites. It comes with "craft" command, easy database migrations and much more, which you can read more about it on their github page. This tutorial will be easy to understand - we will try to make it easy to follow by explaining code, appending screenshots and providing additional links to other sources.

### Let's start
Since all documentation of how to install Masonite and create a new project is available [here](https://docs.masoniteproject.com/) we will skip this step. If everything went succesful your new project should look like this in file tree:

![](https://frozent.pl/devto/ser.jpg)

##### That's how our new project looks like in PyCharm.  

Our project at the end will include:
- Main page with schedule
- Subpages with detailed information about movies
- Registration and login
- Ability for admins to add new movies

We'll start up with changing basic stuff in configs. Well, perhaps we don't want our project to have template name, right? So, let's go to `config/application.py` and open this file. 

That's how it supposed to look like: 

![](https://frozent.pl/devto/ser2.jpg)

Find `NAME` variable. It should be at the top. There you can let your imagination work and name your creation. In our case, we called it "TutoCinema".

```diff
- NAME = env('APP_NAME', 'Masonite 2.2')
+ NAME = env('APP_NAME', 'TutoCinema')
```

After changing name we are going to create first page. Let's head to `storage/static` - that's the place where all our files, which are static, such as images, CSS, JS, etc. are stored.

> *Static content is any content that can be delivered to an end user without having to be generated, modified, or processed. The server delivers the same file to each user, making static content one of the simplest and most efficient content types to transmit over the Internet.*
> 
> [source](https://docs.masoniteproject.com/creating-a-blog)


Now let's clean this place up a bit and delete template files from storage: 
- `storage/static/sass`
- `storage/compiled`

After that we create a separate folder  `storage/static/style`, where we will place CSS files for our pages. 

Next we have to make a new View for our project. Views are HTML files or "templates", that will be rendered. More about what rendering is about will be later. Thanks to this we will have our main page with schedule. To create it we have to type in CMD (open it in project folder if you didn't do it yet): 
```shell
craft view mainpage
```

After doing so, we should receive a confirmation that our view was successfully created. Also in our folder `resources/templates/` there ought to be a new file `mainpage.html`

![](https://frozent.pl/devto/ser3.jpg)

Let's open this file and copy-paste code from below.
```html
<!doctype html>
<html>
    <head>
        <link href="https://fonts.googleapis.com/css?family=Roboto+Mono|Sulphur+Point&display=swap" rel="stylesheet">
        <link href="static/style/main.css" type="text/css" rel="stylesheet">
        <title>TutoCinema</title>
    </head>
    <body>
        <div class="toolbar">
            <div class="logo">TutoCinema</div>
        </div>
        <div class="box">
            <a href="" class="box-left-a">
                <div class="box-left">
                    <div class="box-back" style="background-image: url()"></div>
                    <div class="box-bar">
                        <span class="box-premiere">PREMIERE</span>
                        <div class="box-title">Star Wars: The Rise of Skywalker</div>
                    </div>
                </div>
            </a>
            <div class="box-right">
                <a href="">
                    <div class="box-right-small">
                        <div class="box-back" style="background-image: url()"></div>
                        <div class="box-bar">
                            <div class="box-title">Frozen 2</div>
                        </div>
                    </div>
                </a>
                <a href="">
                    <div class="box-right-small">
                        <div class="box-back" style="background-image: url()"></div>
                        <div class="box-bar">
                            <span class="box-premiere">PREMIERE</span>
                            <div class="box-title">Wreck-It Ralph</div>
                        </div>
                    </div>
                </a>
            </div>
        </div>
        <div class="schedule">
            <span class="schedule-title">Schedule</span>
            <div class="schedule-table">
                <div class="schedule-table-r">
                    <div class="schedule-table-mov">Movies</div>
                    <div class="schedule-table-type">Type</div>
                    <div class="schedule-table-date">Date</div>
                </div>
                <div class="schedule-table-r">
                    <a href="">
                        <div class="schedule-table-mov">Frozen 2</div>
                        <div class="schedule-table-type">Dubbing</div>
                        <pre class="schedule-table-date">13th-15th XII 2019: 11:10
23rd-26th XII 2019: 13:30, 14:30</pre>
                    </a>
                </div>
                <div class="schedule-table-r">
                    <a href="">
                        <div class="schedule-table-mov">Star Wars: The Rise of Skywalker</div>
                        <div class="schedule-table-type">Dubbing</div>
                        <pre class="schedule-table-date">13th-15th XII 2019: 11:10</pre>
                    </a>
                </div>
                <div class="schedule-table-r">
                    <a href="">
                        <div class="schedule-table-mov">Wreck-It Ralph</div>
                        <div class="schedule-table-type">Subtitles</div>
                        <pre class="schedule-table-date">13th-15th XII 2019: 11:10</pre>
                    </a>
                </div>
            </div>
        </div>
    </body>
</html>
```
##### NOTE: We deleted links to image sources, so there are empty `background-image: url()`

Then, let's create another file - `main.css` in `storage/static/style/`, where we're going to paste this CSS:
```css
body { background: #fafafa; }
pre  { font-family: "Roboto Mono", monospace; font-size: 16px; }

.box        { font-family: "Sulphur Point", sans-serif; position: relative; display: flex; justify-content: center; max-width: 1200px; margin: 0 auto 20px auto; border: 1px solid #aaa; height: 480px; }
.box-left-a { width: 66.66%; }
.box-left   { height: 100%; position: relative; }
.box-right  { width: 33.33%; height: 100%; position: relative; }
.box-back   { width: 100%; height: 100%; background-size: cover; background-position: center; }
.box-right-small { position: relative; height: 50%; width: 100%; }
.box-bar      { background: linear-gradient(to bottom, transparent, rgba(40, 40, 40, 0.75)); position: absolute; bottom: 0; left: 0; width: 100%; height: 30%; display: flex; flex-direction: column; justify-content: flex-end; align-items: baseline; }
.box-right-small .box-bar .box-title { font-size: 22px; }
.box-left .box-bar .box-title { font-size: 40px; }
.box-title    { padding: 5px 10px; color: white; text-shadow: 0 1px black; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; box-sizing: border-box; }
.box-right-small:hover .box-back, .box-left:hover .box-back { filter: brightness(110%); }
.box-premiere { position: relative; background: rgb(20, 20, 20); color: white; padding: 2px 5px; border-radius: 5px; text-shadow: 0 1px black; margin: 0 10px; letter-spacing: 1px; font-size: 14px; }

.toolbar { position: relative; margin: 10px auto 10px auto; max-width: 1200px; }
.logo    { font-family: "Sulphur Point", sans-serif; font-size: 40px; font-weight: bold; letter-spacing: 3px; }

.schedule         { position: relative; margin: 10px auto 10px auto; max-width: 1200px; }
.schedule-title   { font-size: 30px; font-family: "Sulphur Point", sans-serif; font-weight: bold; letter-spacing: 3px; }
.schedule-table   { width: 100%; margin-top: 10px; }
.schedule-table-r { font-weight: normal; font-size: 18px; text-align: left; font-family: "Sulphur Point", sans-serif; display: flex; }
.schedule-table-r div, .schedule-table-r pre { margin: 0 10px; vertical-align: top; box-sizing: border-box; color: #333; }
.schedule-table-r:nth-child(1) div { border-bottom: 1px dashed #777; margin-bottom: 5px; font-size: 22px; color: black; }
.schedule-table-mov  { width: calc(30% - 20px); }
.schedule-table-type { width: calc(10% - 20px); text-align: center; }
.schedule-table-date { width: calc(60% - 20px); }
.schedule-table-r a  { display: flex; width: 100%; text-decoration: none; }
.schedule-table-r:not(:nth-child(1)) { padding: 5px 0; border-bottom: 1px solid #ddd; transition: 0.2s background; }
.schedule-table-r:not(:nth-child(1)):hover { background: #eee;}

@media (max-width: 767px) and (min-width: 120px) {
  .box        { height: 720px; flex-direction: column; }
  .box-left-a { width: 100%; height: 240px; }
  .box-right  { width: 100%; height: 480px; }
  .box-bar    { height: auto; }
  .box-title  { font-size: 30px !important; }
  .schedule-table-type, .schedule-table-mov, .schedule-table-date { font-size: 14px; }
}
```

This way we will have a simple main page. However, we're not going to see it yet, because we haven't *routed* it yet. 
To do that we should go to `routes/web.py`

![](https://frozent.pl/devto/ser4.jpg)

To show our [View](https://docs.masoniteproject.com/the-basics/views) we have to "route" it, so depending on what client writes as URL, it will display what we want. And because we created main page, we want to route `'/'` URI. With help of [Controllers](https://docs.masoniteproject.com/the-basics/controllers), we will make it to work. Look at `ROUTES` list - there's `Get()` function, which we will change, cuz it's leftover from template of new project. `GET` is one of the *HTTP verbs*, more about this [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). First argument in Get() is an URL address which we will be "routing" (in our case it's `'\'`). Second argument is a controller which we will be referring to. Also `.name()` at the end of `Get()` function allows us to change the name of our route, because:
> *It is good convention to name your routes since route URI's can change but the name should always stay the same.*
> 
> [source](https://docs.masoniteproject.com/the-basics/routing#named-routes)
 
We gonna change names of controller to which we will create in a while (`MainPageController`) and change name of route to `mainPage`. So it will look like this: 
```diff
- Get('/', 'WelcomeController@show').name('welcome'),
+ Get('/', 'MainPageController@show').name('mainPage'),
```

Now we will have to create `MainPageController` by typing in CMD: 
```shell
craft controller MainPage
```

![](https://frozent.pl/devto/ser5.jpg) 

Newly created controller should be found in `app/http/controllers/`, also at this point we can get rid off unused `WelcomeController.py`. Let's open `MainPageController.py` where we should see this code: 

![](https://frozent.pl/devto/ser6.jpg)

Now let's find `show()` method, which currently isn't doing anything in particular, but it will render our main page, by changing this line: 
```diff
- pass
+ return view.render('mainpage')
```
##### `mainpage` is a name of our View.

Now to have a little sneak peek into our progress, let's type in console: 
```shell
craft serve
```

And then, under `http://127.0.0.1:8000` we should be presented with our page! 

![](https://frozent.pl/devto/ser7.jpg)

##### As we stated previously, images link were removed in .html file, so don't worry if you don't see anything

### Auth system
Now we are going to work on registration and login. We start by typing in CMD
```shell
craft auth
```

It will create important files for auth system.

Now we gonna change configs again to create datebase which will keep users data and our movies' information. So, we have to open `.env` which is located in the root of the project.

![](https://frozent.pl/devto/ser8.jpg)

Then change `APP_NAME` to your project's name
```diff
- APP_NAME=Masonite 2.2
+ APP_NAME=TutoCinema
```

And afterwards we are interested in variables prefixed with `DB_`. We gonna use `SQLite` because we won't need to keep big data. Change `DB_CONNECTION` to `sqlite` and name somehow your database, in our case - `cinema.db`
```diff
- DB_CONNECTION=mysql
+ DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
- DB_DATABASE=masonite
+ DB_DATABASE=cinema.db
```

And after this type in CMD
```shell
craft migrate
```

so our database will *migrate* to create needed data for auth system. Now we gonna check if everything works as inteded by turning our server with
```shell
craft serve
```

and go to `http://127.0.0.1:8000/register`. Fill the form with random values to register. Then it should display dashboard. 

![](https://frozent.pl/devto/ser9.jpg)

However we don't want to see dashboard. What we want is to redirect to main page. So to do that we need to open three controllers `RegisterController.py`, `LoginController.py`, `ConfirmController.py` and `resource/templates/auth/base.html`. In all of this files change all `'/home'` to `'/'`.

By the way, delete `HomeContoller.py` and `resource/templates/auth/home.html`. Also in `routes/web.py` delete this line
```diff
- Get().route('/home', 'HomeController@show').name('home'),
```

All of those are not needed.

If we want after log out to redirect to main page instead of login form, we need to change line `LoginController.py` in `logout()` function.
```diff
def logout(self, request: Request, auth: Auth):
    auth.logout()
-   return request.redirect('/login')
+   return request.redirect('/')
```

This way have made an ability to register and login users. The only problem left is that we don't have any buttons to do it. So we are going to add them right now.

Let's add to our `main.css` new lines, which will stylize our buttons for registeration, login and logout pages.
```css
.login { position: absolute; top: 5px; right: 0; font-family: "Sulphur Point", sans-serif; font-size: 24px; color: black; }
.register { position: absolute; top: 35px; right: 0; font-family: "Sulphur Point", sans-serif; color: black; }
.logout-text { position: absolute; top: 5px; right: 0; font-family: "Sulphur Point", sans-serif; font-size: 24px; color: black; text-transform: capitalize; }
.logout { position: absolute; top: 35px; right: 0; font-family: "Sulphur Point", sans-serif; font-size: 14px; color: #777; }
```

Finally, we have to add these buttons to `mainpage.html`. Masonite uses *Jinja2* to render Views, so more about it you can find on [Masonite documentation](https://docs.masoniteproject.com/the-basics/views). We can check if user is log in with `auth()` function. If they are log in this function will return user object with user data. To get their nickname we simply need to write `{{ auth().name }}`. To use it properly in `mainpage.html` we have to append this code
```diff
<div class="toolbar">
    <div class="logo">TutoCinema</div>
+   {% if auth() %}
+       <div class="logout-text">{{ auth().name }}</div>
+       <a href="/logout"><div class="logout">Logout</div></a>
+   {% else %}
+       <a href="/login"><div class="login">Login</div></a>
+       <a href="/register"><div class="register">Register</div></a>
+   {% endif %}
</div>
```

This way we completed basic login and register using Masonite. 

Here is how main page looks like when user is log in 

![](https://frozent.pl/devto/ser11.jpg)

and here is when not

![](https://frozent.pl/devto/ser10.jpg)

##### NOTE: Masonite gives an ability to create only basic auth system, so for example it doesn't check if user with same login already exists, neither if new registered user has the same mail as another user. In this tutorial we consider that our login and registeration work as intended. 

### Schedule 

Let's focus now on how our movies are kept in database. Every movie has:
- title
- type: dubbing or subtitles
- date of projection 
- description
- is it premiere
- price of tickets
- poster

Masonite allows you to create a new Model, which will improve our work in case of migration without bases. 
> *Masonite uses Orator which is an Active Record implementation of an ORM.* 
> 
> [source](https://docs.masoniteproject.com/creating-a-blog#models)

We gonna make our own called `Movie`. To do that we need to type in CMD
```shell
craft model Movie
```

It will create new Model called `Movie`, where we can take a look at it here - `app/Movie.py`. However, we still won't have any table in database, which will keep our movies. To create one we need to use migrate command
```shell
craft migration create_movies --create movies
```

where `create_movies` is the name of a migration and `movies` is the name of table. It's important to suffix the name of table with "-s" compared to the name of Model. Created migration will appear at `databases/migrations`, where it will be prefixed with date of creation. 

Let's open that migration. 

![](https://frozent.pl/devto/ser12.jpg)

From here we can see that our table has `id` field which will increase every time we add a new record. Fields we want to add will have these types:
- title - string 
- type: dubbing or subtitles - bool
- date of projection - text 
- description - text
- is it premiere - bool
- price of tickets - string
- poster (referred to a file which has to be a image) - string

 More about types you can find [here](https://orator-orm.com/docs/0.9/schema_builder.html#adding-columns). In your migration file, you've just created, add this:
```diff
def up(self):
    with self.schema.create('movies') as table:
        table.increments('id')
+       table.string('title')
+       table.text('description')
+       table.string('coverart')
+       table.text('whenPlayed')
+       table.string('price')
+       table.boolean('premiere')
+       table.boolean('type')
        table.timestamps()
```

After these changes, we should type in CMD
```shell
craft migrate
```
To migrate again our database and create our data table. 

Let's go back to our `Movie` Model. Open up `app/Movie.py` and add what we want fill in table:
```diff
class Movie(Model):
+   __fillable__ = ['title', 'description','coverart','whenPlayed','price','premiere','type']
    pass
```

Now we need to somehow make a new record. In our case - a new movie. Because of that we need to add to our main page form, which will allow us to add it. In this tutorial we won't show how to delete the record, just how to add it. 

Let's create a form in HTML, which will send POST request after filling it up to `createMovie` with all data from filled fields. Then it will add a new record to database. 

Firstly we have to add new lines to our `main.css` which will add style to our form. 
```css
.newmovie { position: relative; margin: 20px auto 50px auto; padding-top: 10px; border-top: 1px dashed #555; max-width: 1200px; }
form { font-family: "Sulphur Point", sans-serif; }
```

Secondly we should add this code before `</body>` in `mainpage.html`:
```html
{% if ((auth()) and (auth().name == "root")) %}
<div class="newmovie">
    <span class="schedule-title">Create New Movie</span>
    <form action="/createMovie" method="POST" enctype="multipart/form-data">
        {{ csrf_field }}
        <label>Title</label><br>
        <input type="text" name="title"><br><br>
        <label>Description</label><br>
        <textarea name="description"></textarea><br><br>
        <label>When played</label><br>
        <textarea name="playTime"></textarea><br><br>
        <label>Image</label><br>
        <input type="file" name="imagecover" accept="image/png, image/jpeg"><br><br>
        <label>Price</label><br>
        <textarea name="price"></textarea><br>
        <input type="checkbox" name="ispremiere"> <label>Is premiere?</label><br>
        <input type="checkbox" name="isdubbing"> <label>Check if dubbing, else subtitles</label><br><br>
        <input type="submit" value="Add Movie">
    </form>
</div>
{% endif %}
```

So what is happening here now? First, check if user is log in and if they are named `root`. If yes then whole code between IF brackets is appended in HTML to our page. After clicking submit form will send data using POST method to adress `/createMovie`, which we will work on in a while and where everything will be saved. It's worth noticing that our image is being sent as a file. We gonna save it on the server disk, while its name will be saved in database. This way we gonna save some space in data table. There also appears `{{csrf_field}}`, which is:

> *Masonite comes with CSRF protection so we need a token to render with the CSRF field.* 
>
> [source](https://docs.masoniteproject.com/creating-a-blog#the-template-for-creating)

![](https://frozent.pl/devto/ser13.jpg)

We will focus now on `/createMovie`. We gonna use the same controller as we did before - `MainPageController.py`, because we are using this same HTML template (`mainpage.html`). So in `MainPageController.py` we will add new function `def store(self, request: Request, upload: Upload)`. With this function we will be saving to our datebase new record, using data that we have passed through POST request. Using `Upload` object we will save image to server disk.

Do you remember our Movie model? By using `Movie.create()` function we can create new object with data passed in arguments and with `upload.driver('disk').store()` our image will be saved (that we will pass here in `store()` method).

Our function should look like this:
```python
from app.Movie import Movie
from masonite import Upload

# Code in between...

def store(self, request: Request, upload: Upload):
    upload.driver('disk').store(request.input('imagecover'), filename=request.input('title').replace(" ", "")+".jpg", location='storage/imagecover')
    Movie.create(
        title = request.input('title'),
        description = request.input('description'),
        whenPlayed = request.input('playTime'),
        premiere = request.input('ispremiere'),
        type = request.input('isdubbing'),
        price = request.input('price'),
        coverart = request.input('title').replace(" ", "")+".jpg"
    )

    return request.redirect("/")
```
##### NOTE: Create `imagecover` folder in `storage/` or our images won't upload

In `mainpage.html`, we used `name=""` atributte in input tags. We can access that data here with `request.input(<name>)`. After creating our new Movie object we have to redirect ourselves back to main page.

In `routes/web.py` we append new route in `ROUTES` list:

```diff
ROUTES = [
    Get('/', 'MainPageController@show').name('mainPage'),
+   Post('/createMovie', 'MainPageController@store').name('createMovie')
]
``` 
##### Post because we are sending data through POST request

This way we can check our next item in our checklist!

##### NOTE: At this point, we added 3 example videos to database, below you can see one of filled forms

![](https://frozent.pl/devto/ser14.jpg)

### Displaying our movies

To display our saved movies as shedule on our `mainpage.html`, we need to look up at this code fragment:
```html
<div class="schedule-table-r">
    <a href="">
        <div class="schedule-table-mov">Wreck-It Ralph</div>
        <div class="schedule-table-type">Subtitles</div>
        <pre class="schedule-table-date">13th-15th XII 2019: 11:10</pre>
    </a>
</div>
```

In our HTML code we are repeating this 3 times with static data, which we added as examples. Let's change it to template using Jinja2 syntax:
```html
<div class="schedule-table-r">
    <a href="link to movie information">
        <div class="schedule-table-mov">{{ title }}</div>
        <div class="schedule-table-type">{{ type }}</div>
        <pre class="schedule-table-date">{{ whenPlayed }}</pre>
    </a>
</div>
```

I used variable names from our Movie object, but we still don't reference to any of them. We need to make changes to `show()` function in `MainPageController.py` to pass Movies to rendering.
```diff
def show(self, view: View):
+   moviess = Movie.all()
+   return view.render('mainpage', {'movies': moviess})
-   return view.render('mainpage')
```

`Movie.all()` returns all Movie objects we have saved. By adding `{'movies': moviess}` as additional argument in `render()` we are passing these movies as `movies` variable, which can be used in `mainpage.html` with Jinja2. Let's get back to `mainpage.html`.

Find `<div class="shedule-table">` and delete all of our examples we have made, we won't need them as we gonna swap it with template we showed previously.
```html
{% for mov in movies %}
<div class="schedule-table-r">
    <div class="schedule-table-mov">{{mov.title}}</div>
    <div class="schedule-table-type">{{mov.type}}</div>
    <pre class="schedule-table-date">{{mov.whenPlayed}}</pre>
</div>
{% endfor %}
```

Here we used Jinja2 to create *for* loop to go through our `movies` that we just passed and each object can be used with `mov` variable between *for* loop brackets. As so, we used it for example in `{{ mov.title }}`.

Save it and turn on server (with `craft serve`). If you added some movies, you should be able to see a nice shedule table. On our PC it looks like this:

![](https://frozent.pl/devto/ser15.jpg)

Oh no, our `mov.type` returns 0 or `On` (1), because it's bool. We need to create logic to instead return `Dubbing` if true and `Subtitles` if false. Here's fixed code:
```html
{% for mov in movies %}
<div class="schedule-table-r">
    <div class="schedule-table-mov">{{mov.title}}</div>
    {% if mov.type %}
    <div class="schedule-table-type">Dubbing</div>
    {% else %}
    <div class="schedule-table-type">Subtitles</div>
    {% endif %}
    <pre class="schedule-table-date">{{mov.whenPlayed}}</pre>
</div>
{% endfor %}
```

### Showcase

We assume that there are at least 3 movies in database. This way we can change hardcoded showcase to dynamic one with our data.

Take a look at one of our showcase boxes:
```html
<div class="box-back" style="background-image: url(image.jpg)"></div>
<div class="box-bar">
    <span class="box-premiere">PREMIERE</span>
    <div class="box-title">Title</div>
</div>
```

We need to change 3 elements here: `PREMIERE`, `Title` and `image.jpg` to coresponding field from Movie object. So it should look like this:
```html
<div class="box-back" style="background-image: url({{ coverart }})"></div>
<div class="box-bar">
    {% if premiere %}
    <span class="box-premiere">PREMIERE</span>
    {% endif %}
    <div class="box-title">{{ title }}</div>
</div>
```

Our biggest box in showcase is for video of index 0, while 2 smaller are for index 1 and 2. So if `movies` is our list of Movie objects, using `movies[0]` we can extract first Movie object, `movies[1]` second and so on.
So let's change everything in showcase to this:

```html
<a href="" class="box-left-a">
    <div class="box-left">
        <div class="box-back" style="background-image: url(imagecover/{{ movies[0].coverart }})"></div>
        <div class="box-bar">
            {% if movies[0].premiere %}
            <span class="box-premiere">PREMIERE</span>
            {% endif %}
            <div class="box-title">{{ movies[0].title }}</div>
        </div>
    </div>
</a>
<div class="box-right">
    <a href="">
        <div class="box-right-small">
            <div class="box-back" style="background-image: url(imagecover/{{ movies[1].coverart }})"></div>
            <div class="box-bar">
                {% if movies[1].premiere %}
                <span class="box-premiere">PREMIERE</span>
                {% endif %}
                <div class="box-title">{{ movies[1].title }}</div>
            </div>
        </div>
    </a>
    <a href="">
        <div class="box-right-small">
            <div class="box-back" style="background-image: url(imagecover/{{ movies[2].coverart }})"></div>
            <div class="box-bar">
                {% if movies[2].premiere %}
                <span class="box-premiere">PREMIERE</span>
                {% endif %}
                <div class="box-title">{{ movies[2].title }}</div>
            </div>
        </div>
    </a>
</div>
```

And here are the results of our work:

![](https://frozent.pl/devto/ser16.jpg)

### Last thing to add - movie details page

When we click in our main page on any movie, we should be redirected to coresponding page with detailed information about this movie, when it's projected, what are the price of tickets for it etc. We won't show how to make system for tickets, it can be achived using 3rd party solutions, but we will add *buy ticket* button so it can be extended in the future.

Let's start by adding new View called `movie` (don't mix it with Movie model)
```shell
craft view movie
```

We prepare template for our movie which we want to show - its title, description etc. Assuming that `mov` is our Movie object:
```html
<div class="video">
    <div class="video-back" style="background-image: url(../imagecover/{{ mov.coverart }})"></div>
    <div class="video-back-grad"></div>
    <div class="video-title">{{ mov.title }}</div>
    {% if mov.premiere %}
    <div class="video-label-big">Premiere</div>
    {% endif %}
    {% if mov.type %}
    <div class="video-label">Dubbing</div>
    {% else %}
    <div class="video-label">Subtitles</div>
    {% endif %}
    <pre class="video-text">{{ mov.description }}</pre>
    <pre class="video-text">{{ mov.whenplayed }}</pre>
    <pre class="video-text">{{ mov.price }}</pre>
    <a href="">
        <div class="video-buy-ticket">Buy Ticket</div>
    </a>
</div>
```

Everything should be clear, as we are using excatly what we previously did in other sections. Open our newly created HTML template: `resources/templates/movie.html` and copy-paste this code. It already has our template for movie details.
```html
<!doctype html>
<html>
    <head>
        <link href="https://fonts.googleapis.com/css?family=Roboto+Mono|Sulphur+Point&display=swap" rel="stylesheet">
        <link href="../static/style/movie.css" type="text/css" rel="stylesheet">
        <title>TutoCinema</title>
    </head>
    <body>
        <div class="toolbar">
            <div class="logo">TutoCinema</div>
            {% if auth() %}
            <div class="logout-text">{{ auth().name }}</div>
            <a href="/logout"><div class="logout">Logout</div></a>
            {% else %}
            <a href="/login"><div class="login">Login</div></a>
            <a href="/register"><div class="register">Register</div></a>
            {% endif %}
        </div>
        <div class="video">
            <div class="video-back" style="background-image: url(../imagecover/{{ mov.coverart }})"></div>
            <div class="video-back-grad"></div>
            <div class="video-title">{{ mov.title }}</div>
            {% if mov.premiere %}
            <div class="video-label-big">Premiere</div>
            {% endif %}
            {% if mov.type %}
            <div class="video-label">Dubbing</div>
            {% else %}
            <div class="video-label">Subtitles</div>
            {% endif %}
            <pre class="video-text">{{ mov.description }}</pre>
            <pre class="video-text">{{ mov.whenplayed }}</pre>
            <pre class="video-text">{{ mov.price }}</pre>
            <a href="">
                <div class="video-buy-ticket">Buy Ticket</div>
            </a>
        </div>
    </body>
</html>
```

Also we need to create CSS for our `movie.html`, so in `storage/static/style/` create new file `movie.css` and copy-paste this code:
```css
body     { background: #fafafa; }
.toolbar { position: relative; margin: 10px auto 10px auto; max-width: 1200px; }
.logo    { font-family: "Sulphur Point", sans-serif; font-size: 40px; font-weight: bold; letter-spacing: 3px; }

.login       { position: absolute; top: 5px; right: 0; font-family: "Sulphur Point", sans-serif; font-size: 24px; color: black; }
.register    { position: absolute; top: 35px; right: 0; font-family: "Sulphur Point", sans-serif; color: black; }
.logout-text { position: absolute; top: 5px; right: 0; font-family: "Sulphur Point", sans-serif; font-size: 24px; color: black; text-transform: capitalize; }
.logout      { position: absolute; top: 35px; right: 0; font-family: "Sulphur Point", sans-serif; font-size: 14px; color: #777; }

.video            { position: relative; max-width: 1200px; min-height: 500px; padding: 0 0 30px 0; margin: 10px auto; }
.video-back       { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-size: cover; background-position: center; filter: brightness(40%) }
.video-back-grad  { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to left, transparent 50%, rgba(0, 0, 0 ,0.5) 75%, black) }
.video-title      { position: relative; color: white; font-size: 26px; max-width: 400px; font-family: "Sulphur Point", sans-serif; padding: 20px 0 0 20px; }
.video-label      { font-family: "Sulphur Point", sans-serif; padding: 0 20px; max-width: 400px; color: #8edbcc; font-size: 14px; position: relative; margin-top: 10px; }
.video-label-big  { font-family: "Sulphur Point", sans-serif; padding: 0 20px; max-width: 400px; color: #ff6262; font-size: 20px; position: relative; }
.video-text       { font-family: "Sulphur Point", sans-serif; padding: 0 20px; max-width: 400px; color: #ccc; font-size: 16px; position: relative; margin: 5px 0 20px 0; white-space: pre-wrap; word-wrap: break-word; }
.video-buy-ticket { position: relative; background: linear-gradient(125deg,#3f8e3f,#1f511f); padding: 10px 15px; font-size: 20px; margin: 5px 20px; display: inline-block; font-family: "Sulphur Point", sans-serif; color: #c5e1cb; border-radius: 5px; background-size: 300%; transition: 0.2s background-size }
.video-buy-ticket:hover { background-size: 100%; }
```

We have finished creating our new HTML template, now we need to create new movie controller with
```shell
craft controller movie
```

Open it (it's in `app/http/controllers/`) and `routes/web.py`.

Firstly, in `routes/web.py` we add another route, but this time it will be different a bit in URI:
```diff
ROUTES = [
    Get('/', 'MainPageController@show').name('mainPage'),
    Post('/createMovie', 'MainPageController@store').name('createMovie'),
+   Get('/movie/@id', 'movieController@show').name('displayMovie')
]
```

As you can see we added `@id` at the end of URI. When client will write something in a place of it, we will be able to use that as `id` variable in our controller, just like this:
```diff
+ from app.Movie import Movie

// Some code...

    def show(self, view: View):
+       mov = Movie.find(self.request.param('id'))
+       return view.render('movie', {'mov': mov})
-       pass
```

With `<model name>.find()` we can find movie by it's ID from data table. We save it here to `mov` variable, which we will be passing via `render()`. Now after starting server (`craft serve`) and heading to `http://127.0.0.1:8000/movie/1` we should see our page

![](https://frozent.pl/devto/ser17.jpg)

Last thing we need to add are links to this page in our `mainpage.html`, which is fairly easy. Instead of having this code in shedule:
```html
{% for mov in movies %}
<div class="schedule-table-r">
    <div class="schedule-table-mov">{{mov.title}}</div>
    {% if mov.type %}
    <div class="schedule-table-type">Dubbing</div>
    {% else %}
    <div class="schedule-table-type">Subtitles</div>
    {% endif %}
    <pre class="schedule-table-date">{{mov.whenPlayed}}</pre>
</div>
{% endfor %}
```

We gonna have:
```html
{% for mov in movies %}
<div class="schedule-table-r">
    <a href="movie/{{mov.id}}">
        <div class="schedule-table-mov">{{mov.title}}</div>
        {% if mov.type %}
        <div class="schedule-table-type">Dubbing</div>
        {% else %}
        <div class="schedule-table-type">Subtitles</div>
        {% endif %}
        <pre class="schedule-table-date">{{mov.whenPlayed}}</pre>
    </a>
</div>
{% endfor %}
```

Also in our showcase code we need to add manually `movie/1`, `movie/2` and `movie/3` in empty `<a href="">` tags. This way we fully created working website for cinema, which can be extended in future with additional functionality.

### Afterwords

Masonite is really simple framework, yet powerful. It's growing rapidly on GitHub and it's getting more popular every day. We didn't have previous experience with any of Python frameworks, so it was pleasant suprise how easy it is to follow documentation and to understand logic behind it. We fully recommand it for newbies as well as for professional developers, who wants to try new framework at work.

*~Michelle ([@bthefw](https://github.com/bthefw)) and Nick ([@holosiek](https://github.com/holosiek))*