Compare commits

...

29 Commits

Author SHA1 Message Date
shimmer b9f1b04e8d Update Day_0: 增加GitHub稳定访问方法教程链接(知乎) 2026-08-22 21:45:16 +08:00
shimmer 8e683413e1 Update Day_0: 改为注册GitHub并推送到个人仓库, 工单交仓库地址 2026-08-22 21:33:07 +08:00
shimmer 1bc3822912 Update Day_0: 交作业改为工单评论, 移除push(组织仓库无写权限) 2026-08-22 21:28:28 +08:00
shimmer 5091a29998 Update 进阶_Django: 前置要求改为完成Day_15后可挑战 2026-08-22 01:09:46 +08:00
shimmer e62f8b4615 Add Day_15: Django第一个页面 2026-08-22 01:09:42 +08:00
shimmer d6d9c2d953 Add Day_14: pip装Django认识HTTP 2026-08-22 01:09:40 +08:00
shimmer 03eddc109a Add Day_13: 文件读写与JSON 2026-08-22 01:09:38 +08:00
shimmer f662556cc1 Add Day_12: 面向对象(二)继承与__str__ 2026-08-22 01:09:36 +08:00
shimmer f9d1eadfc8 Add Day_11: 面向对象(一)类与实例 2026-08-22 01:09:35 +08:00
shimmer 020ec0bda5 Add Day_10: 异常处理 2026-08-22 01:09:33 +08:00
shimmer 1b3871adb7 Add Day_9: 模块+猜数字游戏 2026-08-22 01:09:31 +08:00
shimmer 1d7a74d1cb Add Day_8: 函数 2026-08-22 01:09:30 +08:00
shimmer e24cdf74e4 Add Day_7: 循环for/while 2026-08-22 01:09:28 +08:00
shimmer 298887a7dd Add Day_6: 条件判断if 2026-08-22 01:09:26 +08:00
shimmer 1144f3aa34 Add Day_5: 字典 2026-08-22 01:09:25 +08:00
shimmer 6336b95cc6 Add Day_4: 列表 2026-08-22 01:09:23 +08:00
shimmer 97ee36971a Add Day_3: 字符串 2026-08-22 01:09:21 +08:00
shimmer 458879884d Add Day_2: 变量与数据类型 2026-08-22 01:09:20 +08:00
shimmer c99b0fa8f7 Add Day_1: 配置Python环境 2026-08-22 01:09:18 +08:00
shimmer 55e7d8568e Add Day_0: Git入门+Hello World 2026-08-22 01:09:16 +08:00
shimmer 999b2c6332 重构: 15天课程体系(从零到Django), 每题先学后做30分钟 2026-08-22 01:08:50 +08:00
shimmer 5cb96bbc7a Add 进阶_Django: 可选进阶任务(AI仅前端/后端手写/验收问答) 2026-08-18 00:22:09 +08:00
shimmer 05aa279c76 Add Day_6: 面向对象(三) descriptor复刻CharField 2026-08-18 00:22:04 +08:00
shimmer 2832c2772d Add Day_5: 面向对象(二) 继承复刻models.Model 2026-08-18 00:21:59 +08:00
shimmer 50bdd627d8 Add Day_4: 面向对象(一) User类 2026-08-18 00:21:53 +08:00
shimmer 85757ce6b1 Add Day_3: 函数与异常(日志/参数校验) 2026-08-18 00:21:48 +08:00
shimmer 00eac4c8ad Add Day_2: 字典与字符串(URL参数解析) 2026-08-18 00:21:42 +08:00
shimmer 19fe528076 更新 README: 新题目安排与进阶规则 2026-08-18 00:21:31 +08:00
shimmer 604b9b37c1 重构: Day_2拆分/Day_3延后为Day_4-6/Django改为进阶任务 2026-08-18 00:21:31 +08:00
21 changed files with 1367 additions and 729 deletions
+69
View File
@@ -0,0 +1,69 @@
# Day_0 · 注册 GitHub,把 Hello World 推送到自己的仓库(第零节)
> 预计耗时:25~30 分钟
> 今天做四件事:**注册 GitHub、建自己的仓库、用命令行把代码推上去、在工单里交仓库地址**。
> 推送的目标是你**自己的 GitHub 仓库**,不是工作室的仓库(工作室仓库只有学长能 push)。
## 今日知识点
Git 是**代码版本管理工具**,GitHub 是存放代码的网站。今天掌握这些命令:
| 命令 | 作用 |
| --- | --- |
| `git init` | 把当前文件夹变成 Git 仓库 |
| `git status` | 查看当前状态(改了哪些文件) |
| `git add hello.py` | 把文件"登记"进暂存区(`git add .` 表示全部) |
| `git commit -m "说明"` | 把暂存区内容存成本地记录 |
| `git branch -M main` | 把默认分支命名为 `main` |
| `git remote add origin <仓库地址>` | 关联远程仓库(`origin` 是它的名字) |
| `git push -u origin main` | 首次推送到远程(`-u` 记住关联,之后直接 `git push`) |
## 先学(教程)
- 廖雪峰 Git 教程(重点看"远程仓库"和"添加远程库",我们和教程做一模一样的事):https://liaoxuefeng.com/books/git/remote/
- GitHub 官方文档·推送提交到远程仓库:https://docs.github.com/zh/get-started/using-git/pushing-commits-to-a-remote-repository
- 菜鸟教程 Git 基础:https://www.runoob.com/git/git-tutorial.html
- **GitHub 稳定访问方法**(国内打不开 GitHub / 推送超时 / 网页访问失败时,按这篇操作):https://zhuanlan.zhihu.com/p/1946180155610469521
## 任务(后做)
1. **注册 GitHub 账号**:打开 https://github.com → Sign up,用真实信息注册(免费),记住你的**用户名**
2. **创建个人仓库**:右上角 `+` → New repository → 仓库名填 `hello-daily-challenge`
→ 选 Public → 点 Create repository(**不要在页面上勾选任何初始化选项**,保持空仓库)
3. 在电脑上新建文件夹 `hello-daily-challenge`,在里面新建 `hello.py`,
文件内容**直接写**(一个字符都不要错):
```python
print("Hello World")
```
4. 在该文件夹里打开命令行,依次执行:
```
git init
git add .
git commit -m "add hello world"
git branch -M main
git remote add origin https://github.com/你的用户名/hello-daily-challenge.git
git push -u origin main
```
> `git remote add` 那行,把"你的用户名"换成你注册时用的用户名。
5. 推送时命令行会问用户名和密码,这样填:
- 用户名:你的 GitHub 用户名
- 密码:**不是登录密码**,而是 Personal Access Token(怎么生成看第 6 步)
6. **生成 Token(密钥)**:GitHub 网页 → 点头像 → Settings → 拉到最下面
Developer settings → Personal access tokens → Tokens (classic) → Generate new token (classic)
→ 勾选 `repo` 权限 → Generate → **复制那串 token**(只显示一次)
7. 刷新 https://github.com/你的用户名/hello-daily-challenge ,确认 `hello.py` 出现了
8. 运行验证:命令行 `python hello.py`,看到输出 `Hello World`
> 卡壳求助:任何一步报错,把**完整报错文字**贴在 Day_0 工单里,学长会帮你。
> 常见坑:用户名拼错、token 没勾 repo 权限、文件夹没先 `git init`。
> GitHub 打不开/推送超时:先看上面"GitHub 稳定访问方法"那篇教程,多半是网络问题,不是你的操作问题。
## 完成标准
- [ ] GitHub 账号已注册
- [ ] 个人仓库 `hello-daily-challenge` 里有 `hello.py`
- [ ] `python hello.py` 输出 `Hello World`
- [ ] 能说出来:add / commit / push 分别做了什么
**提交方式**:在 **Day_0 工单**下方"撰写"处,贴上你的 **GitHub 仓库地址**
(形如 `https://github.com/你的用户名/hello-daily-challenge`),然后点击"评论"。
+44
View File
@@ -0,0 +1,44 @@
# Day_1 · 配置 Python 环境,运行第一个程序
> 预计耗时:20~30 分钟
> 电脑上没有 Python 一切都无从谈起。今天的目标:装好它,并用**两种方式**运行同一个程序。
## 今日知识点
- 什么是 Python 解释器(你写的代码靠它执行)
- 安装时务必勾选 **Add Python to PATH**(否则命令行找不到 python)
- 运行 `.py` 文件的两种方式:命令行 `python 文件名.py`、或 IDE(IDLE / VS Code)里点运行
## 先学(教程)
- 菜鸟教程(看"第一个Python程序"和"Python 环境搭建"两节):https://www.runoob.com/python3/python3-tutorial.html
- B站(黑马程序员,看最前面"软件下载安装、IDE 使用"几集即可):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
1. 下载安装 Python:https://www.python.org/downloads/ ,安装时**勾选 Add Python to PATH**
2. 打开命令行,运行 `python --version`,看到版本号(如 `Python 3.12.x`)即成功
3. 新建文件 `hello.py`(可以继续用 Day_0 你上传过的那份),内容是:
```python
print("Hello World")
```
4. **方式一(命令行)**:在 `hello.py` 所在目录打开命令行,运行 `python hello.py`,看到输出 `Hello World`
5. **方式二(IDE)**:安装 VS Code(https://code.visualstudio.com/ ,装完在扩展里搜 "Python" 安装),
用 VS Code 打开 `hello.py`,点右上角三角按钮运行,同样看到 `Hello World`
> 小贴士:命令行里任何一步报"不是内部或外部命令",八成是 PATH 没勾,重装一遍记得勾上。
## 完成标准
- [ ] `python --version` 有版本号输出
- [ ] 命令行 `python hello.py` 跑出 `Hello World`
- [ ] VS Code 里也能跑出 `Hello World`
**提交方式**:把命令行运行 `python hello.py` 的**截图**贴在当日工单的评论里。
## 参考解答(先做再看)
```python
print("Hello World")
```
就这一行。能跑出来,今天的目标就完成了。
+86
View File
@@ -0,0 +1,86 @@
# Day_10 · 异常处理:程序不崩溃的"安全网"
> 预计耗时:20~30 分钟
> 用户输入的永远不可信——填错、乱填、不填,程序都不能崩。
> Django 校验参数、Celery 任务失败重试,核心都是今天学的 try/except。
## 今日知识点
- `try: ... except 异常类型: ...`:把可能出错的代码放 try 里,出错就跳进 except
- 常见异常:`ValueError`(值不对,如 `int("abc")`)、`ZeroDivisionError`(除数为 0)
- 技巧:先让程序崩一次,看报错最后一行写的什么异常类型,再捕获它
## 先学(教程)
- 菜鸟教程("Python3 错误和异常"一节):https://www.runoob.com/python3/python3-exceptions.html
- B站黑马教程(看讲"异常"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day10.py`:
1. 捕获"输入不是数字"的异常:
```python
try:
num = int(input("请输入一个数字:"))
print(f"你输入的是{num}")
except ValueError:
print("这不是数字!请重新运行并输入数字。")
```
运行两次:一次输入 `123`,一次输入 `abc`,确认不崩溃且有提示。
2. 除法保护:
```python
try:
a = int(input("被除数:"))
b = int(input("除数:"))
print(a / b)
except ValueError:
print("请输入数字!")
except ZeroDivisionError:
print("除数不能为 0!")
```
测试:`10 / 2`、`10 / 0`、`abc / 2` 三种情况。
3. **进阶(加分)**:写一个通用函数 `safe_int(s)`,能转就返回整数,不能转就返回 `None`,
再用它处理输入(`num = safe_int(input(...))`,`if num is None: 打印提示`)。
## 完成标准
- [ ] 任务 1、2 的三种情况都不崩溃,提示清晰
- [ ] 能说出 `int("abc")` 会抛什么异常
- [ ] (进阶)safe_int 返回 None 而不是报错
**提交方式**:把 `day10.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
# 1. 数字校验
try:
num = int(input("请输入一个数字:"))
print(f"你输入的是{num}")
except ValueError:
print("这不是数字!")
# 2. 除法保护
try:
a = int(input("被除数:"))
b = int(input("除数:"))
print(a / b)
except ValueError:
print("请输入数字!")
except ZeroDivisionError:
print("除数不能为 0!")
# 进阶
def safe_int(s):
try:
return int(s)
except ValueError:
return None
num = safe_int(input("请输入一个数字:"))
if num is None:
print("这不是数字!")
else:
print(f"你输入的是{num}")
```
+88
View File
@@ -0,0 +1,88 @@
# Day_11 · 面向对象(一):类与实例
> 预计耗时:25~30 分钟
> Django 最核心的东西是"用类表示数据"(ORM)。今天第一次接触**类**——先别慌,照抄、运行、改改看。
## 今日知识点
- **类**(class)是"图纸",**实例**(instance)是按图纸造出来的"实物"
- `__init__`:造实例时自动执行的"初始化"方法,用来存数据
- `self`:代表"这个实例自己",`self.name` 就是"这个实例的名字"
- 实例方法:类里面定义的函数,通过 `实例.方法()` 调用
## 先学(教程)
- 菜鸟教程("面向对象"一节,先看到"创建实例"为止):https://www.runoob.com/python3/python3-class.html
- B站黑马教程(看讲"面向对象、类"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day11.py`:
1. 定义一只狗:
```python
class Dog:
def __init__(self, name, breed):
self.name = name # 狗的名字
self.breed = breed # 狗的品种
def bark(self):
print(f"{self.name}:汪!")
d1 = Dog("旺财", "金毛")
d2 = Dog("小黑", "柯基")
d1.bark()
d2.bark()
```
2. 加一个方法,打印自我介绍:
```python
# 在 Dog 类里再加一个方法:
def intro(self):
print(f"我是{self.breed},叫{self.name}")
d1.intro()
```
3. **进阶(加分)**:照葫芦画瓢,自己定义一个 `User` 类,字段 `name`、`age`,
方法 `greet()` 打印 `你好,我叫xxx,今年xx岁`,创建两个用户调用。
(提示:这正是 Django 里 `class User(models.Model)` 的雏形。)
## 完成标准
- [ ] 两只狗都能叫、都能自我介绍
- [ ] 能说出 `self` 是干嘛的
- [ ] (进阶)User 类完成
**提交方式**:把 `day11.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
print(f"{self.name}:汪!")
def intro(self):
print(f"我是{self.breed},叫{self.name}")
d1 = Dog("旺财", "金毛")
d2 = Dog("小黑", "柯基")
d1.bark() # 旺财:汪!
d2.bark() # 小黑:汪!
d1.intro() # 我是金毛,叫旺财
# 进阶
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
print(f"你好,我叫{self.name},今年{self.age}岁")
u1 = User("张三", 18)
u1.greet() # 你好,我叫张三,今年18岁
```
+100
View File
@@ -0,0 +1,100 @@
# Day_12 · 面向对象(二):继承与 __str__
> 预计耗时:25~30 分钟
> Django 里所有模型都写 `class Article(models.Model)`——`models.Model` 就是被继承的"基类"。
> 今天亲手写一次继承,你就明白 Django 的模型为什么"自带保存能力"。
## 今日知识点
- **继承**:`class Dog(Animal):` —— Dog 是 Animal 的"子类",自动拥有 Animal 的全部方法
- **重写**:子类里再写一遍同名方法,就覆盖父类的实现
- **`super()`**:在子类方法里调用父类的方法(`super().方法()`)
- **`__str__`**:定义"这个对象打印出来长什么样"(`print(对象)` 时触发)
## 先学(教程)
- 菜鸟教程("面向对象"一节,重点看**继承**部分):https://www.runoob.com/python3/python3-class.html
- B站黑马教程(看讲"继承"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day12.py`:
1. 基类 + 两个子类:
```python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print("...")
class Dog(Animal):
def speak(self): # 重写:覆盖父类
print(f"{self.name}:汪汪!")
class Cat(Animal):
def speak(self):
print(f"{self.name}:喵喵!")
d = Dog("旺财")
c = Cat("咪咪")
d.speak() # 旺财:汪汪!
c.speak() # 咪咪:喵喵!
```
2. 给 Animal 加 `__str__` 方法:
```python
# 在 Animal 类里加:
def __str__(self):
return self.name
print(d) # 打印出来是 旺财 而不是 <__main__.Dog object at ...>
```
3. **进阶(加分)**:用 `super()`——给 Animal 加一个 `info()` 方法打印 `我是动物`,
然后让 Dog 重写 `info()`:先打印 `我是狗`,再调用 `super().info()`,
调用 `d.info()` 观察两行输出和顺序。
## 完成标准
- [ ] 汪汪 / 喵喵 输出正确
- [ ] `print(d)` 显示的是狗名
- [ ] 能说出"子类自动拥有父类的方法"这句话的意思
- [ ] (进阶)super() 两行输出顺序正确
**提交方式**:把 `day12.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print("...")
def __str__(self): # 打印对象时显示的名字
return self.name
def info(self):
print("我是动物")
class Dog(Animal):
def speak(self):
print(f"{self.name}:汪汪!")
def info(self): # 进阶:super()
print("我是狗")
super().info() # 调用父类的 info
class Cat(Animal):
def speak(self):
print(f"{self.name}:喵喵!")
d = Dog("旺财")
c = Cat("咪咪")
d.speak()
c.speak()
print(d) # 旺财
d.info() # 我是狗 / 我是动物
```
+89
View File
@@ -0,0 +1,89 @@
# Day_13 · 文件读写与 JSON:让数据活到下次运行
> 预计耗时:25~30 分钟
> 程序一关,变量就没了。想让数据存下来(这就是"持久化",数据库干的事),先从文件开始。
> Django 的数据库、缓存的本质,都是"把数据存起来再读回来"。
## 今日知识点
- 写文件:`with open("note.txt", "w") as f: f.write(内容)`(`w` = 覆盖写)
- 读文件:`with open("note.txt", "r") as f: 内容 = f.read()`
- `with` 会自动关文件,不用手动 close
- **JSON**:一种"把字典/列表变成文本"的格式,`json.dumps` 变文本、`json.loads` 变回数据
- 为什么重要:HTTP 接口传输的数据、配置文件,全是 JSON 格式
## 先学(教程)
- 菜鸟教程("文件方法"一节,看 open/read/write 即可):https://www.runoob.com/python3/python3-file-methods.html
- 菜鸟教程("JSON"一节):https://www.runoob.com/python3/python3-json.html
- B站黑马教程(看讲"文件操作"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day13.py`:
1. 写入再读回:
```python
text = input("写一句话存起来:")
with open("note.txt", "w", encoding="utf-8") as f:
f.write(text)
with open("note.txt", "r", encoding="utf-8") as f:
content = f.read()
print("读回来的是:", content)
```
运行后确认同目录出现了 `note.txt`,且内容一致。(注意 `encoding="utf-8"`,否则中文会乱码。)
2. 用 JSON 存字典:
```python
import json
user = {"name": "张三", "age": 18, "city": "青岛"}
with open("user.json", "w", encoding="utf-8") as f:
json.dump(user, f, ensure_ascii=False) # ensure_ascii=False 让中文可读
with open("user.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["name"], loaded["age"]) # 从文件里还原的字典
```
3. **进阶(加分)**:把 Day_5 的"三个用户列表"用 `json.dump` 存进 `users.json`,
再读回来遍历打印每个人的名字。
## 完成标准
- [ ] note.txt 和 user.json 出现在同目录,读回内容一致
- [ ] 能说出 `json.dump` 和 `json.load` 各是干嘛的
- [ ] (进阶)用户列表存取成功
**提交方式**:把 `day13.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
text = input("写一句话存起来:")
with open("note.txt", "w", encoding="utf-8") as f:
f.write(text)
with open("note.txt", "r", encoding="utf-8") as f:
print("读回来的是:", f.read())
import json
user = {"name": "张三", "age": 18, "city": "青岛"}
with open("user.json", "w", encoding="utf-8") as f:
json.dump(user, f, ensure_ascii=False)
with open("user.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["name"], loaded["age"])
# 进阶
users = [
{"name": "张三", "age": 19},
{"name": "李四", "age": 20},
{"name": "王五", "age": 18},
]
with open("users.json", "w", encoding="utf-8") as f:
json.dump(users, f, ensure_ascii=False)
with open("users.json", "r", encoding="utf-8") as f:
for u in json.load(f):
print(u["name"])
```
+66
View File
@@ -0,0 +1,66 @@
# Day_14 · pip 安装 Django,认识 HTTP
> 预计耗时:25~30 分钟
> 你马上要接触第一个"框架"了。今天两件事:用 pip 装上 Django,并搞懂网页是怎么请求数据的。
## 今日知识点
- **pip**:Python 的"应用商店",`pip install 包名` 装第三方库
- **Django**:别人写好的 Web 框架,你基于它写后端
- **HTTP 请求/响应**:浏览器(客户端)发请求 → 服务器处理 → 返回响应(网页/数据)
- **URL 结构**:`https://www.runoob.com/python3/python3-tutorial.html`
= 协议(`https`) + 域名(`www.runoob.com`) + 路径(`/python3/python3-tutorial.html`)
## 先学(教程)
- 菜鸟教程 Django(先看"简介"和"环境安装"部分):https://www.runoob.com/django/django-tutorial.html
- B站 Django(看前 1~2 讲:django 安装 / 创建项目):https://www.bilibili.com/video/BV1L8bpeXET5/
## 任务(后做)
1. 命令行安装 Django:
```
pip install django
```
2. 验证安装成功:
```
python -c "import django; print(django.get_version())"
```
看到版本号(如 `5.x.x`)即成功。报"ModuleNotFoundError"就说明没装上,重新执行第 1 步。
3. 认识 HTTP 与 URL:
- 用浏览器打开 https://www.runoob.com/python3/python3-tutorial.html
- 观察地址栏,指认:哪部分是协议?哪部分是域名?哪部分是路径?
- 在页面上按 F12 → 切换到"网络/Network"标签 → 刷新页面,
能看到一列"请求"记录——这就是浏览器向服务器发的 HTTP 请求。
4. **进阶(加分)**:用 Python 发一个 HTTP 请求(`urllib` 是内置模块,不用安装):
```python
from urllib.request import urlopen
resp = urlopen("https://www.runoob.com/python3/python3-tutorial.html")
print(resp.status) # 200 表示成功
print(resp.read()[:100]) # 返回的内容(前 100 字节)
```
感受一下:你的电脑作为"客户端"去请求了一个"服务器"。
## 完成标准
- [ ] 版本号打印成功
- [ ] 能指认 URL 的三部分
- [ ] (进阶)Python 请求返回 200
**提交方式**:把命令行安装 + 版本号的**截图**贴在当日工单评论里。
## 参考解答(先做再看)
```python
# 命令行三步:
# pip install django
# python -c "import django; print(django.get_version())"
# 看到 5.x.x 即成功
from urllib.request import urlopen
resp = urlopen("https://www.runoob.com/python3/python3-tutorial.html")
print(resp.status) # 200
print(resp.read()[:100])
```
+97
View File
@@ -0,0 +1,97 @@
# Day_15 · Django 第一个页面:跑通"请求 → 响应"
> 预计耗时:30 分钟
> 终点站!今天你的程序第一次变成"网站":浏览器访问它,它返回内容。之后你就可以挑战仓库里的进阶任务了。
## 今日知识点
- `django-admin startproject mysite`:创建 Django 项目(一个文件夹骨架)
- `python manage.py runserver`:启动开发服务器(默认 http://127.0.0.1:8000/)
- 视图函数 `def index(request):`:接收请求,返回 `HttpResponse(...)`
- `urls.py` 路由:把网址路径(如 `/`)和视图函数对应起来
## 先学(教程)
- B站 Django(看"创建项目""启动运行""Hello World"几讲):https://www.bilibili.com/video/BV1L8bpeXET5/
- 菜鸟教程 Django("创建项目"一节):https://www.runoob.com/django/django-tutorial.html
## 任务(后做)
全程在命令行操作(跟着做,卡住就把报错贴工单里):
1. 创建项目(在自己方便的位置):
```
django-admin startproject mysite
cd mysite
```
2. 启动服务器:
```
python manage.py runserver
```
浏览器打开 http://127.0.0.1:8000/ ,应该看到 Django 的欢迎页(一只火箭/小飞机)。
先截个图。
3. 关闭服务器(命令行按 Ctrl+C),让页面显示你自己的内容:
- 用编辑器打开 `mysite/mysite/views.py`,**先什么都不写**(这个文件原本是空的),
改成下面这样:
```python
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, Django!")
```
- 打开 `mysite/mysite/urls.py`,把 `urlpatterns` 改成:
```python
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.index),
]
```
- 重新 `python manage.py runserver`,刷新 http://127.0.0.1:8000/ ,
应该看到 **Hello, Django!** 大字。
4. **进阶(加分)**:创建一个应用(Django 的"模块"):
```
python manage.py startapp blog
```
然后把视图和路由挪到 `blog` 应用里(提示:`blog/views.py` 里写视图函数,
`mysite/urls.py` 里用 `path("", include("blog.urls"))`)。
做完记得把 `blog` 加进 `settings.py` 的 `INSTALLED_APPS`。
> 卡壳求助:任何一步报错,把**完整报错文字**贴在当日工单里,学长会帮你。
## 完成标准
- [ ] http://127.0.0.1:8000/ 显示 **Hello, Django!**
- [ ] 能说出来:浏览器输入网址后,你的 Django 程序做了什么(URL → 视图 → 返回内容)
- [ ] (进阶)blog 应用跑通
**提交方式**:把显示 `Hello, Django!` 的浏览器**截图**贴在当日工单评论里。
## 参考解答(先做再看)
```python
# mysite/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, Django!")
# mysite/urls.py
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.index),
]
# 命令行:
# django-admin startproject mysite
# cd mysite
# python manage.py runserver
# 浏览器打开 http://127.0.0.1:8000/
```
+54 -128
View File
@@ -1,141 +1,67 @@
# Day_2 · Django 视角下的 Python 基础
# Day_2 · 变量与数据类型
> 今天还不用学 Django,也不需要会面向对象。
> 下面的题目只用到基础语法(字典、列表、字符串、函数、异常处理),
> 但每一题都是在模拟后端开发中**真正会遇到**的场景。
> 做完你会发现:Django 里那些看似神秘的东西,底层其实就是这些基础语法。
> 预计耗时:20~30 分钟
> 今天学:怎么往"盒子"里装数据(变量),以及数据分哪几种类型。
## 今日知识点 → 以后在 Django / Celery 里对应什么
## 今日知识点
| 今天练的 | 以后在 Django/Celery 里会看到 |
| --- | --- |
| 字典、列表的增删改查 | ORM 查询结果、`request.GET`/`request.POST`、JSON 响应 |
| 字符串分割与格式化 | URL 路由解析、日志输出、模板渲染 |
| 函数、`*args`/`**kwargs` | 视图函数 `def index(request, **kwargs)`、中间件、信号 |
| 异常处理(自定义异常 + try/except) | `Http404`、`ValidationError`、Celery 任务失败与重试 |
- 变量:`名字 = 值` 就能存数据,比如 `name = "张三"`
- 常见类型:`int` 整数、`float` 小数、`str` 字符串(带引号)、`bool` 布尔(True/False)
- `type(变量)` 可以查看类型;`input("提示")` 可以让用户从键盘输入(输入的一定是字符串)
---
## 先学(教程)
## 题目一(必做):解析 URL 查询参数
- 菜鸟教程(看"数据类型"一节,基础语法那节也顺带扫一眼):https://www.runoob.com/python3/python3-data-type.html
- B站黑马教程(看讲"变量、数据类型、输入"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
**背景**:浏览器访问 `https://example.com/register?username=zhangsan&age=20&city=qingdao`,
`?` 后面的 `username=zhangsan&age=20&city=qingdao` 叫做**查询参数(query string)**。
Django 里 `request.GET` 就是帮你解析好的字典。今天我们手写一个解析器,看清它背后发生了什么。
## 任务(后做)
**要求**:写函数 `parse_query_string(query)`,输入一行查询参数(不含 `?`),返回一个字典。
1. 新建 `day2.py`,写以下代码并运行,**先猜输出,再对答案**:
```python
name = "张三"
age = 18
score = 88.5
is_student = True
必须处理的边界情况:
1. 空输入 → 返回空字典 `{}`
2. 重复键,如 `name=a&name=b` → 后者覆盖前者:`{'name': 'b'}`
3. 参数没有 `=`,如 `flag` → 值为空字符串:`{'flag': ''}`
4. 值为空,如 `name=` → `{'name': ''}`
**输入输出示例**
```
输入: username=zhangsan&age=20&city=qingdao
输出: {'username': 'zhangsan', 'age': '20', 'city': 'qingdao'}
输入: name=a&name=b&flag
输出: {'name': 'b', 'flag': ''}
输入: (空字符串)
输出: {}
```
**进阶(加分)**:URL 编码还原——把值中的 `+` 还原为空格,`%20` 也还原为空格。
如 `name=Zhang+San` → `{'name': 'Zhang San'}`。
> 提示:
> - 先 `split('&')` 把整体切成若干段,再对每一段 `split('=', 1)`。
> 想想为什么 `maxsplit` 要写成 1?(如果值里本身就含 `=` 会怎样?)
> - 字典赋值 `d[key] = value` 天然处理了"覆盖"和"空值"两种情况。
**出题意图**:Django 的 `QueryDict`(`request.GET`)底层干的就是这件事。
把它吃透,以后看到 `request.GET.get('page')` 就不再是黑魔法,而是老朋友。
---
## 题目二(必做):万能日志记录器(函数 + *args/**kwargs)
**背景**:后端程序要记录"哪个函数被调用了、用了多久",这叫**日志(logging)**。
Django 中间件、Celery 任务、接口调用统计里,到处都是这种"给函数包一层"的操作。
**要求**:写函数 `log_call(func)`,它接收一个**函数**作为参数,返回一个**新函数**。
新函数在被调用时:
1. 打印 `[LOG] 调用函数 <函数名>,参数为 args=(...), kwargs={...}`
2. 调用原函数,拿到结果
3. 打印 `[LOG] <函数名> 执行完成,耗时 x.xxxx 秒`(用 `time.time()` 算耗时)
4. 返回原函数的结果
参数要用 `*args` 和 `**kwargs` 打包——因为你不知道原函数会接收什么参数。
**示例**
```python
import time
def add(a, b):
return a + b
logged_add = log_call(add)
print(logged_add(1, 2))
# [LOG] 调用函数 add,参数为 args=(1, 2), kwargs={}
# [LOG] add 执行完成,耗时 0.0001 秒
# 3
```
**进阶(加分)**:给 `log_call` 加第二个参数 `label`(默认空字符串),
打印日志时带上标签,如 `log_call(add, label="核心计算")` → `[核心计算][LOG] ...`。
> 提示:如果你已经看过装饰器语法,可以用 `@log_call` 直接写在函数头上;
> 没学过就写成 `log_call(add)(1, 2)` 这种普通调用,两种都行。
**出题意图**:Django 视图函数接收 `request`、URL 参数通过 `**kwargs` 传进来;
Celery 的任务、日志中间件全部建立在"函数作为参数 + `*args/**kwargs`"之上。
这是后端开发里出现频率最高的 Python 技巧,没有之一。
---
## 题目三(进阶):模拟注册接口的参数校验
**背景**:后端接口收到请求后的第一件事永远是**校验参数**。
字段缺失、格式不对,都要返回明确的错误信息,绝不能带着脏数据往下走。
Django 里抛 `ValidationError`、Celery 里任务失败后重试——这一切的起点都是 try/except。
**要求**:
1. 定义一个字典模拟前端传来的注册数据:
`data = {"username": "zhangsan", "email": "zs@163.com", "age": "20"}`(注意 age 是字符串)
2. 自定义一个异常类 `ValidationError(Exception)`。
3. 写函数 `validate_registration(data)`,按以下规则校验,**任何一个不满足就抛 `ValidationError`**:
- `username` 必须存在且长度 ≥ 3
- `email` 必须存在且同时包含 `@` 和 `.`
- `age` 必须存在、能转成 int、且 `1 <= age <= 120`
4. 在调用方用 try/except 捕获:失败打印 `校验失败: <异常信息>`,成功打印 `注册成功,欢迎 <username>!`
**进阶(加分)**:把规则本身做成数据——写一个通用函数
`validate(data, rules)`,其中 `rules` 是一个字典:
`{字段名: (校验函数, 错误信息)}`,返回所有不满足的字段的错误列表。
(提示:校验函数就是你平时写的普通函数或 lambda。)
这是后端参数校验框架(比如 DRF 的 Serializer)的雏形,能想出来说明你已经开始有"抽象"的意识了。
**出题意图**:Django forms / serializers 的校验、Celery 任务的失败处理,
核心模式都是"**校验 → 抛异常 → 上层捕获处理**"。
今天的题目就是把这个模式焊死在你的肌肉记忆里。
---
print(name)
print(type(name), type(age), type(score), type(is_student))
```
2. 用 `input()` 写一个打招呼程序:
```python
your_name = input("请输入你的名字:")
print("你好," + your_name + "!")
```
运行后输入你的名字,确认能回应。
3. **进阶(加分)**:`input()` 输入的是字符串,试试
`age = input("几岁?")` 后运行 `print(age + 1)`,观察报错,再用 `int(age) + 1` 修复它。
## 完成标准
- [ ] 题目一 4 个边界情况全部通过(含进阶则 +1)
- [ ] 题目二能打印出完整的调用日志(含进阶则 +1)
- [ ] 题目三能对"缺字段 / 格式错 / 年龄越界"三种情况分别报错(含进阶则 +1)
- [ ] 任务 1 的类型打印正确(str / int / float / bool)
- [ ] 任务 2 能正常打招呼
- [ ] (进阶)能把字符串转成 int 再运算
完成后打开 `参考解答.py` 对照。建议看完后关掉文件,自己再默写一遍。
**提交方式**:把 `day2.py` 的代码贴在当日工单评论里。
> 写给还没开始的你:这三天是递进关系——
> Day_2 用基础语法模拟后端场景,Day_3 用面向对象复刻 ORM 的雏形,Day_4 真正上手 Django。
> 每天只做必做题,也足够支撑你开学后跟上节奏。
## 参考解答(先做再看)
```python
name = "张三"
age = 18
score = 88.5
is_student = True
print(name) # 张三
print(type(name)) # <class 'str'>
print(type(age)) # <class 'int'>
print(type(score)) # <class 'float'>
print(type(is_student)) # <class 'bool'>
your_name = input("请输入你的名字:")
print("你好," + your_name + "!")
# 进阶
age = input("几岁?") # 输入的是字符串 "18"
# print(age + 1) # 报错:字符串不能直接加整数
print(int(age) + 1) # 19:int() 把字符串转成整数
```
-160
View File
@@ -1,160 +0,0 @@
# -*- coding: utf-8 -*-
"""
Day_2 参考解答
=============
今天的题目只用基础语法,但每个都是后端开发的真实场景。
建议:先自己写,写不出来再看;看完后把代码关掉,自己再默写一遍。
"""
import time
# ============================================================
# 题目一:解析 URL 查询参数
# ============================================================
def parse_query_string(query):
"""把 'username=zhangsan&age=20' 解析成 {'username': 'zhangsan', 'age': '20'}"""
result = {}
if not query: # 边界 1:空输入
return result
for item in query.split('&'): # 先按 & 切成若干段
if '=' in item:
key, value = item.split('=', 1) # maxsplit=1:值里可能也含有 '=',只切第一个
else:
key, value = item, '' # 边界 3:没有 = 的参数,值记为空字符串
result[key] = value # 字典赋值天然处理"重复键覆盖"和"值为空"
return result
# 进阶:URL 编码还原(把 + 和 %20 还原成空格)
def url_decode(value):
return value.replace('+', ' ').replace('%20', ' ')
def parse_query_string_advanced(query):
result = {}
if not query:
return result
for item in query.split('&'):
if '=' in item:
key, value = item.split('=', 1)
else:
key, value = item, ''
result[key] = url_decode(value)
return result
# 一个小测试
if __name__ == '__main__':
print(parse_query_string('username=zhangsan&age=20&city=qingdao'))
# {'username': 'zhangsan', 'age': '20', 'city': 'qingdao'}
print(parse_query_string('name=a&name=b&flag'))
# {'name': 'b', 'flag': ''}
print(parse_query_string(''))
# {}
print(parse_query_string_advanced('name=Zhang+San&city=A%20B'))
# {'name': 'Zhang San', 'city': 'A B'}
# ============================================================
# 题目二:万能日志记录器
# ============================================================
def log_call(func, label=""):
"""给函数包一层日志。func 被调用时打印参数和耗时。"""
def wrapper(*args, **kwargs):
tag = f"[{label}] " if label else ""
print(f"{tag}[LOG] 调用函数 {func.__name__},参数为 args={args}, kwargs={kwargs}")
start = time.time()
result = func(*args, **kwargs) # 原样透传参数
elapsed = time.time() - start
print(f"{tag}[LOG] {func.__name__} 执行完成,耗时 {elapsed:.4f} 秒")
return result # 原样返回结果
return wrapper
@log_call # 等价于 add = log_call(add)
def add(a, b):
return a + b
def multiply(a, b):
return a * b
multiply = log_call(multiply, label="核心计算") # 进阶:带标签
if __name__ == '__main__':
print(add(1, 2))
print(multiply(3, 4))
# 输出示例:
# [LOG] 调用函数 add,参数为 args=(1, 2), kwargs={}
# [LOG] add 执行完成,耗时 0.0000 秒
# 3
# [核心计算][LOG] 调用函数 multiply,参数为 args=(3, 4), kwargs={}
# [核心计算][LOG] multiply 执行完成,耗时 0.0000 秒
# 12
# ============================================================
# 题目三:模拟注册接口的参数校验
# ============================================================
class ValidationError(Exception):
"""自定义校验异常:参数不合法时抛出"""
pass
def validate_registration(data):
username = data.get("username")
email = data.get("email")
age = data.get("age")
if not username or len(username) < 3:
raise ValidationError("用户名必须存在且长度不少于 3")
if not email or "@" not in email or "." not in email:
raise ValidationError("邮箱必须包含 @ 和 .")
if age is None:
raise ValidationError("年龄必须存在")
try:
age_int = int(age) # 字符串 "20" 也能转
except (TypeError, ValueError):
raise ValidationError("年龄必须是整数")
if not (1 <= age_int <= 120):
raise ValidationError("年龄必须在 1~120 之间")
def register():
data = {"username": "zhangsan", "email": "zs@163.com", "age": "20"}
try:
validate_registration(data)
print(f"注册成功,欢迎 {data['username']}!")
except ValidationError as e:
print(f"校验失败: {e}")
# ---- 进阶:把规则做成数据 ----
def is_valid_email(email):
return isinstance(email, str) and "@" in email and "." in email
def validate(data, rules):
"""rules: {字段名: (校验函数, 错误信息)},返回所有错误列表"""
errors = []
for field, (check, message) in rules.items():
if not check(data.get(field)):
errors.append(f"{field}: {message}")
return errors
RULES = {
"username": (lambda v: isinstance(v, str) and len(v) >= 3, "用户名长度不足 3"),
"email": (is_valid_email, "邮箱格式不正确"),
"age": (lambda v: isinstance(v, int) and 1 <= v <= 120, "年龄非法"),
}
if __name__ == '__main__':
register() # 注册成功,欢迎 zhangsan!
print(validate({"username": "zs", "email": "bad", "age": 200}, RULES))
# ['username: 用户名长度不足 3', 'email: 邮箱格式不正确', 'age: 年龄非法']
+50 -131
View File
@@ -1,145 +1,64 @@
# Day_3 · 面向对象 × 手写一个小 ORM
# Day_3 · 字符串:和文字打交道
> Django 最核心的能力是 **ORM(对象关系映射)**:你定义类,Django 帮你管理数据库表。
> 打开任何 Django 项目,第一行几乎都是 `class Article(models.Model):`——
> 这一行的背后,是整片面向对象的大海。
> 今天我们不装 Django,用纯 Python 把它的 Model 一层层拆开看:
> 类、实例、继承、魔法方法、property、描述符(descriptor)。
> 全部做完,你会第一次"看穿"框架的魔法。
> 预计耗时:20~30 分钟
> 后端天天处理文字(用户名、日志、网页内容)。今天把字符串的基本操作学扎实。
## 今日知识点 → 以后在 Django 里对应什么
## 今日知识点
| 今天练的 | 以后在 Django 里会看到 |
| --- | --- |
| 类定义 + 类属性 | `class Article(models.Model)`,字段就是类属性 |
| `__init__` / 实例方法 | Model 实例的构造、`.save()` / `.delete()` |
| `__str__` / `__repr__` 魔法方法 | Admin 后台显示的名称、QuerySet 打印结果 |
| 继承与 `super()` | 所有 Model 继承 `models.Model`;自定义 Manager |
| `@property`、描述符与封装 | Model 上的派生字段、字段校验(Field 机制) |
- 字符串拼接:`+`(如 `"你" + "好"`)
- **f-string**(推荐):`f"你好,{name}!"` —— 大括号里直接放变量
- 常用方法:`len(s)` 长度、`s.upper()` 大写、`s.lower()` 小写、`s.strip()` 去首尾空格、`s.split(" ")` 按空格切分
---
## 先学(教程)
## 题目一(必做):设计一个 User 类
- 菜鸟教程("字符串"一节,重点看 f-string 和常用方法):https://www.runoob.com/python3/python3-string.html
- B站黑马教程(看讲"字符串"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
**背景**:Django 里定义一个用户模型只需要三行:
```python
class User(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
```
看起来像魔法,其实核心就是"类 + 类属性"。今天我们不用 Django,用纯 Python 复刻 User 类。
**要求**:
1. 类属性 `all_users = []`(模拟数据库表,用来存所有创建出来的用户)
2. `__init__(self, name, email, age)`:把三个参数存成实例属性,并把 `self` 追加进 `all_users`
3. 实例方法 `get_full_name()`:返回 `用户:<name>(<email>)`
4. 魔法方法 `__str__(self)`:返回 `self.name`(Django admin 后台显示的就是它!)
5. 魔法方法 `__repr__(self)`:返回 `User(name='zhangsan', email='zs@163.com', age=20)`
这种"能还原出原对象"的格式
**验证**:
- 创建 3 个用户,打印 `User.all_users`
- 分别 `print` 每个用户(会触发 `__str__`)
- 用 `eval(repr(u1))` 还原出 u1 的信息,确认内容一致
**思考题(写在注释里)**:`__str__` 和 `__repr__` 到底有什么区别?
(提示:一个是"给人看的",一个是"给开发者调试看的"——具体从使用场景和返回值要求去想。)
---
## 题目二(必做):继承 —— 复刻 models.Model
**背景**:Django 里 `Article` 没有写 `__init__`,没有写 `save()`,却能 `.save()`、能查 `objects`。
为什么?因为通用能力都在父类 `models.Model` 里,子类只需要写自己的字段。
今天我们自己写这个父类。
**要求**:
1. 定义基类 `BaseModel`:
- 类属性 `objects = []`(模拟数据库表)
- `save(self)`:把 `self` 追加进 `objects`,并打印 `[DB] 已保存 <类名>: <str(self)>`
- `delete(self)`:把 `self` 从 `objects` 中移除,打印 `[DB] 已删除 <str(self)>`
- `to_dict(self)`:把实例的所有属性转成字典(提示:`self.__dict__` 是什么?)
- `__str__(self)`:默认返回 `<类名#id>`,比如 `<Article#1405...>`
2. 定义 `Article(BaseModel)`:`__init__` 接收 `title, content, author` 存成实例属性,
再用 `time` 模块记录 `created_at`(形如 `2026-08-18 10:30:00`)
3. 定义 `Comment(BaseModel)`:`__init__` 接收 `content, author`
4. 全流程走一遍:创建文章和评论 → save → 遍历 `BaseModel.objects` 打印 → `to_dict()` → delete
5. 验证继承关系:`print(issubclass(Article, BaseModel))` 应为 `True`,
`print(isinstance(a, BaseModel))` 应为 `True`
**进阶(思考 + 动手)**:如果要求"Article 保存前先打印一行日志",而 `save()` 写在父类里,
怎么办?(提示:在子类里**重写** `save()`,先打印,再 `super().save()`。试试看输出顺序。)
**出题意图**:Django 的 Model 就是"通用逻辑放父类,子类只写自己的字段"。
理解了继承 + `super()`,你就理解了为什么 Django 模型"不写 save 却能保存"。
---
## 题目三(进阶):类属性当"字段声明"——复刻 models.CharField
**背景**:Django 里这样声明字段:
```python
class Article(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
```
`title` 是**类属性**,但每个实例的 `title` 值又各自不同——这是怎么做到的?
答案是 **descriptor(描述符)协议**:类属性如果是实现了 `__get__` / `__set__` 的对象,
那么对实例属性的一切读写都会先经过它。Django 的 Field 就是这种对象。
**要求**:
1. 定义字段类型类(都实现 `__get__` 和 `__set__`):
- `CharField(max_length)`:`__set__` 里校验"必须是字符串、长度 ≤ max_length",否则抛 `ValueError`
- `EmailField`:继承 `CharField`,额外校验必须含 `@` 和 `.`
- `IntegerField(min_value, max_value)`:校验"必须是 int(注意 `True` 不算)、在范围内"
值的存储要写到**实例自己的** `__dict__` 里;还没赋过值时返回一个哨兵对象
(用一个模块级 `_NOT_SET = object()`)。`__get__` 里 `instance is None` 表示
"通过类访问",此时应返回描述符本身(这样 `Article.title` 看到的是 Field 对象,
这正是 Django 里 `Article.title` 的行为)。
2. 写一个基类 `ModelBase`,用 `__init_subclass__`(在子类定义完成后自动调用)
遍历子类的 `cls.__dict__`,把每个 Field 对象的 `.name` 填上它的属性名。
这样描述符才知道自己对应哪个属性。
3. 定义 `Article(ModelBase)`,用类属性声明字段:
## 任务(后做)
1. 用 f-string 写自我介绍:
```python
class Article(ModelBase):
title = CharField(max_length=50)
content = CharField(max_length=1000)
author_email = EmailField(max_length=100)
views = IntegerField(min_value=0)
name = "张三"
age = 18
city = "青岛"
print(f"我叫{name},今年{age}岁,来自{city}。")
```
4. 测试:
- 正常赋值、读取,一切正常
- `a.title = "x" * 100` → 抛 `ValueError`(超长)
- `a.views = -5` → 抛 `ValueError`(越界)
- `a.author_email = "not-an-email"` → 抛 `ValueError`(格式错)
- `print(Article.title)` → 打印出的是 `CharField` 对象本身
**出题意图**:这就是 Django Field 的核心机制。做完这一题,你再去看
`models.CharField(max_length=50)` 时,看到的就不是魔法,而是你今天亲手写过的代码。
---
把名字换成你自己的,运行确认。
2. 字符串方法练习,**先猜输出再运行**:
```python
s = "Hello World"
print(len(s)) # 长度是多少?
print(s.upper()) # 大写
print(s.lower()) # 小写
print(s.strip()) # 首尾没空格也能跑,试试 " Hello ".strip()
```
3. **进阶(加分)**:`words = "apple banana cherry".split(" ")`,
打印切出来的列表,再数一数有几个单词(用 `len(words)`)。
## 完成标准
- [ ] 题目一:User 类全流程 + 想通了 `__str__` / `__repr__` 的区别
- [ ] 题目二:BaseModel + Article + Comment 全流程 + 继承验证(进阶则 +1)
- [ ] 题目三:三种 Field 的校验全部生效(难度较高,做不出来先把题目二的继承吃透)
- [ ] 任务 1 打印出完整自我介绍
- [ ] 任务 2 四行输出都符合预期
- [ ] (进阶)能切分字符串并数出单词个数
完成后打开 `参考解答.py` 对照。
**提交方式**:把 `day3.py` 代码贴在当日工单评论里。
> 进度提示:Day_3 的题目三是整个系列里最难的一题,卡住很正常。
> 它对应的正是 Day_4 你真正写 Django Model 时会遇到的机制。
## 参考解答(先做再看)
```python
name = "张三"
age = 18
city = "青岛"
print(f"我叫{name},今年{age}岁,来自{city}。")
s = "Hello World"
print(len(s)) # 11(含空格)
print(s.upper()) # HELLO WORLD
print(s.lower()) # hello world
print(" Hi ".strip()) # Hi
# 进阶
words = "apple banana cherry".split(" ")
print(words) # ['apple', 'banana', 'cherry']
print(len(words)) # 3
```
-205
View File
@@ -1,205 +0,0 @@
# -*- coding: utf-8 -*-
"""
Day_3 参考解答
=============
用纯 Python 复刻 Django Model 的机制。
建议:先自己写,写不出来再看;看完后关掉文件自己默写。
"""
import time
# ============================================================
# 题目一:设计一个 User 类
# ============================================================
class User:
all_users = [] # 类属性:模拟数据库表
def __init__(self, name, email, age):
self.name = name
self.email = email
self.age = age
User.all_users.append(self) # 把新用户"存表"
def get_full_name(self):
return f"用户:{self.name}({self.email})"
def __str__(self):
# 给"人"看的:print(user) 时显示,Django admin 列表里显示的就是它
return self.name
def __repr__(self):
# 给"开发者调试"看的:要能还原出原对象,eval(repr(u)) 应得到相同信息
return f"User(name={self.name!r}, email={self.email!r}, age={self.age!r})"
if __name__ == '__main__':
u1 = User("zhangsan", "zs@163.com", 20)
u2 = User("lisi", "ls@163.com", 21)
u3 = User("wangwu", "ww@163.com", 19)
for u in User.all_users:
print(u, "->", repr(u))
# zhangsan -> User(name='zhangsan', email='zs@163.com', age=20)
# lisi -> User(name='lisi', email='ls@163.com', age=21)
# wangwu -> User(name='wangwu', email='ww@163.com', age=19)
restored = eval(repr(u1)) # __repr__ 的用途:可还原
print(restored.name, restored.email) # zhangsan zs@163.com
# 思考题答案:
# __str__ 面向普通用户/展示,__repr__ 面向开发者/调试,且要求无歧义可还原;
# 只实现 __repr__ 时,print() 会退而使用 __repr__;反之不会。
# ============================================================
# 题目二:继承 —— 复刻 models.Model
# ============================================================
class BaseModel:
objects = [] # 类属性:模拟数据库表(所有子类共用)
def save(self):
BaseModel.objects.append(self)
print(f"[DB] 已保存 {type(self).__name__}: {self}")
def delete(self):
BaseModel.objects.remove(self)
print(f"[DB] 已删除 {self}")
def to_dict(self):
return dict(self.__dict__) # 实例的所有属性 => 字典
def __str__(self):
return f"<{type(self).__name__}#{id(self)}>"
class Article(BaseModel):
def __init__(self, title, content, author):
self.title = title
self.content = content
self.author = author
self.created_at = time.strftime("%Y-%m-%d %H:%M:%S")
# 进阶:重写 save,先打日志,再调用父类逻辑
def save(self):
print(f"[LOG] 正在保存文章《{self.title}》...")
super().save() # 父类的 save 负责真正"入库"
def __str__(self):
return f"<Article: {self.title}>"
class Comment(BaseModel):
def __init__(self, content, author):
self.content = content
self.author = author
def __str__(self):
return f"<Comment: {self.content[:10]}...>"
if __name__ == '__main__':
a1 = Article("Django 入门", "从 Hello World 开始", "zhangsan")
a1.save() # 触发重写后的 save(带日志 + super)
c1 = Comment("写得太好了!", "lisi")
c1.save()
for obj in BaseModel.objects:
print(obj.to_dict())
print(issubclass(Article, BaseModel)) # True
print(isinstance(a1, BaseModel)) # True
a1.delete()
print(len(BaseModel.objects)) # 1(只剩评论)
# ============================================================
# 题目三(进阶):descriptor —— 复刻 models.CharField
# ============================================================
_NOT_SET = object() # 哨兵值:表示"字段还没赋过值"
class CharField:
def __init__(self, max_length=255):
self.max_length = max_length
self.name: str | None = None # 字段对应的属性名,由 ModelBase.__init_subclass__ 填入
def __get__(self, instance, owner):
if instance is None:
return self # 通过类访问(Article.title):返回描述符本身
return instance.__dict__.get(self.name, _NOT_SET)
def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError(f"{self.name} 必须是字符串")
if len(value) > self.max_length:
raise ValueError(f"{self.name} 长度不能超过 {self.max_length}")
instance.__dict__[self.name] = value # 存到实例自己的字典里
class EmailField(CharField):
def __set__(self, instance, value):
if "@" not in value or "." not in value:
raise ValueError(f"{self.name} 不是合法的邮箱格式")
super().__set__(instance, value) # 通过校验后再走父类存储
class IntegerField:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
self.name: str | None = None
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name, _NOT_SET)
def __set__(self, instance, value):
# bool 是 int 的子类,所以要显式排除 True/False
if not isinstance(value, int) or isinstance(value, bool):
raise ValueError(f"{self.name} 必须是整数")
if self.min_value is not None and value < self.min_value:
raise ValueError(f"{self.name} 不能小于 {self.min_value}")
if self.max_value is not None and value > self.max_value:
raise ValueError(f"{self.name} 不能大于 {self.max_value}")
instance.__dict__[self.name] = value
class ModelBase:
# 子类定义完成后自动调用:把每个 Field 对象和它的属性名绑定
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for attr_name, attr_value in cls.__dict__.items():
if isinstance(attr_value, (CharField, IntegerField)):
attr_value.name = attr_name
# 为避免与本文件上方题目二的 Article 类重名,这里命名为 Post,
# 题目描述里想定义哪个类都可以,机制完全相同。
class Post(ModelBase):
title = CharField(max_length=50)
content = CharField(max_length=1000)
author_email = EmailField(max_length=100)
views = IntegerField(min_value=0)
if __name__ == '__main__':
a = Post()
a.title = "Django 入门"
a.content = "跟着 Day_3 慢慢理解框架"
a.author_email = "zs@163.com"
a.views = 10
print(a.title, a.views, a.author_email) # Django 入门 10 zs@163.com
print(Post.title) # 类访问 => CharField 对象本身
# 以下三条都应该抛 ValueError,逐行取消注释验证
# a.title = "x" * 100
# a.views = -5
# a.author_email = "not-an-email"
# 尚未赋值的字段返回哨兵(不是属性不存在,也不是默认值)
print(a.__dict__.get("views")) # 已被赋值为 10
+64 -98
View File
@@ -1,112 +1,78 @@
# Day_4 · 设计一个真正的 Django 小应用
# Day_4 · 列表:一串数据
> 前提:你应该已经跟着教程跑通了 Django 的安装,执行过
> `django-admin startproject mysite` 和 `python manage.py startapp blog`,
> 知道 `settings.py` / `urls.py` / `views.py` / `models.py` 大概长什么样。
> 今天不是"照着教程敲一遍",而是"**先想清楚,再动手设计**"。
> 题目里每一步都问了为什么——能回答上,才算真的懂了。
> 预计耗时:20~30 分钟
> 列表就是一串排好队的数据。以后 ORM 查出来的"一堆结果",本质上就是一串对象。
## 今日目标
## 今日知识点
做一个**社团招新管理系统**:成员(Member)发布文章(Article),成员可以评论(Comment)。
覆盖:模型设计、ORM 查询、视图、URL 路由、模板、以及"从请求到响应"的完整链路。
- 创建:`fruits = ["apple", "banana"]`
- 增:`fruits.append("orange")` 尾部加;`fruits.insert(0, "pear")` 指定位置加
- 删:`fruits.remove("banana")` 按值删
- 查:`fruits[0]` 第一个、`fruits[-1]` 最后一个、`len(fruits)` 个数
- 遍历:`for f in fruits: print(f)`
## 环境准备
## 先学(教程)
```bash
pip install django
django-admin startproject mysite
cd mysite
python manage.py startapp blog
# 记得把 'blog' 加进 settings.py 的 INSTALLED_APPS
```
- 菜鸟教程("列表"一节,重点看增删改查和遍历):https://www.runoob.com/python3/python3-list.html
- B站黑马教程(看讲"列表"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
---
## 任务(后做)
## 题目一(必做):设计数据模型 models.py
新建 `day4.py`,按顺序写并运行:
**背景**:一个博客/招新系统,核心数据是"文章",文章有作者,作者是社团成员,文章下面有评论。
**要求**:在 `blog/models.py` 里写出完整代码,包含三个模型:
1. **Member**(成员):昵称 `nickname`、邮箱 `email`(要**唯一**)、
加入时间 `joined_at`(自动记录创建时间)、简介 `bio`(允许为空)
2. **Article**(文章):标题 `title`、正文 `content`、作者 `author`
(外键 → Member,作者被删时文章级联删除)、是否发布 `is_published`(默认 False)、
发布时间 `created_at`(自动记录)、浏览量 `views`(默认 0)
3. **Comment**(评论):正文 `content`、所属文章 `article`
(外键,`related_name="comments"`)、评论者 `commenter`(外键 → Member)
四个要求:
- 三个模型都要有合理的 `__str__`(Article 返回标题,Comment 返回"评论者 + 内容前 20 字",Member 返回昵称)
- 用 `Meta` 设置排序:Article 按 `created_at` 倒序,Comment 按 `id` 正序
- 字段参数用对:`max_length`、`default`、`null=True`/`blank=True` 该加的地方要加
- 思考题写进注释里(各一两句即可)
**思考题(写进注释)**:
1. 为什么外键要用 `on_delete=models.CASCADE`?如果改成 `SET_NULL` 会发生什么、需要什么前提?
2. `related_name="comments"` 是干什么的?(提示:`article.comments.all()` 是什么?)
---
## 题目二(必做):写视图 + ORM 查询 views.py
**要求**:在 `blog/views.py` 里写四个视图函数:
1. **`index(request)`**:查出所有**已发布**的文章(`is_published=True`),按时间倒序,
渲染 `index.html`,把文章列表传进模板
2. **`article_detail(request, pk)`**:用 `get()` 取单篇文章,**不存在时抛 `Http404`**;
浏览量 +1 并保存;渲染 `detail.html`
3. **`add_comment(request, pk)`**:POST 请求时读取 `request.POST` 里的 `content`,
创建一条评论(评论者先用 `Member.objects.first()` 随便取一个,我们还没做登录),
完成后**重定向**回文章详情页;GET 请求也直接重定向回详情页
4. **(进阶)`author_articles(request, nickname)`**:按作者昵称查该作者的全部文章,
并且**用 ORM 统计每篇文章的评论数**(提示:`annotate` + `Count`,配合 `related_name`),
渲染 `author.html`。想不出 `annotate` 就先用 Python 算,但要写一句注释说明两种做法的区别
**思考题(写进注释)**:
- 浏览量 +1 有两种写法:
a) `article.views += 1; article.save()`
b) `Article.objects.filter(pk=pk).update(views=F("views") + 1)`
两个请求同时点击文章时,a 会出什么问题?b 为什么没问题?(提示:并发 / 原子性)
---
## 题目三(必做):URL 路由 + 完整链路说明
**要求**:
1. 写出项目级 `urls.py`(含 `include`)和应用级 `blog/urls.py`,路由表覆盖:
- 列表页 `""` → `index`
- 文章详情 `/article/<int:pk>/` → `article_detail`
- 提交评论 `/article/<int:pk>/comment/` → `add_comment`
- 作者文章页 `/author/<str:nickname>/` → `author_articles`
每个路由都要起 `name`。
2. **写一段话(150 字内)**:用户在浏览器输入 `/article/3/` 回车后,
从"请求到响应"经历了哪些环节?必须提到:URL 匹配、视图函数、
ORM 查询、`render`、模板、`HttpResponse` 这几个词。
3. **(进阶)** 给出 `index.html` 的模板片段:
- 用 `{% for %}` 循环展示文章标题和"查看详情"链接
- 用模板过滤器把 `created_at` 格式化成 `Y-m-d H:i`
- 空列表时显示"暂无文章"
---
1. 创建一个水果列表:
```python
fruits = ["apple", "banana"]
print(fruits)
```
2. 追加两个水果,删除一个,再打印:
```python
fruits.append("orange")
fruits.append("grape")
fruits.remove("banana")
print(fruits) # 现在有哪些?
```
3. 遍历打印 + 长度:
```python
for f in fruits:
print(f"我喜欢吃{f}")
print(f"一共{len(fruits)}种水果")
```
4. 打印第一个和最后一个:
```python
print(f"第一个:{fruits[0]}")
print(f"最后一个:{fruits[-1]}")
```
5. **进阶(加分)**:`nums = [3, 1, 4, 1, 5]`,试试 `nums.sort()` 和 `nums.reverse()` 后分别打印。
## 完成标准
- [ ] 题目一:三个模型写全,思考题有自己的答案(不必和参考一致,能自圆其说就行)
- [ ] 题目二:四个视图能跑通,`python manage.py runserver` 后列表页、详情页、评论、作者页都能访问
- [ ] 题目三:路由表写全 + 链路说明写出来(进阶则 +1)
- [ ] 四步输出都符合预期(增删改查 + 遍历)
- [ ] 能说出 `append` 和 `remove` 的作用
- [ ] (进阶)sort / reverse 结果正确
**验收动作(强烈建议)**:跑起来 `runserver`,在浏览器里真的走一遍
"打开列表页 → 点进文章 → 提交评论 → 回到详情页看到评论"。
能跑通,说明 Day_4 真正过关。
**提交方式**:把 `day4.py` 代码贴在当日工单评论里。
完成后打开 `参考解答.md` 对照。
## 参考解答(先做再看)
> 进度提示:Day_4 做完,你就完成了"基础语法 → 面向对象 → 框架实战"的完整闭环。
> 下一步可以自己加功能:分页、搜索、`@login_required`、用 Celery 给文章作者发通知……
> 每加一个功能,都是一次新的练习。
```python
fruits = ["apple", "banana"]
fruits.append("orange")
fruits.append("grape")
fruits.remove("banana")
print(fruits) # ['apple', 'orange', 'grape']
for f in fruits:
print(f"我喜欢吃{f}")
print(f"一共{len(fruits)}种水果") # 一共3种水果
print(f"第一个:{fruits[0]}") # apple
print(f"最后一个:{fruits[-1]}") # grape
# 进阶
nums = [3, 1, 4, 1, 5]
nums.sort()
print(nums) # [1, 1, 3, 4, 5]
nums.reverse()
print(nums) # [5, 4, 3, 1, 1]
```
+91
View File
@@ -0,0 +1,91 @@
# Day_5 · 字典:带名字的数据
> 预计耗时:20~30 分钟
> 字典是"键 → 值"的对应表。Django 里请求参数(`request.GET`)、一条数据库记录,全是字典的样子。
## 今日知识点
- 创建:`user = {"name": "张三", "age": 18}`
- 取值:`user["name"]` 或 `user.get("name")`(get 取不到不报错,可给默认值)
- 改值:`user["age"] = 19`
- 加键:`user["city"] = "青岛"`
- 删键:`del user["city"]`
- 遍历:`for k, v in user.items(): print(k, v)`
## 先学(教程)
- 菜鸟教程("字典"一节,重点看增删改查和 items()):https://www.runoob.com/python3/python3-dictionary.html
- B站黑马教程(看讲"字典"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day5.py`,按顺序写并运行:
1. 创建一个"用户"字典并取值:
```python
user = {"name": "张三", "age": 18, "city": "青岛"}
print(user["name"])
print(user["age"])
```
2. 修改年龄、新增一个键、删除一个键:
```python
user["age"] = 19
user["hobby"] = "写代码"
del user["city"]
print(user)
```
3. 遍历打印每一项:
```python
for key, value in user.items():
print(f"{key} = {value}")
```
4. 用 `get()` 安全取值:
```python
print(user.get("city")) # 键已被删,会是什么?
print(user.get("city", "未知")) # 给默认值
```
5. **进阶(加分)**:造一个**字典列表**,模拟"查出来的三个用户":
```python
users = [
{"name": "张三", "age": 19},
{"name": "李四", "age": 20},
{"name": "王五", "age": 18},
]
for u in users:
print(f"{u['name']}今年{u['age']}岁")
```
## 完成标准
- [ ] 任务 1~4 输出符合预期
- [ ] 能说出 `user["x"]` 和 `user.get("x")` 的区别
- [ ] (进阶)字典列表遍历打印成功
**提交方式**:把 `day5.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
user = {"name": "张三", "age": 18, "city": "青岛"}
print(user["name"]) # 张三
user["age"] = 19
user["hobby"] = "写代码"
del user["city"]
print(user) # {'name': '张三', 'age': 19, 'hobby': '写代码'}
for key, value in user.items():
print(f"{key} = {value}")
print(user.get("city")) # None(键不存在,get 不报错)
print(user.get("city", "未知")) # 未知
# 进阶
users = [
{"name": "张三", "age": 19},
{"name": "李四", "age": 20},
{"name": "王五", "age": 18},
]
for u in users:
print(f"{u['name']}今年{u['age']}岁")
```
+65
View File
@@ -0,0 +1,65 @@
# Day_6 · 条件判断:让程序做决定
> 预计耗时:20~30 分钟
> 后端要根据不同情况做不同处理(参数合法就通过,不合法就报错)——这就是 `if`。
## 今日知识点
- `if 条件:` / `elif 条件:` / `else:`(注意冒号和缩进)
- 比较运算符:`>` `<` `>=` `<=` `==` `!=`
- `int(input(...))`:把键盘输入转成整数再比较
## 先学(教程)
- 菜鸟教程("条件控制"一节):https://www.runoob.com/python3/python3-conditional.html
- B站黑马教程(看讲"if 判断"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day6.py`:
1. 年龄判断程序:
```python
age = int(input("请输入你的年龄:"))
if age < 18:
print("未成年")
elif age < 60:
print("成年")
else:
print("老年")
```
分别输入 `15`、`20`、`70` 测试三种结果。
2. **进阶(加分)**:成绩评级——输入分数(0~100 整数),
`>=90` 打印"优秀",`>=60` 打印"及格",否则打印"不及格"。
用 `if / elif / else` 写完,测 `95`、`75`、`50` 三个分数。
> 提示:缩进必须是统一的(Tab 或 4 空格),混用会报错——这是新手最常见的坑。
## 完成标准
- [ ] 三个年龄输入分别输出"未成年 / 成年 / 老年"
- [ ] 能说出 `elif` 和连续多个 `if` 的区别(试想:都用 if 会怎样?)
- [ ] (进阶)成绩评级三档正确
**提交方式**:把 `day6.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
age = int(input("请输入你的年龄:"))
if age < 18:
print("未成年")
elif age < 60:
print("成年")
else:
print("老年")
# 进阶
score = int(input("请输入分数:"))
if score >= 90:
print("优秀")
elif score >= 60:
print("及格")
else:
print("不及格")
```
+84
View File
@@ -0,0 +1,84 @@
# Day_7 · 循环:重复做的事交给程序
> 预计耗时:20~30 分钟
> 后端最常干的事就是"把一堆数据逐个处理"——这正是循环。列表遍历(Day_4)你已经用过了,今天学正式的 `for` 和 `while`。
## 今日知识点
- `for i in range(n):` 循环 n 次(`range(1, 11)` 是 1~10)
- `while 条件:` 条件为真就一直循环
- `break` 跳出循环;`continue` 跳过本次进入下一次
- 累加套路:`total = total + i`(可简写 `total += i`)
## 先学(教程)
- 菜鸟教程("循环语句"一节,重点看 range 和 break/continue):https://www.runoob.com/python3/python3-loop.html
- B站黑马教程(看讲"循环"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day7.py`:
1. 用 `for` 求 1 加到 100 的和,打印结果(应该是 **5050**):
```python
total = 0
for i in range(1, 101):
total += i
print(total)
```
2. 打印 1~10 的平方:
```python
for i in range(1, 11):
print(f"{i}的平方是{i ** 2}")
```
3. 用 `while` 从 10 倒数到 1:
```python
n = 10
while n >= 1:
print(n)
n -= 1 # 别忘了让 n 变小,否则死循环
```
4. **进阶(加分)**:打印九九乘法表(用双重 for 循环):
```
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
...
```
提示:外层 `for i in range(1, 10):`,内层 `for j in range(1, i + 1):`,
每行结束用 `print()` 换行。
## 完成标准
- [ ] 任务 1 输出 `5050`
- [ ] 任务 2、3 输出正确且程序能正常结束(没有卡死)
- [ ] 能说出 `break` 和 `continue` 的区别
- [ ] (进阶)乘法表正确
**提交方式**:把 `day7.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
# 1. 求和
total = 0
for i in range(1, 101):
total += i
print(total) # 5050
# 2. 平方
for i in range(1, 11):
print(f"{i}的平方是{i ** 2}")
# 3. 倒数
n = 10
while n >= 1:
print(n)
n -= 1
# 4. 进阶:九九乘法表
for i in range(1, 10):
for j in range(1, i + 1):
print(f"{j}x{i}={j * i}", end=" ")
print() # 换行
```
+81
View File
@@ -0,0 +1,81 @@
# Day_8 · 函数:把代码装进"盒子"反复用
> 预计耗时:20~30 分钟
> 后端每个页面就是一个函数(Django 的视图函数 `def index(request):`)。今天学定义和调用。
## 今日知识点
- 定义:`def 名字(参数):` ,用 `return` 返回结果
- 调用:`名字(实参)`
- 默认参数:`def greet(name="朋友"):` —— 不传就用默认值
- 函数的好处:写一次,到处调用;代码不重复
## 先学(教程)
- 菜鸟教程("函数"一节,重点看定义、参数、返回值):https://www.runoob.com/python3/python3-function.html
- B站黑马教程(看讲"函数"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day8.py`:
1. 加法函数:
```python
def add(a, b):
return a + b
print(add(3, 5)) # 应该输出 8
print(add(10, 20)) # 30
```
2. 打招呼函数:
```python
def greet(name):
return f"你好,{name}!"
print(greet("张三"))
```
3. 默认参数:
```python
def greet2(name="朋友"):
return f"你好,{name}!"
print(greet2()) # 你好,朋友!
print(greet2("李四")) # 你好,李四!
```
4. **进阶(加分)**:写函数 `is_adult(age)`,年龄 ≥ 18 返回 `True` 否则 `False`,
然后用它给三个年龄(15、20、25)分别打印"成年/未成年"。
## 完成标准
- [ ] 任务 1~3 输出正确
- [ ] 能说出 `return` 和 `print` 的区别(函数里不 return 会返回什么?)
- [ ] (进阶)is_adult 结果正确
**提交方式**:把 `day8.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
def add(a, b):
return a + b
print(add(3, 5)) # 8
def greet(name):
return f"你好,{name}!"
print(greet("张三"))
def greet2(name="朋友"):
return f"你好,{name}!"
print(greet2()) # 你好,朋友!
print(greet2("李四")) # 你好,李四!
# 进阶
def is_adult(age):
return age >= 18
for age in (15, 20, 25):
print(f"{age}岁:" + ("成年" if is_adult(age) else "未成年"))
```
+78
View File
@@ -0,0 +1,78 @@
# Day_9 · 模块与猜数字小游戏(综合复习)
> 预计耗时:25~30 分钟
> Python 有很多现成的"工具包"叫**模块**(module),`import` 就能用。
> 今天用随机数模块写个小游戏,顺便把 input / if / while 全部串起来复习一遍。
## 今日知识点
- `import random`:随机数模块,`random.randint(1, 10)` 生成 1~10 的随机整数
- `import time`:时间模块,`time.time()` 拿当前时间(秒)
- 为什么要模块:别人写好、你直接用,就像 Django 也是别人写好给你用的"大模块"
## 先学(教程)
- 菜鸟教程("模块"一节,看 import 怎么用即可):https://www.runoob.com/python3/python3-module.html
- B站黑马教程(看讲"模块、random"的几集):https://www.bilibili.com/video/BV1qW4y1a7fU/
## 任务(后做)
新建 `day9.py`,写一个**猜数字游戏**:
```python
import random
target = random.randint(1, 10) # 电脑随机想一个 1~10 的数
print("我想到一个 1~10 之间的数,你猜猜看!")
while True:
guess = int(input("你的猜测:"))
if guess == target:
print("猜对啦!")
break # 猜对就结束
elif guess < target:
print("小了,再大点")
else:
print("大了,再小点")
```
测试:多玩几局,确认"大了/小了/猜对"三种反馈都正常。
**进阶(加分)**:
1. 用 `import time` 记录"猜了几秒":游戏开始记一次 `time.time()`,结束再记一次,两者相减就是秒数,游戏结束时打印。
2. 限制最多猜 5 次,超过就打印"机会用完了,答案是 X"并结束(提示:用 `for` 代替 `while True`,或者加一个计数变量 + `break`)。
## 完成标准
- [ ] 游戏能正常玩完一整局
- [ ] 能说出 `import random` 是干什么的
- [ ] (进阶)能统计用时 或 限制次数(二选一即可)
**提交方式**:把 `day9.py` 代码贴在当日工单评论里。
## 参考解答(先做再看)
```python
import random
import time
start = time.time()
target = random.randint(1, 10)
print("我想到一个 1~10 之间的数,你猜猜看!")
for i in range(5): # 进阶 2:最多 5 次
guess = int(input("你的猜测:"))
if guess == target:
print("猜对啦!")
break
elif guess < target:
print("小了,再大点")
else:
print("大了,再小点")
else: # for 正常结束(没 break)才执行
print(f"机会用完了,答案是{target}")
end = time.time()
print(f"用时{end - start:.1f}秒") # 进阶 1:统计用时
```
+35 -6
View File
@@ -4,19 +4,48 @@
请克隆仓库以获取每日一题
## 学习方式
每天一个文件夹(Day_0 ~ Day_15),**先看教程再动手做**,每天预计 30 分钟以内:
1. 打开当天文件夹的 `README.md`
2. 先点开"先学(教程)"里的链接学习(菜鸟教程 + B站视频)
3. 再做"任务",卡住才看"参考解答"
4. 把运行结果(截图或代码)贴在**当日工单**的评论里
## 提交方式
请在该工单下方"撰写"处提交你的文件然后点击"评论",如果显示"登录 并参与到对话中"就请点击"登录"并注册你的账户
## 目录结构
## 课程安排
| 目录 | 内容 | 前置要求 |
| --- | --- | --- |
| Day_2 | Django 视角下的 Python 基础(字典 / 函数 / `*args`/`**kwargs` / 异常) | 掌握 Python 基础语法 |
| Day_3 | 面向对象 × 手写一个小 ORM(类 / 继承 / descriptor) | 掌握基础语法即可,无需学过 Django |
| Day_4 | 设计一个真正的 Django 小应用(models / views / urls / 模板) | 已跟着教程跑通 Django 环境 |
| Day_0 | Git 入门 + Hello World(第零节·开学前热身) | 无 |
| Day_1 | 配置 Python 环境,运行第一个程序 | 完成 Day_0 |
| Day_2 | 变量与数据类型 | 完成 Day_1 |
| Day_3 | 字符串 | 承接 Day_2 |
| Day_4 | 列表 | 承接 Day_3 |
| Day_5 | 字典 | 承接 Day_4 |
| Day_6 | 条件判断 if | 承接 Day_5 |
| Day_7 | 循环 for / while | 承接 Day_6 |
| Day_8 | 函数 | 承接 Day_7 |
| Day_9 | 模块 + 猜数字小游戏(综合复习) | 承接 Day_8 |
| Day_10 | 异常处理 try / except | 承接 Day_9 |
| Day_11 | 面向对象(一):类与实例 | 承接 Day_10 |
| Day_12 | 面向对象(二):继承与 __str__ | 承接 Day_11 |
| Day_13 | 文件读写与 JSON | 承接 Day_12 |
| Day_14 | pip 安装 Django,认识 HTTP | 承接 Day_13 |
| Day_15 | Django 第一个页面:请求 → 响应 | 承接 Day_14 |
| 进阶_Django | 设计一个真正的 Django 小应用 | **可选**,完成 Day_15 后挑战 |
每个目录包含当日题目(`README.md`)与参考解答(`参考解答.py` 或 `参考解答.md`)。
每个目录包含:当日题目(`README.md`,含教程链接、任务、参考解答)。
## 进阶任务说明(进阶_Django)
1. **前端**(模板 HTML / CSS / JS)**允许使用 AI 辅助**
2. **后端**(models / views / urls / ORM 查询 / 业务逻辑)**必须自己手搓**,不允许 AI 代写
3. 完成后**找学长验收**:验收时会围绕你的代码提问,答得上来才算通过
> 定位目标:为用 Django、Celery 等框架编写后端程序打下基础
> 难度递进:基础语法 → 面向对象 → 框架实战
> 难度递进:基础 → 函数与异常 → 面向对象 → 框架实战
+124
View File
@@ -0,0 +1,124 @@
# 进阶任务 · 设计一个真正的 Django 小应用(可选)
> **本任务不做要求**,是给学有余力的同学的挑战。
> 开学前能把这个完整做出来,已经非常厉害了。
> 完成后**主动找学长验收**——验收时会围绕你的代码**提问**(为什么这么设计、某个写法是什么原理),
> 答得上来才算通过,所以请确保每一行都是你自己写出来的。
**本任务规则**:
1. **前端**(模板 HTML / CSS / JS)**允许使用 AI 辅助**
2. **后端**(models / views / urls / ORM 查询 / 业务逻辑)**必须自己手搓**,不允许 AI 代写
3. 完成后**找学长验收**:验收 = 现场回答问题,不能只看跑不跑得通
> 前提:**建议先完成 Day_15**(能跑通 Django 的 Hello, Django!)再挑战本任务。
> 你应该已经跑通 Django 的安装,执行过
> `django-admin startproject mysite` 和 `python manage.py startapp blog`,
> 知道 `settings.py` / `urls.py` / `views.py` / `models.py` 大概长什么样。
> 这不是"照着教程敲一遍",而是"**先想清楚,再动手设计**"。
> 题目里每一步都问了为什么——能回答上,才算真的懂了。
## 今日目标
做一个**社团招新管理系统**:成员(Member)发布文章(Article),成员可以评论(Comment)。
覆盖:模型设计、ORM 查询、视图、URL 路由、模板、以及"从请求到响应"的完整链路。
## 环境准备
```bash
pip install django
django-admin startproject mysite
cd mysite
python manage.py startapp blog
# 记得把 'blog' 加进 settings.py 的 INSTALLED_APPS
```
---
## 题目一(必做):设计数据模型 models.py
**背景**:一个博客/招新系统,核心数据是"文章",文章有作者,作者是社团成员,文章下面有评论。
**要求**:在 `blog/models.py` 里写出完整代码,包含三个模型:
1. **Member**(成员):昵称 `nickname`、邮箱 `email`(要**唯一**)、
加入时间 `joined_at`(自动记录创建时间)、简介 `bio`(允许为空)
2. **Article**(文章):标题 `title`、正文 `content`、作者 `author`
(外键 → Member,作者被删时文章级联删除)、是否发布 `is_published`(默认 False)、
发布时间 `created_at`(自动记录)、浏览量 `views`(默认 0)
3. **Comment**(评论):正文 `content`、所属文章 `article`
(外键,`related_name="comments"`)、评论者 `commenter`(外键 → Member)
四个要求:
- 三个模型都要有合理的 `__str__`(Article 返回标题,Comment 返回"评论者 + 内容前 20 字",Member 返回昵称)
- 用 `Meta` 设置排序:Article 按 `created_at` 倒序,Comment 按 `id` 正序
- 字段参数用对:`max_length`、`default`、`null=True`/`blank=True` 该加的地方要加
- 思考题写进注释里(各一两句即可)
**思考题(写进注释)**:
1. 为什么外键要用 `on_delete=models.CASCADE`?如果改成 `SET_NULL` 会发生什么、需要什么前提?
2. `related_name="comments"` 是干什么的?(提示:`article.comments.all()` 是什么?)
---
## 题目二(必做):写视图 + ORM 查询 views.py
**要求**:在 `blog/views.py` 里写四个视图函数:
1. **`index(request)`**:查出所有**已发布**的文章(`is_published=True`),按时间倒序,
渲染 `index.html`,把文章列表传进模板
2. **`article_detail(request, pk)`**:用 `get()` 取单篇文章,**不存在时抛 `Http404`**;
浏览量 +1 并保存;渲染 `detail.html`
3. **`add_comment(request, pk)`**:POST 请求时读取 `request.POST` 里的 `content`,
创建一条评论(评论者先用 `Member.objects.first()` 随便取一个,我们还没做登录),
完成后**重定向**回文章详情页;GET 请求也直接重定向回详情页
4. **(进阶)`author_articles(request, nickname)`**:按作者昵称查该作者的全部文章,
并且**用 ORM 统计每篇文章的评论数**(提示:`annotate` + `Count`,配合 `related_name`),
渲染 `author.html`。想不出 `annotate` 就先用 Python 算,但要写一句注释说明两种做法的区别
**思考题(写进注释)**:
- 浏览量 +1 有两种写法:
a) `article.views += 1; article.save()`
b) `Article.objects.filter(pk=pk).update(views=F("views") + 1)`
两个请求同时点击文章时,a 会出什么问题?b 为什么没问题?(提示:并发 / 原子性)
---
## 题目三(必做):URL 路由 + 完整链路说明
**要求**:
1. 写出项目级 `urls.py`(含 `include`)和应用级 `blog/urls.py`,路由表覆盖:
- 列表页 `""` → `index`
- 文章详情 `/article/<int:pk>/` → `article_detail`
- 提交评论 `/article/<int:pk>/comment/` → `add_comment`
- 作者文章页 `/author/<str:nickname>/` → `author_articles`
每个路由都要起 `name`。
2. **写一段话(150 字内)**:用户在浏览器输入 `/article/3/` 回车后,
从"请求到响应"经历了哪些环节?必须提到:URL 匹配、视图函数、
ORM 查询、`render`、模板、`HttpResponse` 这几个词。
3. **(进阶)** 给出 `index.html` 的模板片段:
- 用 `{% for %}` 循环展示文章标题和"查看详情"链接
- 用模板过滤器把 `created_at` 格式化成 `Y-m-d H:i`
- 空列表时显示"暂无文章"
---
## 完成标准
- [ ] 题目一:三个模型写全,思考题有自己的答案(不必和参考一致,能自圆其说就行)
- [ ] 题目二:四个视图能跑通,`python manage.py runserver` 后列表页、详情页、评论、作者页都能访问
- [ ] 题目三:路由表写全 + 链路说明写出来(进阶则 +1)
**验收动作(强烈建议)**:跑起来 `runserver`,在浏览器里真的走一遍
"打开列表页 → 点进文章 → 提交评论 → 回到详情页看到评论"。
能跑通,再带着对每道思考题的理解去找学长验收。
完成后打开 `参考解答.md` 对照。
> 进度提示:做完这个进阶任务,你就完成了"基础语法 → 面向对象 → 框架实战"的完整闭环。
> 下一步可以自己加功能:分页、搜索、`@login_required`、用 Celery 给文章作者发通知……
> 每加一个功能,都是一次新的练习。
@@ -1,7 +1,8 @@
# Day_4 参考解答
# 进阶任务参考解答
> 说明:这里是完整参考实现,按文件拆成四块。
> 建议先自己写,卡住了再来看对应文件;看完后尝试不参考、重写一遍 views.py。
> 提醒:后端请务必自己手写,验收时会围绕这些代码提问。
---