Skip to main content
0

มาลองสร้างเว็บ Image Gallery อัปโหลดภาพง่าย ๆ ด้วย Flask

สรุปสั้น ๆ

สำหรับมือใหม่ อยากลองทำ Gallery แสดงภาพของตัวเองที่สามารถอัปโหลดรูปได้ไม่ใช่เรื่องยาก ทำยังไง ไปดู!!!

เขียนโดย
Sirasit Boonklang (Aeff)
Tech and Coding Consultant

บทความนี้ตีพิมพ์ และ เผยแพร่เมื่อ 12 เมษายน 2566

โดยเราสามารถใช้ Flask ที่เป็น Framework ตัวช่วยในการทำเว็บแบบง่าย ๆ สำหรับสาย Python และในการที่เราจะทำเว็บแอปพลิเคชันที่มีการอัปโหลดไฟล์ขึ้นมาได้ใน Flask จะมี Lib ในการช่วยจัดการในการอัปโหลดไฟล์มายังหน้าเว็บนั่นก็คือ Flask-Uploads

การเริ่มต้นใช้งาน Flask-Uploads

เราจะเริ่มทำ Flask Application ที่ให้ผู้ใช้สามารถอัปโหลดไฟล์ไปยังเซิร์ฟเวอร์และแสดงไฟล์ที่อัปโหลดบนหน้าเว็บได้ โดยเราจะมาเริ่มแบบง่าย ๆ

โดยเริ่มจากเปิดโปรเจกต์ขึ้นมา สร้าง Virtual Environments ด้วยคำสั่ง python -m venv venv แล้วทำการ Activate ด้วยคำสั่ง .\venv\Scripts\activate

ทำการติดตั้ง Flask-Uploads และ flask-wtf ด้วยคำสั่ง pip install Flask-Uploads flask-wtf

ทำการสร้างโฟลเดอร์ templates ที่มีไฟล์ index.html และ ไฟล์ app.py ไว้อยู่ในตำแหน่งเดียวกันกับโฟลเดอร์ templates (ห้ามอยู่ใน templates นะ) ตามโครงสร้างของแผนภาพด้านล่างได้เลยนะครับ

Gallery-Flask-Uploads/
├── app.py
└── templates/
└── index.html

เราเริ่มมาดูที่ไฟล์หลักนั่นคือ app.py กันก่อนครับ ส่วนแรกจะเป็นการเรียกใช้งาน Lib ที่เกี่ยวข้องดังนี้

app.py

from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import FileField, SubmitField
from werkzeug.utils import secure_filename
import os
from wtforms.validators import InputRequired
  • Flask เป็นการเรียกใช้เฟิร์มเวิร์ก Flask
  • render_template เรียกใช้ฟังก์ชันสำหรับการแสดงผลหน้าไฟล์ HTML
  • FlaskForm เป็นคลาสจากโมดูล flask_wtf สำหรับสร้างฟอร์ม HTML โดยใช้คลาส Python
  • FileField เป็นคลาสจากโมดูล wtforms ที่สร้าง Input ไฟล์ในแบบฟอร์ม
  • SubmitField เป็นคลาสจากโมดูล wtforms ที่สร้างปุ่มส่งในแบบฟอร์ม
  • secure_filename เป็นฟังก์ชันจากโมดูล Werkzeug ที่ return ชื่อไฟล์เวอร์ชันที่ปลอดภัย
  • os เป็นโมดูลที่เอามาใช้ในการอ่านหรือเขียนลงในระบบ
  • InputRequired เป็นคลาสจากโมดูล wtforms.validators ที่ตรวจสอบว่า Input แบบฟอร์มไม่ว่างเปล่าหรือไม่

ส่วนที่ 2 ในไฟล์จะเป็นการกำหนดค่าต่าง ๆ และ สร้างคลาส FlaskFrom สำหรับการอัปโหลดไฟล์

app = Flask(__name__)
app.config['SECRET_KEY'] = '8OgSq5gCe7'
app.config['UPLOAD_FOLDER'] = 'static/files'

