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')
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.
- Delete the
storage/static/sassandstorage/compiledfolders - Create a new folder:
storage/static/style
Now, let's generate the view for our homepage. In your terminal, run:
craft view mainpage
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>
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 */
}
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'),
Controller: Create the controller using the craft command:
craft controller MainPage
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')
You can now delete the unused WelcomeController.py and its associated view. Let's fire up the server and take a look!
craft serve
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
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
Then, run the migrations to create the user tables:
craft migrate
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, andConfirmController.py, change all instances of'/home'to'/' - In
resources/templates/auth/base.html, do the same - You can now delete
HomeController.pyand its view - In
routes/web.py, remove the route for'/home' - To redirect to the homepage after logout, update the
logoutmethod inLoginController.py:
def logout(self, request: Request, auth: Auth):
auth.logout()
return request.redirect('/')
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 %}
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; }
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
Create a Migration for the movies table:
craft migration create_movies --create movies
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()
Run the migration:
craft migrate
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']
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 %}
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; }
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("/")
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
]
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
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 %}
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 %}
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
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')
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
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 %}
5. Create the CSS for the Movie Page: Save this as storage/static/style/movie.css.
/* ... (Include all the provided movie.css styles here) ... */
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)