DEV Community

Holo
Holo

Posted on

Cinema website tutorial using Masonite framework

Intro

This tutorial will guide you how to create a fully working cinema website with repertoire. We will achieve it by using Python framework - 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 we will skip this step. If everything went succesful your new project should look like this in file tree:

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:

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".

- 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

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):

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

Let's open this file and copy-paste code from below.

<!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:

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

To show our View 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, 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. 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

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:

- Get('/', 'WelcomeController@show').name('welcome'),
+ Get('/', 'MainPageController@show').name('mainPage'),

Now we will have to create MainPageController by typing in CMD:

craft controller MainPage

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:

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:

- 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:

craft serve

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

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

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.

Then change APP_NAME to your project's name

- 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

- 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

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

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.

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

- 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.

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.

.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. 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

<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

and here is when not

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

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

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

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.

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. In your migration file, you've just created, add this:

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

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:

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.

.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:

{% 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

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:

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:

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

Displaying our movies

To display our saved movies as shedule on our mainpage.html, we need to look up at this code fragment:

<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:

<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.

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.

{% 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:

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:

{% 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:

<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:

<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:

<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:

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)

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:

<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.

<!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:

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

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:

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:

+ 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

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:

{% 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:

{% 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) and Nick (@holosiek)

Top comments (0)