class UploadFileForm(FlaskForm):
    file = FileField("File", validators=[InputRequired()])
    submit = SubmitField("Upload File")
  • app = Flask(name) สร้างอินสแตนซ์ของคลาส Flask
  • app.config[‘SECRET_KEY’] เป็นการสร้าง SECRET_KEY เพื่อใช้กับ session cookies หรือ secure data อื่น
  • app.config[‘UPLOAD_FOLDER’] = ‘static/files’ กำหนดว่าจะจัดเก็บไฟล์ไว้ที่ไหน
  • class UploadFileForm(FlaskForm): คลาสย่อย FlaskForm ใหม่ชื่อ UploadFileForm
  • file = FileField(“File”, validators=[InputRequired()]) สร้าง FileField ที่ให้ User อัปโหลดไฟล์ InputRequired() เป็นตัวตรวจสอบว่าฟิลด์ว่างเปล่าหรือไม่

ส่วนต่อมาจะเป็นการกำหนดการ route ใน Flask app และการทำงานในฟังก์ชัน UploadFile2Web โดยโค้ดการทำงานส่วนสุดท้ายจะมีดังนี้

@app.route('/', methods=['GET', 'POST'])
def UploadFile2Web():
    form = UploadFileForm()
    if form.validate_on_submit():
        file = form.file.data
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file.filename)))

    image_files = os.listdir(app.config['UPLOAD_FOLDER'])
    return render_template('index.html', form=form, image_files=image_files)

if __name__ == '__main__':
    app.run(debug=True)
  • @app.route(‘/’, methods=[‘GET’, ‘POST’]) โดยเราได้มีการกำหนดให้เมื่อมี Request มาที่ Path Root โดยสามารถใช้งานได้ทั้ง Methods GET และ POST โปรแกรมก็จะทำงานที่ฟังก์ชัน UploadFile2Web ()
  • UploadFile2Web() เราได้มีการสั่งให้ render ไฟล์ index.html ออกมาแสดงเมื่อมีการเรียกมาที่ route นี้ โดยใช้คำสั่ง render_template(‘index.html’, form=form, image_files=image_files) นอกจากที่ Flask Application ของเราจะแสดงหน้า index.html ออกมา เรายังมีการส่งตัวแปรไปยังไฟล์ index.html ด้วยนั่นคือ ตัวแปร form และ image_files
  • เมื่อเราได้ทำการสร้าง instance ของ UploadFileForm() เก็บไว้ในตัวแปร form แล้ว เราก็มาทำการเช็คดูว่า form ได้มีการ submit และ validate แล้วหรือไม่
  • ถ้าใช่จะทำการ Save ไฟล์ไปยัง Server โดยใช้คำสั่ง file.save(os.path.join(app.config[‘UPLOAD_FOLDER’], secure_filename(file.filename)))
  • สุดท้ายเราก็ไปเอาไฟล์ภาพมาแสดง โดยเอาไฟล์จาก Path ที่เก็บไว้ในตัวแปร UPLOAD_FOLDER ด้วยคำสั่ง os.listdir(app.config[‘UPLOAD_FOLDER’]) แล้วเก็บไว้ในตัวแปร image_files
  • แล้วตอนที่ index.html ถูก render ออกมาก็จะนำภาพทั้งหมดที่อยู่ใน Path ที่เราเก็บไว้มาแสดงในหน้าเว็บ

และตอนนี้เราก็ได้ทำการเขียนโค้ดส่วนของการทำงานการจัดการไฟล์ และ การอัปโหลดไฟล์ไปยัง Server ได้แล้วในไฟล์ app.py โดยโค้ดแบบเต็ม ๆ เราจะได้ในลักษณะนี้

app.py (Full Code)

from flask import Flask, render_template, url_for
from flask_wtf import FlaskForm
from wtforms import FileField, SubmitField
from werkzeug.utils import secure_filename
import os
from wtforms.validators import InputRequired

app = Flask(__name__)
app.config['SECRET_KEY'] = '8OgSq5gCe7'
app.config['UPLOAD_FOLDER'] = 'static/files'

class UploadFileForm(FlaskForm):
    file = FileField("File", validators=[InputRequired()])
    submit = SubmitField("Upload File")

@app.route('/', methods=['GET', 'POST'])
def UploadFile2Web ():
    form = UploadFileForm()
    if form.validate_on_submit():
        file = form.file.data
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file.filename)))

    image_files = os.listdir(app.config['UPLOAD_FOLDER'])
    return render_template('index.html', form=form, image_files=image_files)

if __name__ == '__main__':
    app.run(debug=True)

