Skip to content

Commit 6c0528c

Browse files
author
仿生猫梦见苦力怕
committed
Like function added.
1 parent c6fe1cc commit 6c0528c

17 files changed

+316
-50
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*.db
2+
future_devs.md
23
/__pycache__/
34
/.idea/
45
/venv/

app/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
from flask import Flask
22
from flask_login import LoginManager
3+
from flask_mail import Mail
34
from sqlmodel import SQLModel, create_engine
45
from config import Config
56

7+
mail = Mail()
8+
69
# Initialize Flask app
710
app = Flask(__name__)
811
app.config.from_object(Config)
12+
mail.init_app(app)
913

1014
# Initialize Flask-Login
1115
login_manager = LoginManager()

app/auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def login():
2121
login_user(user) # 登录用户
2222
return redirect(url_for('main.dashboard'))
2323
else:
24-
flash('Invalid username or password')
24+
flash('用户名或密码错误!')
2525
return render_template('login.html')
2626

2727

@@ -46,4 +46,5 @@ def register():
4646
@login_required
4747
def logout():
4848
logout_user()
49+
flash('你已登出!')
4950
return redirect(url_for('main.index'))

app/md_processor.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from flask import url_for
2+
3+
from markdown.extensions import Extension
4+
from markdown.inlinepatterns import ImageInlineProcessor, IMAGE_LINK_RE
5+
import xml.etree.ElementTree as etree
6+
7+
8+
class CustomImageInlineProcessor(ImageInlineProcessor):
9+
"""自定义 Markdown 图片处理器"""
10+
11+
def handleMatch(self, m, data):
12+
el = etree.Element("img")
13+
src = m.group(2)
14+
if src.startswith("/static/"):
15+
src = url_for('static', filename=src[8:]) # 将 /static/ 路径转换为 Flask 的静态文件 URL
16+
el.set("src", src)
17+
el.set("alt", m.group(1))
18+
return el, m.start(0), m.end(0)
19+
20+
21+
class CustomMarkdownExtension(Extension):
22+
"""自定义 Markdown 扩展"""
23+
24+
def extendMarkdown(self, md):
25+
md.inlinePatterns.register(CustomImageInlineProcessor(IMAGE_LINK_RE, md), 'image', 150)

app/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ class User(SQLModel, table=True):
88
username: str = Field(index=True)
99
email: str = Field(index=True)
1010
password_hash: str
11+
is_admin: bool = Field(default=False)
1112

1213
posts: List["Post"] = Relationship(back_populates="author")
1314
comments: List["Comment"] = Relationship(back_populates="author")
15+
likes: List["Like"] = Relationship(back_populates="user") # 用户点赞的博文
1416

1517
# Flask-Login 所需的属性和方法
1618
@property
@@ -39,6 +41,7 @@ class Post(SQLModel, table=True):
3941

4042
author: User = Relationship(back_populates="posts")
4143
comments: List["Comment"] = Relationship(back_populates="post")
44+
likes: List["Like"] = Relationship(back_populates="post") # 博文的点赞
4245

4346

4447
class Comment(SQLModel, table=True):
@@ -51,3 +54,13 @@ class Comment(SQLModel, table=True):
5154

5255
author: User = Relationship(back_populates="comments")
5356
post: Post = Relationship(back_populates="comments")
57+
58+
59+
class Like(SQLModel, table=True):
60+
id: Optional[int] = Field(default=None, primary_key=True)
61+
user_id: int = Field(foreign_key="user.id") # 点赞用户
62+
post_id: int = Field(foreign_key="post.id") # 被点赞的博文
63+
64+
# 关系
65+
user: "User" = Relationship(back_populates="likes")
66+
post: "Post" = Relationship(back_populates="likes")

app/routes.py

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import markdown
22
from flask import Blueprint, render_template, request, url_for, flash
33
from flask_login import login_required, current_user
4+
from sqlalchemy import func
45
from sqlmodel import Session, select, desc
56
from werkzeug.utils import redirect
67
from datetime import datetime
78

8-
from app import engine
9-
from app.models import Post, User, Comment
9+
from app import engine, app, mail
10+
from app.md_processor import CustomMarkdownExtension
11+
from app.models import Post, User, Comment, Like
1012
from app.forms import CommentForm, EditCommentForm, PostForm
1113

1214
# 创建蓝图
@@ -22,7 +24,16 @@ def index():
2224
.order_by(desc(Post.created_at))
2325
.limit(10)
2426
).all()
25-
return render_template('index.html', posts=posts, datetime=datetime, len=len)
27+
28+
popular_posts = session.exec(
29+
select(Post)
30+
.order_by(desc(func.count(Like.id)))
31+
.join(Like, isouter=True)
32+
.group_by(Post.id)
33+
.limit(5)
34+
).all()
35+
36+
return render_template('index.html', posts=posts, datetime=datetime, len=len, popular_posts=popular_posts)
2637

2738

