📱 Put It on the School Wifi · เอาขึ้นไวไฟโรงเรียน · 放到学校的 wifi 上

Step by step, in Python. Take the homework app you already made, and let the whole class open it on their phones. Same data, second face. · ทีละขั้นตอน ด้วย Python เอาแอปการบ้านที่คุณทำไว้แล้ว มาให้ทั้งห้องเปิดดูบนมือถือได้ ข้อมูลชุดเดิม แค่เปลี่ยนหน้าตา · 用 Python 一步一步来。把你已经做好的作业程序,变成全班都能在手机上打开的东西。同一份数据,换一张脸。

http://192.168.1.42:5000
Homework
Student name
What is the homework?
Add
Ploy Unit 3 worksheet Done
O'Brien Write 5 sentences Done
Ploy Read page 40

This is what a student sees on their phone. It runs on the teacher's computer, on the school's own wifi. นี่คือสิ่งที่นักเรียนเห็นบนมือถือ มันรันอยู่บนคอมพิวเตอร์ของครู บนไวไฟของโรงเรียนเอง 这就是学生在手机上看到的样子。它跑在老师的电脑上,用的是学校自己的 wifi。

🇬🇧 English

You need the homework app first. This page does not make a new database. It opens the same one.

That is the whole point. Your data lives in one place. The terminal app and the web page are just two ways of looking at it.

Add a job on your phone, then type python3 homework.py list in the terminal. It is there.

🇹🇭 ไทย

คุณต้องทำแอปการบ้านก่อน หน้านี้ไม่ได้สร้างฐานข้อมูลใหม่ แต่เปิดฐานข้อมูลเดิม

นั่นคือหัวใจทั้งหมด ข้อมูลของคุณอยู่ที่เดียว ส่วนแอปใน terminal กับหน้าเว็บ เป็นแค่สองวิธีในการมองมัน

ลองเพิ่มงานจากมือถือ แล้วพิมพ์ python3 homework.py list ใน terminal ดู มันอยู่ตรงนั้น

🇨🇳 中文

你要先做完作业程序。这一页不会建新的数据库,它打开的是同一个

这就是重点。你的数据只放在一个地方,终端程序和网页只是看它的两种方式。

在手机上加一份作业,然后在终端里敲 python3 homework.py list。它就在那里。

0 Get ready · เตรียมตัว · 做好准备

🇬🇧 English

This time there is something to install. Flask turns Python into a web server.

Work in the same folder as homework.py, so both programs see the same homework.db.

🇹🇭 ไทย

คราวนี้มีของที่ต้องติดตั้ง Flask จะเปลี่ยน Python ให้เป็นเว็บเซิร์ฟเวอร์

ให้ทำงานในโฟลเดอร์เดียวกับ homework.py เพื่อให้ทั้งสองโปรแกรมเห็น homework.db ไฟล์เดียวกัน

🇨🇳 中文

这次确实要装东西了。Flask 把 Python 变成一个网页服务器。

在和 homework.py 同一个文件夹里做,这样两个程序看到的是同一个 homework.db

pip3 install flask
cd ~/Documents/Code/homework      # the same folder as homework.py
vim board.py

1 A web page out of Python · หน้าเว็บที่ออกมาจาก Python · 用 Python 变出一个网页

🇬🇧 English

Six lines and you have a website.

@app.route("/") means: when someone asks for the front page, run this function. Whatever it returns is what the browser shows.

A route is just an address joined to a function.

🇹🇭 ไทย

หกบรรทัดก็ได้เว็บไซต์แล้ว

@app.route("/") แปลว่า เมื่อมีคนขอหน้าแรก ให้รันฟังก์ชันนี้ อะไรก็ตามที่มันคืนค่ากลับมา คือสิ่งที่เบราว์เซอร์จะแสดง

route ก็คือที่อยู่ที่ผูกไว้กับฟังก์ชัน แค่นั้นเอง

🇨🇳 中文

六行,你就有一个网站了。

@app.route("/") 的意思是:有人来要首页时,就跑这个函数。它返回什么,浏览器就显示什么。

route(路由)就是一个地址接上一个函数,就这么简单。

board.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def board():
    return "Hello from the classroom computer!"

app.run(port=5000)
▶ RUN IT python3 board.py, then open http://localhost:5000. Press Ctrl+C to stop it. python3 board.py แล้วเปิด http://localhost:5000 กด Ctrl+C เพื่อหยุด python3 board.py,然后打开 http://localhost:5000。按 Ctrl+C 停止。