ต่อมาเราก็มาสร้างหน้าเว็บโดยไปที่ไฟล์ index.html ในโฟลเดอร์ templates โดยโค้ดเริ่มต้นของไฟล์ HTML จะเป็นแบบนี้

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image Gallery</title>
</head>
<body>
  <h1>Upload Image</h1>
<form method='POST' enctype='multipart/form-data'>
        {{form.hidden_tag()}}
        {{form.file()}}
        {{form.submit()}}
</form>

<p>Total images: {{ image_files|length }}</p>
<div class="gallery">
    {% for image in image_files %}
        <img src="{{ url_for('static', filename='files/' + image) }}" alt="{{ image }}" onclick="openModal('{{ url_for('static', filename='files/' + image) }}')">
    {% endfor %}
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image Gallery</title>
</head>
<body>
  <h1>Upload Image</h1>
<form method='POST' enctype='multipart/form-data'>
        {{form.hidden_tag()}}
        {{form.file()}}
        {{form.submit()}}
</form>

<p>Total images: {{ image_files|length }}</p>
<div class="gallery">
    {% for image in image_files %}
        <img src="{{ url_for('static', filename='files/' + image) }}" alt="{{ image }}" onclick="openModal('{{ url_for('static', filename='files/' + image) }}')">
    {% endfor %}
</div>
</body>
</html>

โดยผมขอข้าม HTML เบื้องต้นไปแต่จะมาโฟกัสในส่วนที่รับค่าและทำงานร่วมกับ Flask นะครับโดยส่วนหลัก ๆ จะอยู่ภายในแท็ก Body

<h1>Upload Image</h1>
<form method='POST' enctype='multipart/form-data'>
        {{form.hidden_tag()}}
        {{form.file()}}
        {{form.submit()}}
</form>
  • ส่วนแรกเราจะบอกว่า HTML Form นี้เป็นส่วนอัปโหลดภาพโดยใช้ <h1>Upload Image</h1>
  • <form method=’POST’ enctype=’multipart/form-data’> บอกว่าฟอร์มจะถูกส่งโดยใช้ Methods HTTP POST และข้อมูลในฟอร์มจะถูกเข้ารหัสโดยใช้ประเภทการเข้ารหัส “multipart/form-data”
  • {{form.hidden_tag()}} เป็นฟังก์ชันพิเศษใน Flask ที่สร้างช่อง Input ที่ซ่อนอยู่เพื่อป้องกันการโจมตี CSRF
  • {{form.file()}} สร้างฟิลด์สำหรับรับ Input ไฟล์ที่จะอัปโหลด
  • {{form.submit()}} สร้างปุ่มที่คลิกได้ซึ่งส่งแบบฟอร์มไปยังเซิร์ฟเวอร์

ส่วนต่อมาจะเป็นส่วนในการแสดงจำนวนและรูปทั้งหมดที่มีอยู่ในระบบ

<p>Total images: {{ image_files|length }}</p>
<div class="gallery">
{% for image in image_files %}
<img src="{{ url_for('static', filename='files/' + image) }}" alt="{{ image }}" onclick="openModal('{{ url_for('static', filename='files/' + image) }}')">
{% endfor %}
</div>
<p>Total images: {{ image_files|length }}</p>
<div class="gallery">
{% for image in image_files %}
<img src="{{ url_for('static', filename='files/' + image) }}" alt="{{ image }}" onclick="openModal('{{ url_for('static', filename='files/' + image) }}')">
{% endfor %}
</div>
  • {{ image_files|length }} คือ Jinja2 expression ที่เอาไว้ดูว่าความยาวของ list ที่ชื่อว่า image_file มีเท่าไหร่แล้วเราก็นำค่านี้มาแสดงเป็นจำนวนรูปที่มีในระบบ
  • {% for image in image_files %}เป็น Jinja2 loop ที่วนเอาแต่ละภาพใน list ของ image_file ออกมา
  • <img src=”{{ url_for(‘static’, filename=’files/’ + image) }}” alt=”{{ image }}” onclick=”openModal(‘{{ url_for(‘static’, filename=’files/’ + image) }}’)”> เป็นส่วนที่แสดงรูปภาพในแกลเลอรี โดยไปเอารูปภาพจากโฟลเดอร์ static/file รูปแต่ละรูปออกมาโดยใช้ url_for นอกจากนั้นเรายังมีการสร้าง modal สำหรับกดเข้าไปดูภาพเต็มของแต่ละภาพอีกด้วย โดยส่วนของ modal จะมีการนำ JavaScript มาช่วยโดยมีโค้ดการทำงานดังนี้
