13.flask博客項目實戰八之關注功能


配套視頻教程

本文B站配套視頻教程

這章將學習如何實現類似於Twitter和其他社交網絡的“粉絲”或“關注”,比如關注你。

接下來,將更多介紹應用程序的數據庫。讓用戶能夠輕松選擇Ta們想要關注的其他用戶。因此,將擴展數據庫,以便跟蹤誰在關注(粉)誰。

重訪數據庫關系

我想每一個用戶維護一個“關注”和“關注者”列表。不幸的是,關系數據庫沒有我能用於這些列表的列表類型,所有都有記錄、這些記錄之間的關系。

數據庫中有一個表示用戶的表,所以剩下的是提出可以為關注、關注者鏈接建模的正確關系類型。現在是學習基本數據庫關系類型的好時機:

一對多

使用過一對多關系,下方就是此關系的圖表:一個用戶可以有多個帖子
image.png

通過這個關系連接的兩個實體分別是 用戶、帖子。一個用戶有很多篇帖子,每個帖子有一個用戶(作者)。在數據庫中,這個關系多側使用外鍵。在上述關系中,外鍵user_id字段添加到posts表中。這個字段將每個帖子連接到user表中其作者的記錄。

明顯地,user_id字段提供了對給定帖子的作者的直接訪問,但反過來呢?為使得關系有用,我們應該可以獲得給定用戶所編寫的帖子列表。posts表user_id字段也足以回答此問題,因為數據庫中具有允許有效查詢的索引,如 【檢索user_id為X的所有帖子】

多對多

多對多關系有點復雜。例如,考慮一個擁有studentsteachers的數據庫,可以說一個學生有很多個老師,一個老師有很多個學生。這就像來自兩端的兩個重疊的一對多關系。

對於這種類型的關系,我們應該能夠查詢到數據庫,並取得教授給定學生的教師列表、教師班級的學生列表。這在關系數據庫中表示實際上並不重要,因為無法通過向現有表添加外鍵來完成。

多對多關系的表示需要使用稱為 關聯表 的輔助表。下方是數據庫如何查找學生和教師的案列:
image.png

雖然一開始可能看起來不太明顯,但是具有兩個外鍵的關聯表 能夠有效地回答關於多對多關系的所有查詢。

多對一、一對一

多對一關系,類似於一對多關系。不同之處在於從“多”側看這種關系。

一對一關系是一對多關系的特殊情況。表示是類似的,但是得向數據庫添加約束以防止“多”側具有多個鏈接。雖然在某些情況下,這種關系很有用,但並不像其他關系類型常見。

表示粉絲

通過上述關系類型的學習,很容易確定跟蹤關注者的正確數據模型是多對多關系。因為一個用戶可以關注很多用戶,而一個用戶也可以被很多用戶所關注。在學生、教師的案列中,我們通過多對多關系關聯這兩個實體。但在關注案列下,我有用戶關注其他用戶,所以只有用戶。那么多對多關系的第二個實體是什么?

關系的第二個實體也是用戶。將類的實例鏈接到同一個類的其他實例的關系 稱為 自引用關系,這將是我們在此所擁有的。

下方是跟蹤關注者的自引用 多對多關系的圖表:
image.png

followers表關系的關聯表。表中外鍵都指向user表中的行,因為它將用戶鏈接到用戶。這個表中每一個記錄表示關注者用戶、關注用戶之間的一個鏈接。如同學生、教師的案列一樣,像這樣的設置允許數據庫回答有關我們將要解決的 關注、關注用戶的所有問題。

用數據庫模型表示

首先,在數據庫中添加關注者 followers,下方是followers關聯表:
app/models.py:關注者關聯表

#...
from flask_login import UserMixin

#關注者關聯表
followers = db.Table(
	'followers',
	db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
	db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
	)

class User(UserMixin, db.Model):
#...

上述代碼是上一節【跟蹤關注者的自引用 多對多關系的圖表】的的直接翻譯了。不過,注意,這里沒有聲明這個表為模型,即如user表、post表那樣。由於這是一個除了外鍵而沒有其他數據的輔助表,因此在沒有關聯模型類的情況下創建了這個表。

現在,可在user表中聲明多對多關系:
app/models.py:多對多 關注者 關系

#...