2 Real HTML, and the data · HTML จริง และข้อมูลจริง · 真正的 HTML 和数据

🇬🇧 English

Returning text is no good for a real page. Put the HTML in its own file, inside a folder called templates. Flask looks there by itself.

render_template loads that file and hands it your data. Inside the file, {% for %} makes one row for each job.

The database query is exactly the same SELECT you wrote in the terminal app. Nothing new to learn.

🇹🇭 ไทย

การคืนค่าเป็นข้อความเฉย ๆ ใช้ทำหน้าเว็บจริงไม่ได้ ให้เอา HTML ไปไว้ในไฟล์ของมันเอง ในโฟลเดอร์ชื่อ templates แล้ว Flask จะไปหาเอง

render_template จะโหลดไฟล์นั้นแล้วส่งข้อมูลของคุณเข้าไป ในไฟล์นั้น {% for %} จะสร้างหนึ่งแถวต่อหนึ่งงาน

คำสั่งดึงข้อมูลคือ SELECT ตัวเดียวกับที่คุณเขียนในแอป terminal เป๊ะ ๆ ไม่มีอะไรใหม่ต้องเรียน

🇨🇳 中文

只返回一段文字做不了真正的网页。把 HTML 放进它自己的文件里,放在一个叫 templates 的文件夹中,Flask 会自己去那里找。

render_template 加载那个文件,并把你的数据交给它。在文件里,{% for %} 会为每份作业生成一行。

查数据库用的就是你在终端程序里写过的那条 SELECT,一模一样。没有新东西要学。

board.py
jobs = conn.execute(
    "SELECT id, student, title, due, done FROM jobs ORDER BY done, id"
).fetchall()
return render_template("board.html", jobs=jobs)
templates/board.html
<ul>
  {% for id, student, title, due, done in jobs %}
  <li><b>{{ student }}</b> {{ title }}</li>
  {% endfor %}
</ul>

3 A form: GET asks, POST changes · ฟอร์ม: GET ถาม POST เปลี่ยน · 表单:GET 是问,POST 是改

🇬🇧 English

Browsers talk in two main ways. GET means "show me something". POST means "here is some data, change something".

Reading uses GET. Adding homework uses POST. Keep them apart and the whole web makes more sense.

request.form holds what the person typed. The names come from the name= in the HTML.

🇹🇭 ไทย

เบราว์เซอร์คุยกันสองแบบหลัก ๆ GET แปลว่า "ขอดูหน่อย" ส่วน POST แปลว่า "นี่ข้อมูล ช่วยเปลี่ยนให้ที"

การอ่านใช้ GET การเพิ่มการบ้านใช้ POST แยกสองอย่างนี้ให้ชัด แล้วเว็บทั้งใบจะเข้าใจง่ายขึ้นเยอะ

request.form เก็บสิ่งที่ผู้ใช้พิมพ์ ชื่อของมันมาจาก name= ใน HTML

🇨🇳 中文

浏览器主要用两种方式说话。GET 是"给我看看",POST 是"这是一些数据,帮我改一下"。

读用 GET,加作业用 POST。把这两件事分清楚,整个网络都会变得好懂很多。

request.form 装着用户输入的东西。里面的名字来自 HTML 里的 name=

@app.route("/", methods=["GET", "POST"])
def board():
    if request.method == "POST":
        student = request.form.get("student", "").strip()
        title   = request.form.get("title", "").strip()
        if student and title:
            conn.execute("INSERT INTO jobs (student, title) VALUES (?, ?)",
                         (student, title))
            conn.commit()

4 Refresh sends it twice · กดรีเฟรชแล้วส่งซ้ำ · 一刷新就发两次

🇬🇧 English

Add a job, then press refresh. The same job is added again. Refresh again, again. This is a famous bug and you just made it.

Why: the last thing the browser did was a POST. Refresh means "do that again", so it posts again.

The cure is one line. After a POST, send the browser away to a fresh GET. Now refresh only reloads a page, which is safe.

This trick has a name: redirect after post. Real websites all do it.

🇹🇭 ไทย

ลองเพิ่มงานหนึ่งชิ้น แล้วกดรีเฟรช งานเดิมจะถูกเพิ่มอีกครั้ง รีเฟรชอีก ก็เพิ่มอีก นี่คือบั๊กชื่อดัง และคุณเพิ่งสร้างมันขึ้นมา

