Skip to content

Commit 9b9ef9b

Browse files
authored
Merge pull request #378 from shreyasvedpathak/main
Generate certificate - Flask web app using PILLOW
2 parents dacfc83 + 64443be commit 9b9ef9b

File tree

12 files changed

+240
-0
lines changed

12 files changed

+240
-0
lines changed

certificate_generator/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Certificate Generator
2+
3+
## Table of Contents
4+
5+
- [Folder Structure](#folder-structure)
6+
- [Requirements](#requirements)
7+
- [Approach](#approach)
8+
9+
## Folder Structure
10+
11+
```plain
12+
│ README.md
13+
│ requirements.txt
14+
│ run.py # To run the web app
15+
16+
└───app
17+
│ routes.py # Contains routes
18+
│ utils.py # Contains generate_certifcate() function
19+
│ __init__.py
20+
21+
├───static # Contains necessary static files
22+
│ └───certificates
23+
│ ├───generated # Stores generated certificates
24+
│ │ Shreyas Vedpathak.png # sample certificate
25+
│ │
26+
│ └───template # Contains neccesary required files
27+
│ Sanchez-Regular.ttf # Font
28+
│ template.png # Template for certificate
29+
30+
└───templates # HTML files for frontend
31+
certificate.html
32+
download.html
33+
home.html
34+
```
35+
36+
## Requirements
37+
38+
- Flask==1.1.2
39+
- Pillow==8.3.1
40+
41+
## Approach
42+
43+
1. We get the user name and pr number from the form in `certificate.html`.
44+
2. We use the received information to generate and save the certificate using `generate_certificate()` function in `app/utils.py`.
45+
3. Finally, we render that certificate using `download.html` and we provide the option of downloading, and generate again.

certificate_generator/app/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from flask import Flask
2+
3+
app = Flask(__name__)
4+
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
5+
6+
from app import routes

certificate_generator/app/routes.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# External Imports
2+
import os
3+
from flask import render_template, request, send_file
4+
5+
# Internal Imports
6+
from app import app
7+
from app.utils import generate_certificate
8+
9+
10+
@app.after_request
11+
def add_header(r):
12+
"""
13+
Prevents caching, which will make sure old files are not sent.
14+
"""
15+
r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
16+
r.headers["Pragma"] = "no-cache"
17+
r.headers["Expires"] = "0"
18+
r.headers['Cache-Control'] = 'public, max-age=0'
19+
return r
20+
21+
22+
@app.route('/', methods=['GET', 'POST'])
23+
def home():
24+
'''home
25+
Renders home page
26+
'''
27+
return render_template("certificate.html")
28+
29+
30+
@app.route('/render', methods=['POST'])
31+
def render_certificate():
32+
"""
33+
Get's information from user and generates
34+
the certificate using generate_certificate function
35+
"""
36+
if request.method == "POST":
37+
file_name = generate_certificate(
38+
request.form['name'],
39+
request.form['pr_num'])
40+
return render_template('download.html', file_name=file_name)
41+
42+
43+
@app.route('/download_certificate', methods=['GET'])
44+
def download():
45+
"""
46+
Download the generated certificate
47+
"""
48+
if request.method == "GET":
49+
filename = request.args.get("filename")
50+
filepath = os.path.join("static/certificates/generated", filename)
51+
return send_file(filepath, as_attachment=True, cache_timeout=0,
52+
attachment_filename=filename)
Loading
Binary file not shown.
Loading
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% extends 'home.html' %}
2+
3+
{% block content %}
4+
<div class="container w-50">
5+
<form action="/render" method="post">
6+
<div class="form-floating mb-3">
7+
<input name="name" type="text" class="form-control" id="floatingInput" placeholder="Enter Name">
8+
<label for="floatingInput">Enter Name</label>
9+
</div>
10+
<div class="input-group mb-3">
11+
<span class="input-group-text" id="basic-addon1">#</span>
12+
<input name="pr_num" type="number" class="form-control" placeholder="PR number" aria-label="PR number"
13+
aria-describedby="basic-addon1">
14+
</div>
15+
<div class="my-4">
16+
<a href="/render">
17+
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
18+
</a>
19+
</div>
20+
</form>
21+
</div>
22+
23+
{% endblock content %}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{% extends 'home.html' %}
2+
3+
{% block content %}
4+
<div class="container w-50">
5+
<div>
6+
<img src="{{url_for('static', filename='certificates/generated/' + file_name)}}" class="img-fluid rounded shadow" alt="certificate">
7+
</div>
8+
<div class="d-flex justify-content-evenly my-4">
9+
<a href="/download_certificate?filename={{file_name}}">
10+
<button type="button" class="btn btn-success">Download</button>
11+
</a>
12+
<a href="/">
13+
<button type="button" class="btn btn-dark">Generate again</button>
14+
</a>
15+
</div>
16+
</div>
17+
{% endblock content %}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<!-- Required meta tags -->
6+
<meta charset="utf-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
9+
<!-- Bootstrap CSS -->
10+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
11+
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
12+
13+
<title>Certificate Generator</title>
14+
</head>
15+
16+
<body>
17+
<div class="container text-center w-70 my-4">
18+
<h1>Certificate Generator</h1>
19+
</div>
20+
21+
{% block content %}
22+
23+
{% endblock content %}
24+
25+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
26+
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
27+
crossorigin="anonymous"></script>
28+
29+
</body>
30+
31+
</html>

certificate_generator/app/utils.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# External Imports
2+
from PIL import Image, ImageFont, ImageDraw
3+
import os
4+
5+
# Internal Imports
6+
from app import app
7+
8+
9+
def generate_certificate(name, pr_num):
10+
'''generate_certificate Generates certificate using base template
11+
12+
Args:
13+
name (string): Name to be displayed on certificate
14+
pr_num (integer): PR number to be displayed on certificate
15+
'''
16+
# Path to template certificate
17+
template_dir = os.path.join(app.root_path, "static/certificates/template")
18+
template_filename = "template.png"
19+
20+
# Path to save the certificate
21+
output_dir = os.path.join(app.root_path, "static",
22+
"certificates/generated/")
23+
output_filename = f"{name}.png"
24+
25+
# Load the template file
26+
img = Image.open(os.path.join(template_dir, template_filename))
27+
draw = ImageDraw.Draw(img)
28+
29+
# Text to put on the certificate
30+
event_name = "Hacktoberfest"
31+
contributed_at = "Automation scripts"
32+
msg = f"""for taking part in {event_name} and
33+
contribution in #{pr_num} at {contributed_at}.
34+
"""
35+
# Load fonts
36+
name_font = ImageFont.truetype(os.path.join(
37+
template_dir, "Sanchez-Regular.ttf"), 150)
38+
msg_font = ImageFont.truetype(os.path.join(
39+
template_dir, "Sanchez-Regular.ttf"), 70)
40+
41+
# Insert text (Name)
42+
draw.text((1000, 1390), name, (51, 213, 172), font=name_font)
43+
# Insert text (Message)
44+
draw.text((1000, 1650), msg, (14, 69, 115), font=msg_font)
45+
46+
# Calculate the width of the texts. We will need it to
47+
# draw the underline. As the pr number width can vary we cannot
48+
# hardcode the coordinates of the text to be underlined.
49+
# So we find the width of the pr_num and then calculate
50+
# the starting position of the underlined text.
51+
twidth, theight = draw.textsize(contributed_at, font=msg_font)
52+
prwidth, prheight = draw.textsize(pr_num, font=msg_font)
53+
54+
lx = 1560 + prwidth + 120
55+
ly = 1810
56+
draw.line((lx, ly, lx + twidth, ly), fill=(14, 69, 115), width=5)
57+
58+
# Save certificate
59+
img.save(os.path.join(output_dir, output_filename))
60+
# Return filename, required for rendering the certificate
61+
return output_filename

0 commit comments

Comments
 (0)