class User(UserMixin, db.Model):
	#...
	last_seen = db.Column(db.DateTime, default=datetime.utcnow)

	followed = db.relationship(
		'User',
		secondary=followers,
		primaryjoin=(followers.c.follower_id==id),
		secondaryjoin=(followers.c.followed_id==id),
		backref=db.backref('followers', lazy='dynamic'),
		lazy='dynamic'
		)

	def __repr__(self):
		return '<User {}>'.format(self.username)

	#...

這個關系的建立並非易事。如同我們為post表 一對多關系那樣,正使用 db.relationship()函數進行定義模型類中的關系。這個關系將User實例 鏈接到其他User實例,因此作為約定,假設通過此關系鏈接到一對用戶,左側用戶 關注 右側用戶。我們在左側用戶中定義了 followed 的關系,因為當我們從左側查詢這個關系時,將得到已關注的用戶列表(即 右側用戶)。下方逐個檢查db.relationship()的所有參數:

  1. User 是關系的右側實體(左側是父類)。由於這是一種自引用關系,我必須在兩邊使用相同的類。
  2. secondary 配置用於這個關系的關聯表,就是在這個類上定義的關聯表 followers
  3. primaryjoin指定了左側實體(關注者)與關聯表鏈接的條件。關系左側的連接條件是與關聯表中follower_id字段匹配的用戶ID。follwer.c.follower_id表達式引用了關聯表中follower_id列。
  4. secondaryjoin指定了右側實體(被關注者)與關聯表鏈接的條件。這個條件與primaryjoin類似,唯一不同的是:現在使用的followed_id,它是關聯表中的另一個外鍵。
  5. backref定義如何右側實體訪問這個關系。從左側開始,關系被名稱為 followed,因此右側將使用名稱followers 來表示鏈接到右側目標用戶的所有左側用戶。附加lazy 參數表示這個查詢的執行模式,設置為dynamic模式的查詢在特定請求之前不會運行,這也是我們設置帖子的一對多關系。
  6. lazy類似於同名參數backref,但這個適用於左側查詢而不是右側。

如果上述很難理解,不必擔心。接下來將會展示如何利用這些關系來執行查詢,一切將變得清晰。

數據庫的改變 需要記錄在 新的數據庫遷移中:

(venv) D:\microblog>flask db migrate -m "followers"
[2018-08-15 15:25:39,889] INFO in __init__: Microblog startup
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'followers'
Generating D:\microblog\migrations\versions\65b4b5c357fa_followers.py ... done

(venv) D:\microblog>flask db upgrade
[2018-08-15 15:26:03,154] INFO in __init__: Microblog startup
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 00cd8a8ea68a -> 65b4b5c357fa, followers

關注和取消關注

由於SQLAlchemy ORM,一個用戶關注另一用戶 的行為 能被以followed 關系如同是一個列表一樣 記錄在數據庫中,例如,假如我有兩個用戶存儲在 user1 和 user2變量中,我能夠用這個簡單語句表示 第一個用戶關注第二個用戶

user1.followed.append(user2)

停止關注這個用戶,可以這樣做:

user1.followed.remove(user2)

盡管關注、取消關注 很容易,但我們希望在代碼中提升可重用性,因此我們不會在代碼中使用 “appends”、“removes”。代替的方法是,我們將在User模型中實現“follow”和“unfollow”方法。最后是將應用程序邏輯從視圖函數移到模型或其他輔助類或模塊中,因為正如在本章看到的,這讓單元測試會變得更容易。

下方User模型中添加、刪除關系的更改:
app/models.py:添加、刪除關注者

#...
	class User(UserMixin, db.Model):
		#...
		def avatar(self, size):
			digest = md5(self.email.lower().encode('utf-8')).hexdigest()
			return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(digest, size)

		def follow(self, user):
			if not self.is_following(user):
				self.followed.append(user)

		def unfollow(self, user):
			if self.is_following(user):
				self.followed.remove(user)

		def is_following(self, user):
			return self.followed.filter(followers.c.followed_id==user.id).count()>0
#...

follow()unfollow()方法用了正如上所展示的關系對象的append()remove()方法,但是在它們接觸這個關系之前,它們用了 is_following()輔助方法以確保請求動作是有道理的。例如,假如要求 user1 關注user2,但事實證明在數據庫中已經存在這個關注關系,就不必重復了。相同的邏輯同樣運用到 取消關注。