<div id="myModal" class="modal">
<span class="close" onclick="closeModal()">&times;</span>
<img class="modal-content" id="img01">
</div>
    <script>
        var modal = document.getElementById("myModal");
        var modalImg = document.getElementById("img01");

        function openModal(imgSrc) {
            modal.style.display = "block";
            modalImg.src = imgSrc;
        }

        function closeModal() {
            modal.style.display = "none";
        }
    </script>
<div id="myModal" class="modal">
<span class="close" onclick="closeModal()">&times;</span>
<img class="modal-content" id="img01">
</div>
    <script>
        var modal = document.getElementById("myModal");
        var modalImg = document.getElementById("img01");

        function openModal(imgSrc) {
            modal.style.display = "block";
            modalImg.src = imgSrc;
        }

        function closeModal() {
            modal.style.display = "none";
        }
    </script>

ตอนนี้หน้า Gallery แบบง่าย ๆ ไว ๆ ก็เสร็จไปแล้วโดยโค้ดในส่วนไฟล์ index.html แบบเต็ม ๆ จะเป็นดังนี้

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
<title>Home</title>
</head>
<body>
<h1>Image Gallery</h1>
 <form method='POST' enctype='multipart/form-data'>
 {{form.hidden_tag()}}
 {{form.file()}}
 {{form.submit()}}
 </form>
 <p>Total images: {{ image_files|length }}</p>
<div class="gallery">
{% for image in image_files %}
<img src="{{ url_for('static', filename='files/' + image) }}" alt="{{ image }}" onclick="openModal('{{ url_for('static', filename='files/' + image) }}')">
{% endfor %}
</div>
 <!-- The Modal -->
<div id="myModal" class="modal">
<span class="close" onclick="closeModal()">&times;</span>
<img class="modal-content" id="img01">
</div>
 <script>
var modal = document.getElementById("myModal");
var modalImg = document.getElementById("img01");
 function openModal(imgSrc) {
modal.style.display = "block";
modalImg.src = imgSrc;
} 
function closeModal() {
modal.style.display = "none";
}
</script>

</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
<title>Home</title>
</head>
<body>
<h1>Image Gallery</h1>
 <form method='POST' enctype='multipart/form-data'>
 {{form.hidden_tag()}}
 {{form.file()}}
 {{form.submit()}}
 </form>
 <p>Total images: {{ image_files|length }}</p>
<div class="gallery">
{% for image in image_files %}
<img src="{{ url_for('static', filename='files/' + image) }}" alt="{{ image }}" onclick="openModal('{{ url_for('static', filename='files/' + image) }}')">
{% endfor %}
</div>
 <!-- The Modal -->
<div id="myModal" class="modal">
<span class="close" onclick="closeModal()">&times;</span>
<img class="modal-content" id="img01">
</div>
 <script>
var modal = document.getElementById("myModal");
var modalImg = document.getElementById("img01");
 function openModal(imgSrc) {
modal.style.display = "block";
modalImg.src = imgSrc;
} 
function closeModal() {
modal.style.display = "none";
}
</script>

</body>
</html>

เมื่อทั้งโค้ดส่วน app.py และ index.html เสร็จแล้วเราสามารถรันโปรแกรมด้วยคำสั่ง flask runแล้วโปรแกรมจะทำงานที่ localhost:5000

โอเคครับ ตอนนี้เราได้หน้าเว็บแบบไว ๆ ที่สามารถลองกดอัปโหลดไฟล์ขึ้นมาได้แล้วนะครับ แต่ปัญหาคือหน้าเว็บยังไม่สวย ภาพก็ใหญ่ 😅 เราเลยต้องใช้ CSS เข้ามาช่วย

สำหรับการเพิ่ม CSS เข้ามาในไฟล์ index.html ใน Flask เราจะใช้คำสั่ง <link rel=”stylesheet” href=”{{ url_for(‘static’, filename=’css/style.css’) }}”> ใส่ไว้ในแท็ก <head></head>

<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
   <title>Image Gallery</title>
</head>

แล้วโค้ดของ CSS ของผมจะอยู่ที่ Path static/css/style.css

