DEV Community

Cover image for Crafting a Dynamic Cinema Website with the Masonite Framework
Dwayne John
Dwayne John

Posted on

Crafting a Dynamic Cinema Website with the Masonite Framework

Hey everyone, Dwayne here. Today, I'm excited to guide you through building a fully-functional cinema website, complete with a movie repertoire, user authentication, and an admin panel. We'll be leveraging the power and elegance of the Masonite framework for Python to bring this project to life.

Masonite is a fantastic, developer-friendly framework that comes packed with features like the craft command-line tool, seamless database migrations, and a robust ORM, allowing for rapid development and clean code. This tutorial is designed to be easy to follow, with clear explanations, code snippets, and helpful screenshots along the way.

What We're Building

By the end of this guide, our cinema website will have:

  • A main page featuring a dynamic movie showcase and schedule. If you want to watch for free on PC then I know a legit source in which you can easily watch movies for free on PC or Android.
  • Detailed subpages for each movie
  • A complete user registration and login system
  • Functionality for admins to add new movies to the repertoire

Project Setup and Initial Configuration

The official Masonite documentation provides an excellent guide for installation and creating a new project, so we'll skip that foundational step here. Assuming you've successfully run craft new, your project structure should look something like this in your IDE:

The first order of business is personalization. Let's change the generic project name. Navigate to config/application.py and update the NAME variable:

# Find this line in config/application.py
NAME = env('APP_NAME', 'Masonite 2.2')

# And change it to your project's name
NAME = env('APP_NAME', 'TutoCinema')
Enter fullscreen mode Exit fullscreen mode

Creating the Main Page View

Static files like CSS, JavaScript, and images are housed in storage/static/. Let's clean up the default template files and create a dedicated folder for our styles.

  1. Delete the storage/static/sass and storage/compiled folders
  2. Create a new folder: storage/static/style

Now, let's generate the view for our homepage. In your terminal, run:

craft view mainpage
Enter fullscreen mode Exit fullscreen mode

This command creates a new template file at resources/templates/mainpage.html. Open this file and replace its contents with the following HTML structure. (Note: We've left image URLs empty for now.)

<!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>
        <!-- Toolbar with Logo and Auth Links -->
        <div class="toolbar">
            <div class="logo">TutoCinema</div>
        </div>

        <!-- Main Showcase Section -->
        <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>

        <!-- Schedule Table -->
        <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>
                <!-- Static schedule rows will be replaced dynamically later -->
                <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>
                <!-- ... more static rows ... -->
            </div>
        </div>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Next, create storage/static/style/main.css and add the CSS to style our page.

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; }
/* ... (Include all the provided CSS here) ... */

@media (max-width: 767px) and (min-width: 120px) {
  /* Responsive styles */
}
Enter fullscreen mode Exit fullscreen mode

Routing and Controllers: Making the Page Live

To see our view in the browser, we need to route a URL to it and create a controller to handle the logic.

Routing: Open routes/web.py. We'll change the default route to use a new controller we're about to create.

# Change this line in the ROUTES list
Get('/', 'WelcomeController@show').name('welcome'),

# To this
Get('/', 'MainPageController@show').name('mainPage'),
Enter fullscreen mode Exit fullscreen mode

Controller: Create the controller using the craft command:

craft controller MainPage
Enter fullscreen mode Exit fullscreen mode

Now, open app/http/controllers/MainPageController.py and modify the show method to render our main page.

def show(self, view: View):
    return view.render('mainpage')
Enter fullscreen mode Exit fullscreen mode

You can now delete the unused WelcomeController.py and its associated view. Let's fire up the server and take a look!

craft serve
Enter fullscreen mode Exit fullscreen mode

Navigate to http://127.0.0.1:8000. You should see the skeleton of your cinema homepage. The styling is in place, but the data is still static.

Implementing User Authentication

Adding user registration and login is incredibly straightforward with Masonite.

craft auth
Enter fullscreen mode Exit fullscreen mode

This command generates all the necessary files for an authentication system. Next, we need to configure our database. We'll use SQLite for simplicity. Open your .env file in the project root and make the following changes:

APP_NAME=TutoCinema
DB_CONNECTION=sqlite
DB_DATABASE=cinema.db
Enter fullscreen mode Exit fullscreen mode

Then, run the migrations to create the user tables:

craft migrate
Enter fullscreen mode Exit fullscreen mode

Start the server again (craft serve) and visit http://127.0.0.1:8000/register. You should be able to register a new user and be redirected to a dashboard. Let's change that redirect to our main page.

  • In RegisterController.py, LoginController.py, and ConfirmController.py, change all instances of '/home' to '/'
  • In resources/templates/auth/base.html, do the same
  • You can now delete HomeController.py and its view
  • In routes/web.py, remove the route for '/home'
  • To redirect to the homepage after logout, update the logout method in LoginController.py:
def logout(self, request: Request, auth: Auth):
    auth.logout()
    return request.redirect('/')
Enter fullscreen mode Exit fullscreen mode

Finally, let's add login/logout buttons to our mainpage.html. We'll use Masonite's auth() helper to check the user's status.

Add this to mainpage.html inside the toolbar div:

{% raw %}
<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>
{% endraw %}
Enter fullscreen mode Exit fullscreen mode

Add the corresponding CSS to main.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; }
Enter fullscreen mode Exit fullscreen mode

Now your site has a functional login system!

Building the Movie Model and Database

Our movies need a home in the database. Each movie will have:

  • title (string)
  • type (boolean: dubbing or subtitles)
  • whenPlayed (text)
  • description (text)
  • premiere (boolean)
  • price (string)
  • coverart (string, for the image filename)

Let's create the Model and the database table.

Create the Model:

craft model Movie
Enter fullscreen mode Exit fullscreen mode

