Skip to main content

สรุปสั้น ๆ

สำหรับมือใหม่ อยากลองทำ 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 ฟรี !เรียนรู้เพิ่มเติม

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

BorntoDev

Author BorntoDev

BorntoDev Co., Ltd.

More posts by BorntoDev

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

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

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

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

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

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

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

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