Gallery-Flask-Project/
├── app.py
├── static/
│ ├── css/
│ │ └── style.css
│ └── files/
└── templates/
└── index.html

โดยโค้ดในไฟล์ style.css ของผมมีดังนี้

/* Set default styles for the body */
body {
    font-family: 'Roboto', sans-serif;
    text-align: center;
    background-color: #f0f0f0;
}
/* Create a gallery grid */
.gallery {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    padding: 2px;
}
/* Style the gallery images */
.gallery img {
    margin: 5px;
    width: calc(25%);
    height: auto;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    border: 1px solid #f0f0f0;
    border-radius: 10px;
    transition: 0.3s;
}
/* On hover, increase the border size of the images */
.gallery img:hover {
    border: 5px solid #777;
}
/* Create a modal to display full-sized images */
.modal {
    display: none;
    position: fixed;
    z-index: 100;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0, 0, 0, 0.8);
    display: flex;
    justify-content: center;
    align-items: center;
}
/* Style the full-sized image */
.modal-content {
    margin: 0 auto;
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}
/* Style the close button for the modal */
.close {
    position: absolute;
    top: 15px;
    right: 35px;
    color: #f1f1f1;
    font-size: 40px;
    font-weight: bold;
    transition: 0.3s;
}
.close:hover, .close:focus {
    color: #bbb;
    text-decoration: none;
    cursor: pointer;
}
/* Style the upload form */
form {
    display: inline-block;
    margin-bottom: 20px;
}

หลังจากนั้นกดอีกครั้งทำการรันโปรแกรมอีกครั้งด้วยคำสั่ง flask run และอัปโหลดรูปขึ้นไปก็จะมีหน้าของเว็บแบบนี้

หากใครอยากลองเอาโปรเจกต์นี้ไปปรับแต่งหรือลองใช้งานก็สามารถมาโคลนที่ Github Repo นี้ได้เลยครับ 😊 aeff60/Image-Gallery-Flask (github.com)

สำหรับรายละเอียดของ Library ที่ใช้ในโปรเจกต์นี้สามารถอ่านเพิ่มเติมได้ที่ลิงก์นี้นะครับ

Flask-Uploads: https://flask-uploads.readthedocs.io/en/latest/

Flask WTF: https://flask-wtf.readthedocs.io/en/1.0.x/

ระบบฝึกทักษะ การเขียนโปรแกรม

ที่พร้อมตรวจผลงานคุณ 24 ชั่วโมง

  • โจทย์ปัญหากว่า 200 ข้อ ที่รอท้าทายคุณอยู่
  • รองรับ 9 ภาษาโปรแกรมหลัก ไม่ว่าจะ Java, Python, C ก็เขียนได้
  • ใช้งานได้ฟรี ! ครบ 20 ข้อขึ้นไป รับ Certificate ไปเลย !!
เข้าใช้งานระบบ DevLab ฟรี !เรียนรู้เพิ่มเติม

เรียนรู้ไอที “อัพสกิลเขียนโปรแกรม” จากตัวจริง
ปั้นให้คุณเป็น คนสายไอทีระดับมืออาชีพ

6

แนะนำสำหรับคุณ

Close Menu

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • คุกกี้ที่จำเป็น
    เปิดใช้งานตลอด

    ประเภทของคุกกี้มีความจำเป็นสำหรับการทำงานของเว็บไซต์ เพื่อให้คุณสามารถใช้ได้อย่างเป็นปกติ และเข้าชมเว็บไซต์ คุณไม่สามารถปิดการทำงานของคุกกี้นี้ในระบบเว็บไซต์ของเราได้
    รายละเอียดคุกกี้

  • คุกกี้สำหรับการติดตามทางการตลาด

    ประเภทของคุกกี้ที่มีความจำเป็นในการใช้งานเพื่อการวิเคราะห์ และ นำเสนอโปรโมชัน สินค้า รวมถึงหลักสูตรฟรี และ สิทธิพิเศษต่าง ๆ คุณสามารถเลือกปิดคุกกี้ประเภทนี้ได้โดยไม่ส่งผลต่อการทำงานหลัก เว้นแต่การนำเสนอโปรโมชันที่อาจไม่ตรงกับความต้องการ
    รายละเอียดคุกกี้

บันทึกการตั้งค่า