สาเหตุคือ สิ่งสุดท้ายที่เบราว์เซอร์ทำคือ POST การรีเฟรชแปลว่า "ทำอันนั้นอีกที" มันจึง POST ซ้ำ

วิธีแก้คือบรรทัดเดียว หลังจาก POST ให้ส่งเบราว์เซอร์ไปที่ GET ใหม่ ทีนี้การรีเฟรชก็แค่โหลดหน้าใหม่ ซึ่งปลอดภัย

เทคนิคนี้มีชื่อว่า redirect after post เว็บไซต์จริงทำแบบนี้กันหมด

🇨🇳 中文

加一份作业,然后按刷新。同一份作业被又加了一次。再刷新,再加。这是个很有名的 bug,你刚刚亲手做出来了。

原因是:浏览器最后做的事是 POST。刷新的意思是"再做一遍那件事",于是它又 POST 了一次。

解决办法只要一行。POST 之后,把浏览器打发去一个新的 GET。这样刷新就只是重新加载页面,很安全。

这个做法有个名字:redirect after post。真正的网站都这么做。

+ at the end of the POST part, before you read the rows+ ท้ายส่วนของ POST ก่อนจะอ่านข้อมูลออกมา+ 在 POST 那段的最后,读数据之前
        return redirect(url_for("board"))
▶ TRY THE BUG Take that line out. Add a job. Refresh five times. Count the rows. Then put it back. ลองเอาบรรทัดนั้นออก เพิ่มงานหนึ่งชิ้น แล้วกดรีเฟรชห้าครั้ง นับแถวดู แล้วค่อยใส่กลับ 把那一行删掉。加一份作业,刷新五次,数数有几行。然后再加回去。

5 Make it work on a phone · ทำให้ใช้บนมือถือได้ · 让它在手机上好用

🇬🇧 English

Without one line in your HTML, phones pretend to be a wide desktop and shrink everything to unreadable.

That line tells the phone: use your real width.

Then make the buttons big. A finger is much bigger than a mouse pointer. Around 44 pixels tall is comfortable.

🇹🇭 ไทย

ถ้าไม่มีบรรทัดหนึ่งใน HTML มือถือจะแกล้งทำเป็นจอคอมกว้าง ๆ แล้วย่อทุกอย่างจนอ่านไม่ออก

บรรทัดนั้นบอกมือถือว่า ให้ใช้ความกว้างจริงของตัวเอง

จากนั้นทำปุ่มให้ใหญ่ นิ้วมือใหญ่กว่าลูกศรเมาส์เยอะ สูงราว 44 พิกเซลกำลังสบาย

🇨🇳 中文

HTML 里少了一行,手机就会假装自己是很宽的电脑屏幕,把所有东西缩到看不清。

那一行告诉手机:用你自己真正的宽度。

然后把按钮做大。手指比鼠标指针大多了,高度 44 像素左右比较舒服。

templates/board.html
<meta name="viewport" content="width=device-width,initial-scale=1">

6 Everyone on the wifi · ทุกคนบนไวไฟเดียวกัน · wifi 上的每个人

🇬🇧 English

So far only your computer can open it. localhost means "this machine and nobody else".

host="0.0.0.0" means "listen to the whole room". Now anyone on the same wifi can reach you.

They need your address. It looks like 192.168.1.42. Rather than teach three commands for three systems, let the program print it.

Students then type http://192.168.1.42:5000 into their phone.

🇹🇭 ไทย

ถึงตอนนี้มีแค่เครื่องคุณที่เปิดได้ คำว่า localhost แปลว่า "เครื่องนี้เท่านั้น ไม่มีใครอื่น"

host="0.0.0.0" แปลว่า "ฟังทั้งห้อง" ทีนี้ใครก็ตามที่อยู่บนไวไฟเดียวกันก็เข้าถึงคุณได้

เขาต้องรู้ที่อยู่ของคุณ ซึ่งหน้าตาประมาณ 192.168.1.42 แทนที่จะสอนคำสั่งสามแบบสำหรับสามระบบ ให้โปรแกรมพิมพ์ออกมาเองเลยดีกว่า

แล้วนักเรียนก็พิมพ์ http://192.168.1.42:5000 ลงในมือถือ

🇨🇳 中文

到现在为止只有你的电脑能打开它。localhost 的意思是"只有这台机器,别人不行"。

host="0.0.0.0" 的意思是"听整个房间"。这样同一个 wifi 上的人都能连到你。