2839
@main.route('/dashboard')
@@ -47,7 +58,9 @@ def dashboard():
4758
def create_post():
4859
form = PostForm()
4960
if form.validate_on_submit():
50-
html_content = markdown.markdown(form.content.data) # 解析 Markdown
61+
html_content = markdown.Markdown(
62+
extensions=[CustomMarkdownExtension()]
63+
).convert(form.content.data) # 解析 Markdown
5164
new_post = Post(
5265
title=form.title.data,
5366
content=form.content.data,
@@ -78,7 +91,9 @@ def view_post(post_id):
7891
def add_comment(post_id):
7992
form = CommentForm()
8093
if form.validate_on_submit():
81-
html_content = markdown.markdown(form.content.data) # 解析 Markdown
94+
html_content = markdown.Markdown(
95+
extensions=[CustomMarkdownExtension()]
96+
).convert(form.content.data) # 解析 Markdown
8297
new_comment = Comment(
8398
content=form.content.data,
8499
html_content=html_content,
@@ -100,7 +115,7 @@ def edit_post(post_id):
100115
if not post:
101116
flash('博文未找到!')
102117
return redirect(url_for('main.dashboard'))
103-
if post.author_id != current_user.id:
118+
if post.author_id != current_user.id and not current_user.is_admin:
104119
flash('你没有编辑这篇博文的权限!')
105120
return redirect(url_for('main.dashboard'))
106121

@@ -113,7 +128,9 @@ def edit_post(post_id):
113128
else:
114129
post.title = title
115130
post.content = content
116-
post.html_content = markdown.markdown(content)
131+
post.html_content = markdown.Markdown(
132+
extensions=[CustomMarkdownExtension()]
133+
).convert(content) # 转换 Markdown
117134
session.commit()
118135
flash('你的博文已被更新!')
119136
return redirect(url_for('main.view_post', post_id=post.id))
@@ -129,8 +146,8 @@ def delete_post(post_id):
129146
if not post:
130147
flash('博文未找到!')
131148
return redirect(url_for('main.dashboard'))
132-
if post.author_id != current_user.id:
133-
flash('你没有编辑这篇博文的权限!')
149+
if post.author_id != current_user.id and not current_user.is_admin:
150+
flash('你没有删除这篇博文的权限!')
134151
return redirect(url_for('main.dashboard'))
135152

136153
session.delete(post)
@@ -147,14 +164,16 @@ def edit_comment(comment_id):
147164
if not comment:
148165
flash('评论未找到!', 'error')
149166
return redirect(url_for('main.index'))
150-
if comment.author_id != current_user.id:
167+
if comment.author_id != current_user.id and not current_user.is_admin:
151168
flash('你没有权限编辑这条评论!', 'error')
152169
return redirect(url_for('main.view_post', post_id=comment.post_id))
153170

154171
form = EditCommentForm()
155172
if form.validate_on_submit():
156173
comment.content = form.content.data
157-
comment.html_content = markdown.markdown(form.content.data) # 解析 Markdown
174+
comment.html_content = markdown.Markdown(
175+
extensions=[CustomMarkdownExtension()]
176+
).convert(form.content.data) # 解析 Markdown
158177
session.commit()
159178
flash('评论已更新!', 'success')
160179
return redirect(url_for('main.view_post', post_id=comment.post_id))
@@ -173,7 +192,7 @@ def delete_comment(comment_id):
173192
if not comment:
174193
flash('评论未找到!', 'error')
175194
return redirect(url_for('main.index'))
176-
if comment.author_id != current_user.id:
195+
if comment.author_id != current_user.id and not current_user.is_admin:
177196
flash('你没有权限删除这条评论!', 'error')
178197
return redirect(url_for('main.view_post', post_id=comment.post_id))
179198

@@ -182,3 +201,39 @@ def delete_comment(comment_id):
182201
session.commit()
183202
flash('评论已删除!', 'success')
184203
return redirect(url_for('main.view_post', post_id=post_id))
204+
205+
206+
@main.route('/post/<int:post_id>/like', methods=['POST'])
207+
@login_required
208+
def like_post(post_id):
209+
with Session(engine) as session:
210+
# 检查用户是否已经点赞过该博文
211+
existing_like = session.exec(
212+
select(Like)
213+
.where(Like.user_id == current_user.id)
214+
.where(Like.post_id == post_id)
215+
).first()
216+
217+
if existing_like:
218+
# 如果已经点赞过,取消点赞
219+
session.delete(existing_like)
220+
session.commit()
221+
flash('已取消点赞!', 'info')
222+
else:
223+
# 如果未点赞过,添加点赞
224+
new_like = Like(user_id=current_user.id, post_id=post_id)
225+
session.add(new_like)
226+
session.commit()
227+
flash('点赞成功!', 'success')
228+
return redirect(url_for('main.view_post', post_id=post_id))
229+
230+
231+
@app.errorhandler(404)
232+
def page_not_found(error):
233+
return render_template('404.html'), 404
234+
235+
236+
@app.errorhandler(500)
237+
def internal_server_error(error):
238+
return render_template('500.html', error=error), 500
239+

app/static/css/styles.css

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ footer {
6666
right: 0;
6767
}
6868

69+
.hint {
70+
mso-foreground: #6c757d;
71+
opacity: 50%;
72+
}
73+
6974
/* 通用按钮样式 */
7075
.form-button {
7176
display: inline-block;
@@ -301,6 +306,7 @@ footer {
301306
/* 评论列表 */
302307
.comment-list {
303308
margin-top: 40px;
309+
margin-bottom: 80px;
304310
}
305311

306312
.comment-list h3 {
@@ -341,4 +347,53 @@ footer {
341347

342348
.comment-actions .action-button {
343349
margin-right: 10px;
344-
}
350+
}
351+
352+
/* 点赞按钮 */
353+
.like-button {
354+
background-color: #28a745;
355+
color: white;
356+
}
357+
358+
.like-button:hover {
359+
background-color: #218838;
360+
}
361+
362+
/* 点赞数 */
363+
.like-count {
364+
margin-left: 10px;
365+
font-size: 0.9rem;
366+
color: #666;
367+
}
368+
369+
/* 错误页面容器 */
370+
.error-container {
371+
max-width: 600px;
372+
margin: 50px auto;
373+
padding: 20px;
374+
background-color: #ffffff;
375+
border-radius: 10px;
376+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
377+
text-align: center;
378+
}
379+
380+
.error-container h1 {
381+
font-size: 2rem;
382+
color: #dc3545;
383+
margin-bottom: 20px;
384+
}
385+
386+
.error-container p {
387+
font-size: 1rem;
388+
color: #333;
389+
margin-bottom: 20px;
390+
}
391+
392+
.error-container a {
393+
color: #007bff;
394+
text-decoration: none;
395+
}
396+
397+
.error-container a:hover {
398+
text-decoration: underline;
399+
}

app/templates/404.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}页面未找到 - 六(5)班 班级博客{% endblock %}
4+
5+
{% block content %}
6+
<div class="error-container">
7+
<h1>404 - 页面未找到</h1>
8+
<p>抱歉,您访问的页面不存在。</p>
9+
<p>如果您是手动输入了页面 URL,请检查您的拼写是否有错误。</p>
10+
<p>点击 <a href="{{ url_for('main.index') }}">这里</a> 返回首页。</p>
11+
</div>
12+
{% endblock %}

app/templates/500.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}服务器错误 - 六(5)班 班级博客{% endblock %}
4+
5+
{% block content %}
6+
<div class="error-container">
7+
<h1>500 - 服务器内部错误</h1>
8+
<p>抱歉,服务器在处理您的请求时发生了内部错误。</p>
9+
<p>如果可能,请向服务器的开发者反馈该问题,并提供完整的复现流程。</p>
10+
<p>请稍后重试,或返回 <a href="{{ url_for('main.index') }}">首页</a></p>
11+
12+
<p>详细错误信息:</p>
13+
<p>{{ error[:80] | safe }}</p>
14+
</div>
15+
{% endblock %}

app/templates/index.html

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,36 @@ <h2>最新博文精选</h2>
1212
{% for post in posts %}
1313
<li class="post-item">
1414
<h3><a href="{{ url_for('main.view_post', post_id=post.id) }}" class="post-link">{{ post.title }}</a></h3>
15-
<p>作者:{{ post.author.username }} | 发布时间:{{ post.created_at.strftime('%Y-%m-%d %T') }}</p>
15+
<p>作者:{{ post.author.username }} | 发布时间:{{ post.created_at.strftime('%Y-%m-%d %T') }} | <em class="hint">Markdown 格式预览不可用……</em></p>
1616
<p>{{ (post.content[:80] + '...') if (len(post.content) > 80) else post.content }}</p>
17+
<div class="post-meta">
18+
<span class="like-count">👍 {{ post.likes|length }} 人点赞</span>
19+
</div>
1720
</li>
1821
{% endfor %}
1922
</ul>
2023
{% else %}
2124
<p>还没有人发布博文,快来成为第一个吧!</p>
2225
{% endif %}
2326

27+
<h2>最受欢迎博文</h2>
28+
{% if popular_posts %}
29+
<ul class="post-list">
30+
{% for post in popular_posts %}
31+
<li class="post-item">
32+
<h3><a href="{{ url_for('main.view_post', post_id=post.id) }}" class="post-link">{{ post.title }}</a></h3>
33+
<p>作者:{{ post.author.username }} | 发布时间:{{ post.created_at }} | <em class="hint">Markdown 格式预览不可用……</em></p>
34+
<p>{{ (post.content[:80] + '...') if (len(post.content) > 80) else post.content }}</p>
35+
<div class="post-meta">
36+
<span class="like-count">👍 {{ post.likes|length }} 人点赞</span>
37+
</div>
38+
</li>
39+
{% endfor %}
40+
</ul>
41+
{% else %}
42+
<p>还没有博文被点赞。</p>
43+
{% endif %}
44+
2445
{% if current_user.is_authenticated %}
2546
<button onclick="window.location.href='{{ url_for('main.create_post') }}'" class="form-button">发布一篇新的博文!</button>
2647
{% else %}

0 commit comments

Comments
 (0)