is_following()方法在followed關系中發出一個查詢去檢查兩個用戶是否已存在鏈接。在之前看到 使用SQLAlchemyfilter_by()方法去查詢對象,例如 查找給定username的用戶。在這用到的filter()方法是類似的,但是低水平,因為它能包含任意過濾條件,不像filter_by()只能檢查與常量值的相等性。在is_following()中我們正在使用的條件 查找關聯表中的項目,左側外鍵設置為self 用戶,右側設置為 user參數。查詢以一個count()方法終止,該方法返回結果數。這個查詢結果將是0 或1,因此檢查計數為1 或大於0實際上是等效的。過去使用的前提查詢終止符是all()、first()。

獲取已關注用戶的帖子

對數據庫中的關注者的支持幾乎已經完成,但實際上還缺少一個重要的功能。在應用程序的index頁面,將顯示已登錄用戶所關注的其他所有用戶的帖子,因此,我們需要提供一個返回這些帖子的數據庫查詢。

最明顯解決方案是 運行一個返回已關注用戶列表的查詢,正如我們已知道的,它就是 user.followed.all()。接着,對這些返回的每個用戶,我們運行查詢取得帖子。一旦我們有了帖子,就將它們合並到一個列表,並按日期對它們進行排序。聽起來是這樣,其實不是!

這種方法有幾個問題。假如一個用戶關注了1000人,會發送什么?那么我得執行1000此數據庫查詢來收集所有帖子。接着需要合並、排序內存中的1000個列表。第二個問題,考慮到應用程序的主頁最終將實現分頁,因此它不會顯示所有可用帖子,僅是前幾個,如果需要,可用一個鏈接去取得更多。第三個問題,如果要按日期排序顯示帖子,如何知道哪些用戶帖子才是所有用戶中最新的?除非我得到所有帖子並先排序。這實際上是一個不能很好擴展的糟糕解決方案。

實際上並沒有辦法避免博客帖子的合並、排序,但在應用程序中執行會導致一個非常低效的過程。這類工作是關系數據庫最擅長的。數據庫具有索引,允許它以更有效的方式執行查詢、排序。因此,我們真正想要的是提出一個簡單的數據庫查詢,它定義了我們想要取得的信息,然后讓數據庫找出如何以最有效的方式提取信息。

下方就是這個查詢:
app/models.py:已關注用戶的帖子的查詢

#...
class User(UserMixin, db.Model):
	#...	
		def is_following(self, user):
			return self.followed.filter(followers.c.followed_id==user.id).count()>0

		def followed_posts(self):
			return Post.query.join(
				followers, (followers.c.followed_id==Post.user_id)).filter(
					followers.c.follower_id==self.id).order_by(
						Post.timestamp.desc())
#...

這是在這個應用程序中使用的最復雜的查詢了。這個查詢結構有3個主要部分 join()filter()order_by(),都是SQLAlchemy中查詢對象的方法。

聯合查詢

要理解 join 操作的作用,先看一個例子。假設我有一個User表,包含以下內容:
image.png

簡單起見,我們不顯示 用戶模型中的所有字段,只顯示對這個查詢的重要字段。

假設 followers 關聯表 表示用戶john正在關注用戶susan和david,用戶susan正在關注用戶mary,用戶mary正在關注david。表示這個內容的數據是:
image.png

最后,posts表 包含每個用戶的一條帖子:
image.png

這個表還省略了一些不屬於本討論范圍的字段。

這是我為這個查詢再次定義的join()

Post.query.join(followers, (followers.c.followed_id == Post.user_id))

上述代碼正在posts表上調用join 操作。第一個參數是關注者關聯表;第二個參數是連接條件。我希望數據庫創建一個臨時表,此表結合了posts表followers表的數據。這個數據將根據我作為參數傳遞的條件進行合並。

使用的條件是followers表followed_id字段 必須等於 posts表user_id字段。要執行此合並,數據庫將從posts表(join 的左側)獲取每個記錄,並附加followers表(join 的右側)中匹配條件的所有記錄。如果followers表中有多個記錄符合條件,則每個記錄條目將重復。如果對於給定的帖子在followers表沒有匹配,那么這個帖子記錄不會join操作結果中。

使用上述定義的示例數據,join操作的結果是:
image.png

注意,在所有情況下,user_idfollowed_id列是如何相等的,這是因為連接條件。來自用戶john的帖子沒有出現在上述連接表中,是因為被關注者中沒有包含用戶john,換句話說,沒有任何人關注john。而來自用戶david的帖子出現了兩次,因為這個用戶有個關注者。