Create a Migration for the movies table:

craft migration create_movies --create movies
Enter fullscreen mode Exit fullscreen mode

Open the newly created file in databases/migrations and define the table's structure.

def up(self):
    with self.schema.create('movies') as table:
        table.increments('id')
        table.string('title')
        table.text('description')
        table.string('coverart') # Stores the image filename
        table.text('whenPlayed') # Stores playtimes as text
        table.string('price')
        table.boolean('premiere')
        table.boolean('type') # True for dubbing, False for subtitles
        table.timestamps()
Enter fullscreen mode Exit fullscreen mode

Run the migration:

craft migrate
Enter fullscreen mode Exit fullscreen mode

Update the Model: Open app/Movie.py and specify the fillable attributes.

from masoniteorm.models import Model

class Movie(Model):
    __fillable__ = ['title', 'description', 'coverart', 'whenPlayed', 'price', 'premiere', 'type']
Enter fullscreen mode Exit fullscreen mode

Creating an Admin Form to Add Movies

Now, let's build a form that allows an admin (in this case, a user with the name "root") to add new movies.

1. Add the HTML Form to mainpage.html:
Place this code before the closing </body> tag.

{% raw %}
{% 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 %}
{% endraw %}
Enter fullscreen mode Exit fullscreen mode

2. Style the Form in main.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; }
Enter fullscreen mode Exit fullscreen mode

3. Handle the Form Submission in the Controller:
Update MainPageController.py to handle the POST request, save the image, and create a new movie record.

from app.Movie import Movie
from masonite import Upload

# ... inside the MainPageController class ...

def store(self, request: Request, upload: Upload):
    # Save the uploaded image
    upload.driver('disk').store(request.input('imagecover'), filename=request.input('title').replace(" ", "")+".jpg", location='storage/imagecover')

    # Create a new movie record
    Movie.create(
        title=request.input('title'),
        description=request.input('description'),
        whenPlayed=request.input('playTime'),
        premiere=bool(request.input('ispremiere')),
        type=bool(request.input('isdubbing')),
        price=request.input('price'),
        coverart=request.input('title').replace(" ", "") + ".jpg"
    )
    return request.redirect("/")
Enter fullscreen mode Exit fullscreen mode

Don't forget to create the storage/imagecover/ directory for the images.

4. Create the Route: Add a new route in routes/web.py.

from masonite.routes import Get, Post

ROUTES = [
    Get('/', 'MainPageController@show').name('mainPage'),
    Post('/createMovie', 'MainPageController@store').name('createMovie') # New route
]
Enter fullscreen mode Exit fullscreen mode

Dynamically Displaying Movies

Now for the magic! Let's replace our hardcoded movie data with dynamic data from the database.

1. Pass Movies to the View: Modify the show method in MainPageController.py.

def show(self, view: View):
    movies = Movie.all() # Fetch all movies
    return view.render('mainpage', {'movies': movies}) # Pass them to the template
Enter fullscreen mode Exit fullscreen mode

2. Update the Schedule Table in mainpage.html:
Replace the static schedule rows with a Jinja2 loop.

{% raw %}
<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>
    {% 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 %}
</div>
{% endraw %}
Enter fullscreen mode Exit fullscreen mode

3. Dynamically Populate the Showcase: Update the showcase section to display the first three movies.

{% raw %}
<div class="box">
    <!-- Main Featured Movie (first in list) -->
    <a href="movie/{{ movies[0].id }}" 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">
        <!-- Second Movie -->
        <a href="movie/{{ movies[1].id }}">
            <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>
        <!-- Third Movie -->
        <a href="movie/{{ movies[2].id }}">
            <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>
</div>
{% endraw %}
Enter fullscreen mode Exit fullscreen mode

Refresh the page, and you'll see your database content dynamically rendering on the site!

Creating Detailed Movie Pages

The final step is to create individual pages for each movie.

1. Create the View and Controller:

craft view movie
craft controller movie
Enter fullscreen mode Exit fullscreen mode

2. Define the Route with a Parameter: In routes/web.py, add a route that captures the movie ID.

Get('/movie/@id', 'MovieController@show').name('displayMovie')
Enter fullscreen mode Exit fullscreen mode

3. Build the Controller Logic: Open app/http/controllers/MovieController.py.

from app.Movie import Movie
from masonite.views import View
from masonite.request import Request

class MovieController:
    def show(self, view: View, request: Request):
        movie = Movie.find(request.param('id')) # Find movie by ID from URL
        return view.render('movie', {'mov': movie}) # Pass it to the template
Enter fullscreen mode Exit fullscreen mode

4. Build the Movie Template: Populate resources/templates/movie.html with the following structure.

{% raw %}
<!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>
        <!-- Reuse the toolbar from mainpage -->
        <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>

        <!-- Movie Details Section -->
        <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>
{% endraw %}
Enter fullscreen mode Exit fullscreen mode

5. Create the CSS for the Movie Page: Save this as storage/static/style/movie.css.

/* ... (Include all the provided movie.css styles here) ... */
Enter fullscreen mode Exit fullscreen mode

Now, when you click on any movie in the schedule or showcase, you'll be taken to its detailed page!

Wrapping Up

And there you have it! We've built a dynamic, database-driven cinema website from the ground up using the Masonite framework. We've covered routing, controllers, views with Jinja2 templating, user authentication, file uploads, and model-database interaction.

Masonite proves to be an incredibly intuitive and powerful framework, perfect for developers of all skill levels looking to build modern web applications with Python. Its clear documentation and logical structure make development a smooth and enjoyable process.

I hope you found this tutorial helpful. Feel free to extend this project with features like a shopping cart, seat selection, or payment integration!

Happy coding,

Dwayne

Top comments (0)