他们需要你的地址,长得像 192.168.1.42。与其教三个系统的三条命令,不如让程序自己打印出来。

然后学生在手机上输入 http://192.168.1.42:5000

import socket

def my_address():
    probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        probe.connect(("8.8.8.8", 80))   # nothing is sent; it just picks a route
        return probe.getsockname()[0]
    except Exception:
        return "127.0.0.1"
    finally:
        probe.close()

print("On the wifi:  http://%s:5000" % my_address())
app.run(host="0.0.0.0", port=5000)

7 What this is NOT · สิ่งที่นี่ไม่ใช่ · 这东西不是什么

🇬🇧 English

Two honest warnings. Knowing the edge of a tool is part of knowing the tool.

This server is for building, not for the internet. Flask says so when it starts. On the school wifi for one class, it is fine. Do not put it on the open internet.

There is no login. Anyone on the wifi can type any name, and tick anyone's homework. That is fine for homework. It is not fine for marks or anything private.

One good thing you get free: the template puts < and > in safely. If a student types <script> as their name, it shows as text instead of running. We tested that.

🇹🇭 ไทย

คำเตือนตรง ๆ สองข้อ การรู้ขอบเขตของเครื่องมือก็เป็นส่วนหนึ่งของการรู้จักเครื่องมือนั้น

เซิร์ฟเวอร์ตัวนี้มีไว้สำหรับพัฒนา ไม่ใช่สำหรับอินเทอร์เน็ต Flask ก็บอกแบบนั้นตอนเริ่มทำงาน ใช้บนไวไฟโรงเรียนในคาบเรียนหนึ่งคาบนั้นโอเค แต่อย่าเอาไปวางบนอินเทอร์เน็ตจริง

ไม่มีระบบล็อกอิน ใครก็ตามที่อยู่บนไวไฟพิมพ์ชื่ออะไรก็ได้ และติ๊กงานของใครก็ได้ ซึ่งโอเคสำหรับการบ้าน แต่ไม่โอเคสำหรับคะแนนหรือเรื่องส่วนตัว

มีข้อดีหนึ่งที่ได้มาฟรี ๆ คือ template จะใส่ < กับ > ให้อย่างปลอดภัย ถ้านักเรียนพิมพ์ <script> เป็นชื่อ มันจะแสดงเป็นข้อความ ไม่ถูกรัน เราทดสอบแล้ว

🇨🇳 中文

两个老实话。知道一个工具的边界,也是了解这个工具的一部分。

这个服务器是用来开发的,不是给互联网用的。Flask 启动时自己就会这么说。在学校 wifi 上用一节课没问题,但别把它放到公开的互联网上。

它没有登录功能。wifi 上的任何人都能输入任何名字,也能勾掉别人的作业。做作业清单没问题,但涉及分数或隐私就不行了。

有一样好东西是白送的:模板会安全地处理 <>。如果学生把 <script> 当名字输进去,它会显示成文字,而不会被执行。这一点我们测过。

8 The whole app · ทั้งหมดรวมกัน · 完整的程序

🇬🇧 English

Two files. board.py goes next to homework.py. board.html goes inside a folder called templates.

🇹🇭 ไทย

สองไฟล์ board.py วางไว้ข้าง ๆ homework.py ส่วน board.html วางไว้ในโฟลเดอร์ชื่อ templates

🇨🇳 中文

两个文件。board.py 放在 homework.py 旁边,board.html 放进一个叫 templates 的文件夹里。

homework/
  homework.py          # the terminal app
  homework.db          # the data, shared
  board.py             # the web app
  templates/
    board.html         # the page

9 Now change it · ลองแก้ดู · 现在动手改它

🇬🇧 English

You now have one set of data and two ways into it. Adding a third would be easy.

🇹🇭 ไทย

ตอนนี้คุณมีข้อมูลชุดเดียว กับสองทางเข้าหามัน ถ้าจะเพิ่มทางที่สามก็ง่ายแล้ว

🇨🇳 中文

现在你有一份数据、两个入口。再加第三个也很容易了。

Try thisHowLevel
A page for one studentA new route: @app.route("/student/<name>").easy
An Undo buttonCopy the /done/ route, set done = 0.easy
Show late jobs in redCompare due with today, add a CSS class.harder
Show the report pageReuse the GROUP BY from the homework app.harder
Let the AI teacher helpAdd a route that sends the job title to your local AI.harder