雖然創建了join操作,但暫時並未得到想要的結果。請繼續,這只是更大查詢的一部分。

過濾

join操作給我一個所有被關注用戶的帖子的列表,遠超出我真正想要的那部分數據。我只對這個列表的一個子集感興趣,即某個用戶關注的用戶們的帖子。因此,我需要調用filter()來去掉不需要的數據。

下方是查詢的過濾部分:

filter(followers.c.follower_id == self.id)

由於這個查詢是位於User類中的方法,因此self.id 表達式引用了我感興趣的用戶的用戶ID。filter()選擇join表中follower_id等於這個ID的行,換句話說,我只保留follower是這個用戶的數據。

假設我感興趣的用戶是 john,其 id 字段設置為1,下方是join表在過濾后的樣子:
image.png

這些才是我想要的帖子。

記住,查詢是從Post類發出的,所以即使我最終得到了由數據庫創建的一個臨時表來作為查詢的一部分,但結果是包含在此臨時表中的帖子,並沒有額外的列是由join操作添加的。

排序

查詢流程的最后一步是 對結果就行排序。這部分的查詢語句如下:

order_by(Post.timestamp.desc())

在此,希望結果是按帖子的時間戳字段按降序排序的。通過這個排序,第一個結果將是最新的博客帖子。

結合自己和關注者的帖子

followed_posts()函數中使用的查詢非常有用,但有一限制。人們希望看到自己的帖子也包含在Ta所關注的用戶帖子的時間線中,不過這個查詢目前還未建立。

有兩種方法可擴展此查詢以包含用戶自己的帖子。最直接的方法是 保持查詢變,但要確保所有用戶都關注自己。如果你是自己的關注者,那么上面顯示的查詢將會找到你自己的帖子,以及你關注的所有人帖子。這種方法的缺點是它會影響關於關注者的統計數據。所有的關注者數量都會被加1,所以必須在顯示之前進行調整。第二個方法是通過創建第二個查詢,返回用戶自己的帖子,然后使用 union 運算符 將這兩個查詢合並為一個查詢。

在考慮了這兩個選項后,決定選擇第二個方法。下方將看到followed_posts()擴展后的功能,通過一個 union 包含用戶自己的帖子:修改代碼
app/models.py:加上用戶自己的帖子

#...
	#...

	def followed_posts(self):
		followed = Post.query.join(followers, (followers.c.followed_id == Post.user_id)).filter(followers.c.follower_id == self.id)
		own = Post.query.filter_by(user_id=self.id)
		return followed.union(own).order_by(Post.timestamp.desc())
#...

在排序之前,注意followed 和 own 查詢結果集是如何合並為一個的。

對User模型進行單元測試

雖然我不擔心建立的這個“復雜的”關注功能的實現,但我認為它並非微不足道。但當編碼重要代碼時,擔心的是確保這部分代碼將來繼續工作,因為在應用程序的不同部分進行了修改。確保已編寫代碼將來繼續工作的最佳方法是創建一套自動化測試,每次更改時都可以重新運行。

Python包含一個非常有用的 unittest包,可輕松編寫、執行單元測試。在 tests.py模塊中為User類存在的方法編寫一些單元測試:
microblog/tests.py:用戶模型單元測試

from datetime import datetime,timedelta
import unittest
from app import app,db
from app.models import User,Post

class UserModelCase(unittest.TestCase):
	def setUp(self):
		app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
		db.create_all()

	def tearDown(self):
		db.session.remove()
		db.drop_all()

	def test_password_hashing(self):
		u = User(username='susan2018')
		u.set_password('cat')
		self.assertFalse(u.check_password('dog'))
		self.assertTrue(u.check_password('cat'))

	def test_avatar(self):
		u = User(username='john', email='john@example.com')
		self.assertEqual(u.avatar(128), ('https://www.gravatar.com/avatar'
										'/d4c74594d841139328695756648b6bd6'
										'?d=identicon&s=128'))

	def test_follow(self):
		u1 = User(username='john', email='john@example.com')
		u2 = User(username='susan', email='susan@example.com')
		db.session.add(u1)
		db.session.add(u2)
		db.session.commit()
		self.assertEqual(u1.followed.all(), [])
		self.assertEqual(u2.followers.all(), [])

		u1.follow(u2)
		db.session.commit()
		self.assertTrue(u1.is_following(u2))
		self.assertEqual(u1.followed.count(), 1)
		self.assertEqual(u1.followed.first().username, 'susan')
		self.assertEqual(u2.followers.count(), 1)
		self.assertEqual(u2.followers.first().username, 'john')

		u1.unfollow(u2)
		db.session.commit()
		self.assertFalse(u1.is_following(u2))
		self.assertEqual(u1.followed.count(), 0)
		self.assertEqual(u2.followers.count(), 0)

	def test_follow_posts(self):
		#create four users
		u1 = User(username='john', email='john@example.com')
		u2 = User(username='susan', email='susan@example.com')
		u3 = User(username='mary', email='mary@example.com')
		u4 = User(username='david', email='david@example.com')
		db.session.add_all([u1, u2, u3, u4])

		#create four posts
		now = datetime.utcnow()
		p1 = Post(body="post from john", author=u1, timestamp=now+timedelta(seconds=1))
		p2 = Post(body="post from susan", author=u2, timestamp=now+timedelta(seconds=4))
		p3 = Post(body="post from mary", author=u3, timestamp=now+timedelta(seconds=3))
		p4 = Post(body="post from david", author=u4, timestamp=now+timedelta(seconds=2))
		db.session.add_all([p1, p2, p3, p4])
		db.session.commit()

		#setup the followers
		u1.follow(u2)#john follows susan
		u1.follow(u4)#john follows david
		u2.follow(u3)#susan follows mary
		u3.follow(u4)#mary follows david
		db.session.commit()

		#check the followed posts of each user
		f1 = u1.followed_posts().all()
		f2 = u2.followed_posts().all()
		f3 = u3.followed_posts().all()
		f4 = u4.followed_posts().all()
		self.assertEqual(f1, [p2, p4, p1])
		self.assertEqual(f2, [p2, p3])
		self.assertEqual(f3, [p3, p4])
		self.assertEqual(f4, [p4])

if __name__ == '__main__':
	unittest.main(verbosity=2)

上述添加了4個測試,用於在用戶模型中執行密碼哈希、用戶頭像、關注者功能。setUp()tearDown()方法是特殊方法,分別用於單元測試框架之前、每次測試后執行。

setUp()實現了一些小技巧,以防止單元測試使用我們用於開發的常規數據庫。通過將應用程序配置更改為 sqlite:// ,在測試期間,讓SQLAlchemy使用內存中的SQLite數據庫db.create_all()創建所有數據庫表。這是從頭開始創建數據庫的快速方法,可用於測試。對於開發環境和生產環境的數據庫結構管理,之前已展示過數據庫遷移的方法。

下方命令可運行整個測試組件:

C:\Users\Administrator>d:

D:\>cd D:\microblog\venv\Scripts

D:\microblog\venv\Scripts>activate
(venv) D:\microblog\venv\Scripts>cd D:\microblog

(venv) D:\microblog>python tests.py
[2018-08-16 11:42:36,534] INFO in __init__: Microblog startup
test_avatar (__main__.UserModelCase) ... ok
test_follow (__main__.UserModelCase) ... ok
test_follow_posts (__main__.UserModelCase) ... ok
test_password_hashing (__main__.UserModelCase) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.211s

OK

(venv) D:\microblog>

從現在開始,每次對應用程序進行更改時,都可以重新運行測試以確保正在測試的功能不受到影響。此外,每次向應用程序添加另一個功能時,都應為其編寫單元測試。

將關注功能集成到應用程序

數據庫、模型中的關注功能已實現,但還沒有將它納入到應用程序中,因此現在添加它。這個操作沒有挑戰,因為都是基於之前學到的概念、知識。

在應用程序中添加兩個新路由,以關注、取消關注一個用戶:

app/routes.py:關注、取消關注用戶的路由

上述代碼較簡單,不過要注意當中的 所有錯誤檢查,以防止意外問題,並在發生問題時向用戶提供有用的消息。

現在有了視圖函數,就可以從應用程序中的頁面鏈接到它們了。在每個用戶的個人資料頁面中添加鏈接以關注、取消關注用戶:

app/rtemplates/user.html:在用戶個人資料頁面中添加 關注、取消關注的鏈接

...
				{% if user == current_user %}
					<p>
						<a href="{{ url_for('edit_profile') }}">Edit your profile</a>
					</p>

				{% elif not current_user.is_following(user) %}
					<p>
						<a href="{{ url_for('follow', username=user.username) }}">Follow</a>
					</p>

				{% else %}
					<p>
						<a href="{{ url_for('unfollow', username=user.username) }}">Unfollow</a>
					</p>
				{% endif %}
...

對用戶個人資料頁面更改:一是 在最近訪問的時間戳下添加一行,以顯示該用戶擁有多少關注者和關注用戶。二是當查看自己的個人資料頁時,出現的“Edit”鏈接,可能會變成下方3種鏈接之一:

  1. 如果用戶正在查看自己的個人資料頁,那么“Edit”鏈接跟以前一樣顯示;
  2. 如果用戶正在查看當前未關注的用戶,則會顯示“Follow”鏈接;
  3. 如果用戶正在查看當前關注的用戶,則會顯示“Unfollow”鏈接。

此時,運行應用程序,創建一些用戶並測試 關注、取消關注用戶的功能。
唯一需要記住的是,鍵入要關注 或取消關注的用戶的個人資料頁面的URL,因為目前無法查看用戶列表。例如,如果要關注 susan,則可在瀏覽器地址欄輸入:http://localhost:5000/user/susan,就能訪問該用戶的個人資料頁。確保在測試關注、取消關注時,檢查關注、關注者的數量變化情況。

(venv) D:\microblog>flask run
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
[2018-08-16 14:02:45,152] INFO in __init__: Microblog startup
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [16/Aug/2018 14:02:54] "GET /user/susan2018 HTTP/1.1" 200 -

目前登錄用戶為susan2018,輸入http://127.0.0.1:5000/user/belen,頁面中顯示“Follow”鏈接,點擊它,注意頁面的變化。
image.png

image.png

再注冊用戶johnmarydavid,郵箱分別為用戶名加@example.com,密碼都為123456。
之前有兩個用戶susan(后改名susan2018,密碼cat)、belen 密碼Abc123456。
分別用這些用戶登錄、關注某些用戶。進入數據庫查看:

(venv) D:\microblog>sqlite3 app.db
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
sqlite> select * from user;
1|susan2018|susan@example.com|pbkdf2:sha256:50000$OONOkVyy$8d008c6647ab95a5793cf60bf57eaa3bb1123d6e5b3135c5cc5e42e02eddae32|I rename my name.|2018-08-16 06:16:13.474911
2|belen|belen@example.com|pbkdf2:sha256:50000$PEDt5NxS$cf6c958c97b6ad28d9495d138cb5a310f6f2389534b0cafa3002dd3cec9af9d1|學習Flask超級教程,Python Web開發學習,堅持!|2018-08-16 06:15:38.892932
3|john|john@example.com|pbkdf2:sha256:50000$vdxx4ipx$32ccbde4bc984d459c5a99935adb8c1ce8fc5c0d3e5d7f442815e5005d1a80a4||2018-08-16 06:11:21.665502
4|mary|mary@example.com|pbkdf2:sha256:50000$8q3qPO4V$040967e4481a4882d5277a52b01902b54f3af38736336c08852f2fbae7df61a6||2018-08-16 06:12:21.538626
5|david|david@example.com|pbkdf2:sha256:50000$OhRhtXc2$c03c098ee789ef9229e2f99676f173a30e876f047991744d05f11d6afe296a31||2018-08-16 06:12:50.972967
sqlite> select * from followers;
1|2
2|3
2|4
1|5
1|4
sqlite> .quit

(venv) D:\microblog>

followers表 第一列為 follower_id,第二列為 followed_id。上述含義:
1 susan2018關注了 2 belen、5 david、4 mary
2 belen 關注了 3 john、4 mary

應該在應用程序 /index頁面中顯示所有關注帖子的列表,但由於目前用戶還不能編寫博客帖子(此功能還未完成)。因此,將此推遲更改,直到該功能到位。

目前為止,項目結構:

microblog/
    app/
        templates/
	        _post.html
	        404.html
	        500.html
            base.html
            edit_profile.html
            index.html
            login.html
            register.html
            user.html
        __init__.py
        errors.py
        forms.py
        models.py
        routes.py
    logs/
        microblog.log
    migrations/
    venv/
    app.db
    config.py
    microblog.py
    tests.py

參考
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-viii-followers

SQLAlchemy復雜查詢


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM