blog.miguelgrinberg.com
ãã²ã«ã»ã°ãªã³ããŒã°
<<<å æ¬¡>>>
ãã®èšäºã¯ãMiguel Greenbergã«ããæç§æžã®æ°çã®ç¬¬8éšã®ç¿»èš³ã§ãããèè
ã¯ãã®åºçç©ã2018幎5æã«å®æãããäºå®ã§ãã
ããã¯ãFlask Mega-Tutorialã·ãªãŒãºã®ç¬¬8éšã§ãTwitterãä»ã®ãœãŒã·ã£ã«ãããã¯ãŒã¯ã®æ©èœã«äŒŒãããµãã¹ã¯ã©ã€ããŒãæ©èœã®å®è£
æ¹æ³ã説æããŸãã
åèãŸã§ã«ã以äžã¯ãã®ã·ãªãŒãºã®èšäºã®ãªã¹ãã§ãã
泚1ïŒãã®ã³ãŒã¹ã®å€ãããŒãžã§ã³ããæ¢ãã®å Žåã¯ããã¡ããã芧ãã ãã ã
泚2ïŒçªç¶ããã®ããã°ã®ç§ã®ïŒãã²ã«ïŒã®ä»äºãæ¯æããŠå£°ãäžãããå ŽåããŸãã¯åã«èšäºã1é±éåŸ
ã€å¿èããªãå Žåãç§ïŒãã²ã«ã°ãªãŒã³ããŒã°ïŒã¯ãã®ã¬ã€ãã®å®å
šçã«ããã±ãŒãžåãããé»åæžç±ãŸãã¯ãããªãæäŸããŸãã 詳现ã«ã€ããŠã¯ã learn.miguelgrinberg.comãã芧ãã ãã ã
ãã®ç« ã§ã¯ãããŒã¿ããŒã¹ã®æ§é ã«ã€ããŠããå°ã説æããŸãã ã¢ããªã±ãŒã·ã§ã³ã®ãŠãŒã¶ãŒã«ãèå³ã®ããã³ã³ãã³ãã®ãµãã¹ã¯ãªãã·ã§ã³ãç°¡åã«æŽçããŠãããããã ãããã£ãŠãããŒã¿ããŒã¹ã倿ŽããŠã誰ã誰ããã©ããŒããŠãããã远跡ã§ããããã«ããŸããããã¯ãããªããæã£ãŠãããããå°ãè€éã§ãã
ãã®ç« ã®GitHubãªã³ã¯ïŒ Browse ã Zip ã Diff ã
åã³ããŒã¿ããŒã¹æ¥ç¶
äžèšã§è¿°ã¹ãããã«ãåãŠãŒã¶ãŒã®ããã©ããŒããŠãŒã¶ãŒãšããã©ãã¯ãŒããŠãŒã¶ãŒã®ãªã¹ããç¶æããããšæããŸãã æ®å¿µãªããããªã¬ãŒã·ã§ãã«ããŒã¿ããŒã¹ã«ã¯ããããã®ãªã¹ãã«äœ¿çšã§ããlist
ã¿ã€ãããããŸãããããã¯ãã¬ã³ãŒããšãããã®ã¬ã³ãŒãéã®ãªã¬ãŒã·ã§ã³ãæã€ããŒãã«ã§ãã
ããŒã¿ããŒã¹ã«ã¯ãŠãŒã¶ãŒusers
ã衚ãããŒãã«ãããusers
ããããã£ãŠã ãã©ããŒ/ãã©ãã¯ãŒãªã³ã¯ãã¢ãã«åã§ããé©åãªã¿ã€ãã®é¢ä¿ãèãåºãå¿
èŠããããŸãã ããŒã¿ããŒã¹ã®åºæ¬çãªé¢ä¿ã®ã¿ã€ããèŠãŠã¿ãŸãããã
1察å€
第4ç« ã§ãã§ã«1察å€ã®é¢ä¿ã䜿çšããŠããŸãã ãã®æ¥ç¶ã®å³ã¯æ¬¡ã®ãšããã§ãã

ãã®é¢ä¿ã«é¢é£ä»ããããŠãã2ã€ã®ãªããžã§ã¯ãã¯ããŠãŒã¶ãŒãšã¡ãã»ãŒãžã§ãã ãŠãŒã¶ãŒã«ã¯å€ãã®ã¡ãã»ãŒãžããããã¡ãã»ãŒãžã«ã¯1人ã®ãŠãŒã¶ãŒïŒãŸãã¯äœæè
ïŒãããããšãããããŸãã æ¥ç¶ã¯ããå€ãåŽã®å€éšããŒã䜿çšããŠããŒã¿ããŒã¹ã«æç€ºãããŸãã äžèšã®æ¥ç¶ã§ã¯ãå€éšããŒã¯posts
ã¡ãã»ãŒãžããŒãã«ã«è¿œå ãããuser_id
ãã£ãŒã«ãã§ãã
ãã®ãã£ãŒã«ãã¯ãåã¡ãã»ãŒãžããŠãŒã¶ãŒããŒãã«ã®äœæè
ã®ã¬ã³ãŒãã«é¢é£ä»ããŸãã
æããã«ã user_id
ãã£ãŒã«ãã¯ãã®ã¡ãã»ãŒãžã®äœæè
ãžã®çŽæ¥ã¢ã¯ã»ã¹ãæäŸãuser_id
ããå察æ¹åã¯ã©ãã§ããããïŒ æ¥ç¶ãæå¹ã«ããã«ã¯ããã®ãŠãŒã¶ãŒãäœæããã¡ãã»ãŒãžã®ãªã¹ããååŸã§ããå¿
èŠããããŸãã
ããŒã¿ããŒã¹ã«ã¯å¹ççãªã¯ãšãªãäœæã§ããã€ã³ããã¯ã¹ãããããã posts
ããŒãã«ã®user_id
ãã£ãŒã«ãããã®è³ªåã«çããã®ã«ååã§ãããã®ããããXããuser_idãæã€ãã¹ãŠã®æçš¿ãååŸããŸããã
å€å¯Ÿå€
å€å¯Ÿå€ã®é¢ä¿ã¯ããè€éã§ãã äŸãšããŠãåŠçã students
ãæåž«ãæåž«ãããããŒã¿ããŒã¹ãèããŸãã çåŸã«ã¯ããããã®å
çãããŠãå
çã«ã¯ããããã®çåŸããããšèšããŸãã ããã¯ã䞡端ã§çžäºæ¥ç¶ããã2ã€ã®1察å€ã®é¢ä¿ã«äŒŒãŠããŸãã
ãã®ã¿ã€ãã®é¢ä¿ã§ã¯ãããŒã¿ããŒã¹ã«ã¯ãšãªãå®è¡ãããã®åŠçãæããæåž«ã®ãªã¹ããšæåž«ã¯ã©ã¹ã®åŠçã®ãªã¹ããååŸã§ããã¯ãã§ãã ããã¯ãæ¢åã®ããŒãã«ã«å€éšããŒã远å ããããšã§ã¯ã§ããªãããããªã¬ãŒã·ã§ãã«ããŒã¿ããŒã¹ã§è¡šãã®ã¯ç°¡åã§ã¯ãããŸããã
å€å€è¡šçŸã衚ãå€å¯Ÿå€ã§ã¯ã é¢é£ä»ãããŒãã«ãšåŒã°ãããã«ããŒããŒãã«ã䜿çšããå¿
èŠããããŸã ã 以äžã¯ãããŒã¿ããŒã¹ã§çåŸãšæåž«ã®æ€çŽ¢ãæŽçããäŸã§ãã

ãããããããã¯èª°ãã«ã¯äžæçã«èŠãããããããŸãããã2ã€ã®å€éšããŒãšã®é¢é£ä»ãã®ããŒãã«ã¯ãé¢ä¿ã«å¯Ÿãããã¹ãŠã®èŠæ±ã«å¹æçã«çããŸãã
å€å¯Ÿ1ããã³1察1
å€å¯Ÿ1ã¯ã1察å€ã®é¢ä¿ã®ãããªãã®ã§ãã éãã¯ããã®é¢ä¿ããå€ãã®ãåŽããèŠãããããšã§ãã
1察1ã¯1察å€ã®ç¹æ®ãªã±ãŒã¹ã§ãã ãã¥ãŒã¯äŒŒãŠããŸãããããŒã¿ããŒã¹ã«å¶éã远å ãããŠå€åŽãé²ããè€æ°ã®ãªã³ã¯ãçŠæ¢ããŸãã
ãã®ã¿ã€ãã®é¢ä¿ã圹ç«ã€å ŽåããããŸãããä»ã®ã¿ã€ãã»ã©äžè¬çã§ã¯ãããŸããã
ãµãã¹ã¯ã©ã€ããŒéä¿¡
äžèšã®ãã¹ãŠã®ã¿ã€ãã®é¢ä¿ã®åæã®åèšããã远跡ãããïŒãã©ããŒïŒãå€ãã®ãŠãŒã¶ãŒïŒãŠãŒã¶ãŒïŒãç£èŠãããŠãŒã¶ãŒïŒãŠãŒã¶ãŒïŒãå€ãã®ãµãã¹ã¯ã©ã€ããŒãæã£ãŠããããã ãã©ãã¯ãŒã远跡ããããã®æ£ããããŒã¿ã¢ãã«ãå€å¯Ÿå€ã®é¢ä¿ã§ãããšç°¡åã«å€æã§ããŸãïŒãã©ãã¯ãŒïŒã ããããã»ããã¢ããããããŸãã çåŸãšæåž«ã®äŸã§ã¯ãå€å¯Ÿå€ã®é¢ä¿ã§çžäºæ¥ç¶ããã2ã€ã®ãªããžã§ã¯ãããããŸããã ãããããã©ãã¯ãŒã®å Žåãä»ã®ãŠãŒã¶ãŒããã©ããŒãããŠãŒã¶ãŒãããã®ã§ããŠãŒã¶ãŒã®ã¿ãããŸãã ããã§ã¯ã2çªç®ã®å€å¯Ÿå€ã®é¢ä¿æ§é ãšã¯äœã§ããïŒ
2çªç®ã®é¢ä¿ãªããžã§ã¯ãããŠãŒã¶ãŒã§ãã
ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ãåãã¯ã©ã¹ã®ä»ã®ã€ã³ã¹ã¿ã³ã¹ã«é¢é£ä»ããããŠããé¢ä¿ã¯ãèªå·±åç
§é¢ä¿ãšåŒã°ããããã¯ãŸãã«ããã«ãããã®ã§ãã
èªå·±åç
§åã®å€å¯Ÿå€ã®ãµãã¹ã¯ã©ã€ããã©ããã³ã°ã®ãã£ãŒããæ¬¡ã«ç€ºããŸãã

followers
ããŒãã«ã¯ãé¢ä¿ã®é¢é£ä»ãã®ããŒãã«ãŸãã¯çžå¯Ÿçãªé¢ä¿ã®ããŒãã«ã§ãã ãã®ããŒãã«ã®å€éšããŒã¯ããŠãŒã¶ãŒããŠãŒã¶ãŒã«é¢é£ä»ããããããŠãŒã¶ãŒããŒãã«ã®ãšã³ããªãæããŸãã ãã®è¡šã®åãšã³ããªã¯ããã©ãã¯ãŒãŠãŒã¶ãŒããµãã¹ã¯ã©ã€ãããŠãããŠãŒã¶ãŒãšããã©ããŒããŠãããŠãŒã¶ãŒããµãã¹ã¯ã©ã€ãããŠãããŠãŒã¶ãŒãšã®é¢ä¿ã衚ããŸãã åŠçãšæåž«ã®äŸãšããŠããã®ãããªèšå®ã«ãããããŒã¿ããŒã¹ã¯ç§ãå¿
èŠãšãã眲åè
ãšè³Œèªè
ã«é¢ãããã¹ãŠã®è³ªåã«çããããšãã§ããŸãã ãããã§ãã
ããŒã¿ããŒã¹ã¢ãã«ã®è¡šçŸ
æåã«ããŒã¿ããŒã¹ã«ãã©ãã¯ãŒã远å ããŸãããã å å
¥è
ã®é¢é£ä»ãã®è¡šã¯æ¬¡ã®ãšããã§ãã
followers = db.Table('followers', db.Column('follower_id', db.Integer, db.ForeignKey('user.id')), db.Column('followed_id', db.Integer, db.ForeignKey('user.id')) )
ããã¯ãäžèšã®ãã£ãŒãã®é¢é£ããŒãã«ã®ã©ã€ã倿ã§ãã ãŠãŒã¶ãŒããŒãã«ãšã¡ãã»ãŒãžããŒãã«ã§è¡ã£ãããã«ããã®ããŒãã«ãã¢ãã«ãšããŠå®£èšããŠããªãããšã«æ³šæããŠãã ããã ããã¯å€éšããŒä»¥å€ã®ããŒã¿ãæããªããã«ããŒããŒãã«ãªã®ã§ã察å¿ããã¢ãã«ã¯ã©ã¹ãªãã§äœæããŸããã
ããã§ãusersããŒãã«ã§å€å¯Ÿå€ã®é¢ä¿ã宣èšã§ããŸãã
class User(UserMixin, db.Model): # ... 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')
ãæ³šæ 翻蚳è
ã®ãã©ãã¯ãŒã c .follower_idãcãã¯ãã¢ãã«ãšããŠå®çŸ©ãããŠããªãSQLAlchemyããŒãã«ã®å±æ§ã§ãã ãããã®ããŒãã«ã®å ŽåãããŒãã«åã¯ãã®c屿§ã®ãµã屿§ãšããŠè¡šç€ºãããŸãã
é¢ä¿ã®èšå®ã¯ç°¡åã§ã¯ãããŸããã 1 db.relationship
posts
é¢ä¿ãšåæ§ã«ã db.relationship
颿°ã䜿çšããŠãã¢ãã«ã¯ã©ã¹ã®é¢ä¿ãå®çŸ©ããŸãã ãã®é¢ä¿ã¯ã User
ã€ã³ã¹ã¿ã³ã¹ãä»ã®User
ã€ã³ã¹ã¿ã³ã¹ã«ãªã³ã¯ãUser
ãæ
£äŸã«ããããã®é¢ä¿ã«é¢é£ä»ãããããŠãŒã¶ãŒã®ãã¢ã«å¯ŸããŠãå·ŠåŽã®ãŠãŒã¶ãŒãå³åŽã®ãŠãŒã¶ãŒãç£èŠããŠãããšèšããŸãã æ¥ç¶ãå®çŸ©ããŸããå·ŠåŽã«ã followed
ããšããååfollowed
ããã®æ¥ç¶ãå·ŠåŽããèŠæ±ãããšãåŸç¶ã®ãŠãŒã¶ãŒïŒã€ãŸãå³åŽã®ãŠãŒã¶ãŒïŒã®ãªã¹ãã衚瀺ãããããã§ãã db.relationship()
ãåŒã³åºãããã®ãã¹ãŠã®åŒæ°ã1ã€ãã€èŠãŠã¿ãŸãããã
'User'
ã¯ãªã³ã¯ã®å³åŽã§ãïŒå·ŠåŽã¯èŠªã¯ã©ã¹ã§ãïŒã ããã¯èªå·±åç
§é¢ä¿ãªã®ã§ãäž¡åŽã§åãã¯ã©ã¹ã䜿çšããå¿
èŠããããŸããsecondary
ã¯ããã®ã¯ã©ã¹ã®ããäžã§å®çŸ©ãããã®é¢é£ä»ãã«äœ¿çšãããé¢é£ä»ãããŒãã«ãæ§æããŸããprimaryjoin
ã¯ãå·ŠåŽã®ãªããžã§ã¯ãïŒãã©ãã¯ãŒãŠãŒã¶ãŒïŒãé¢é£ä»ãããŒãã«ã«é¢é£ä»ããæ¡ä»¶ã瀺ããŸãã ãªã³ã¯ã®å·ŠåŽã®çµåæ¡ä»¶ã¯ãé¢é£ä»ãããŒãã«ã®follower_id
ãã£ãŒã«ãã«å¯Ÿå¿ãããŠãŒã¶ãŒèå¥åã§ãã åŒfollowers.c.follower_id
ã¯ãé¢é£ä»ãããŒãã«ã®follower_id
åãåç
§ããŸããsecondaryjoin
ã¯ãå³åŽã®ãªããžã§ã¯ãïŒãã©ããŒããŠãããŠãŒã¶ãŒïŒãé¢é£ä»ãããŒãã«ã«é¢é£ä»ããæ¡ä»¶ãå®çŸ©ããŸãã ãã®æ¡ä»¶ã¯primaryjoin
ã«äŒŒãŠããŸãããå¯äžã®éãã¯ãé¢é£ããŒãã«ã®å¥ã®å€éšããŒã§ããfollowed_id
䜿çšããããšã§ããbackref
ã¯ããªããžã§ã¯ãã®å³åŽãããã®ãªã³ã¯ã«ã¢ã¯ã»ã¹ããæ¹æ³ã決å®ããŸãã é¢ä¿ã®å·ŠåŽã§ã¯ããŠãŒã¶ãŒã¯followdãšåŒã°ãããããå³åŽã§ã¯ãååfollowers
ã䜿çšããŠãå³åŽã®ã¿ãŒã²ãããŠãŒã¶ãŒã«é¢é£ä»ããããŠãããã¹ãŠã®å·ŠåŽã®ãŠãŒã¶ãŒã衚ããŸãã ãªãã·ã§ã³ã®lazy
åŒæ°ã¯ããã®ãªã¯ãšã¹ãã®å®è¡ã¢ãŒãã瀺ããŸãã dynamic
èŠæ±èª¿æŽã¢ãŒãã§ã¯ãç¹å®ã®èŠæ±ãå®è¡ããããŸã§èµ·åã§ããŸãããããã¯ã1察å€ã®é¢ä¿ãã©ã®ããã«ç¢ºç«ããããã«é¢é£ããŠããŸãã
backref
ã¯backref
ã§åãååã®ãã©ã¡ãŒã¿ãŒã®ããã«èŠããŸãããããã¯å³åŽã§ã¯ãªãå·ŠåŽãåç
§ããŠããŸãã
çè§£ããã®ãé£ããå Žåã§ãå¿é
ããªãã§ãã ããã ãããã®ãªã¯ãšã¹ããåŠçããæ¹æ³ã説æãããšãããã«ãã¹ãŠãæç¢ºã«ãªããŸãã
ããŒã¿ããŒã¹ãžã®å€æŽã¯ãæ°ããããŒã¿ããŒã¹ç§»è¡ã§èšé²ããå¿
èŠããããŸãã
(venv) $ flask db migrate -m "followers" 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 /home/miguel/microblog/migrations/versions/ae346256b650_followers.py ... done (venv) $ flask db upgrade INFO [alembic.runtime.migration] Context impl SQLiteImpl. INFO [alembic.runtime.migration] Will assume non-transactional DDL. INFO [alembic.runtime.migration] Running upgrade 37f06a334dbf -> ae346256b650, followers
ããã©ããŒãã®è¿œå ãšåé€ïŒãµãã¹ã¯ã©ã€ããŒïŒ
SQLAlchemy ORMã®ãããã§ãå¥ã®ãŠãŒã¶ãŒã远跡ããããã«ãµã€ã³ã¢ããããŠãããŠãŒã¶ãŒã¯ããªã¹ãã®ããã«ããŒã¿ããŒã¹ã«æžã蟌ãããšãã§ããŸãã ããšãã°ã倿°user1
ããã³user2
ã«2人ã®ãŠãŒã¶ãŒãæ ŒçŽãããŠããå Žåãæ¬¡ã®ç°¡åãªã¹ããŒãã¡ã³ãã§æåã®user1
2çªç®ã®user2
远åŸãããããšãã§ãuser2
ã
user1.followed.append(user2)
user1
ãuser2
远跡ãæåŠããã«ã¯ã次ã®ããã«ããŸãã
user1.followed.remove(user2)
ãµãã¹ã¯ã©ã€ããŒã®è¿œå ãšåé€ã¯éåžžã«ç°¡åã§ãããã³ãŒãã§åå©çšãããããããã®ã§ãã远å ãïŒè¿œå ïŒããã³ãåé€ãïŒã³ãŒããä»ããŠïŒããã€ããã¯ãããŸããã 代ããã«ãããã©ããŒãããã³ããã©ããŒè§£é€ãæ©èœãUser
ã¢ãã«ã®ã¡ãœãããšããŠå®è£
ãUser
ã ã¢ããªã±ãŒã·ã§ã³ããžãã¯ã衚瀺æ©èœããã¢ãã«ãŸãã¯ä»ã®è£å©ã¯ã©ã¹ãŸãã¯ã¢ãžã¥ãŒã«ã«ç§»åãããšããã®ç« ã®åŸåã§èª¬æããããã«ããŠããããã¹ããã¯ããã«ç°¡åã«ãªããããåžžã«åªããŠããŸãã
以äžã¯ãé¢ä¿ã远å ããã³åé€ããããã®ãŠãŒã¶ãŒã¢ãã«ã®å€æŽç¹ã§ãã
class User(UserMixin, db.Model): #... 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()
checkã¡ãœããã䜿çšããŠãèŠæ±ãããã¢ã¯ã·ã§ã³ãæå³ããªãããšã確èªããŸãã ããšãã°ã user1ã«user2ãç£èŠããããã«äŸé Œãããããã®ãããªã¿ã¹ã¯ãããŒã¿ããŒã¹ã«ãã§ã«ååšããããšã倿ããå Žåããªããããè€è£œããã®ã§ããããã åãããžãã¯ãunfollowing
ã«é©çšã§ããŸãã
is_following()
ã¡ãœããã¯ã2人ã®ãŠãŒã¶ãŒã®éã«é¢ä¿ãååšãããã©ããã確èªããèŠæ±ãçæããŸãã filter_by()
ã¯ãSQLAlchemyã¯ãšãªã®filter_by()
ã¡ãœããã䜿çšããŠãããšãã°ãŠãŒã¶ãŒåã§ãŠãŒã¶ãŒãæ€çŽ¢ããŠããŸããã ããã§äœ¿çšããfilter()
ã¡ãœããã¯äŒŒãŠããŸãããããäœãã¬ãã«ã§ããããã¯ã宿°å€ã®ç䟡æ§ã®ã¿ããã§ãã¯ã§ããfilter_by()
ãšã¯ç°ãªããä»»æã®ãã£ã«ã¿ãªã³ã°æ¡ä»¶ãå«ããããšãã§ããããã§ãã is_following()
䜿çšããæ¡ä»¶ã¯ãå·ŠåŽã®å€éšããŒããŠãŒã¶ãŒself
èšå®ãããå³åŽãuser
åŒæ°ã«èšå®ãããŠããé¢é£ä»ãããŒãã«å
ã®èŠçŽ ãæ€çŽ¢ããŸãã èŠæ±ã¯ãã¬ã³ãŒãæ°ãè¿ãcount()
ã¡ãœããã§çµäºããŸãã ãã®ã¯ãšãªã®çµæã¯0
ãŸãã¯1
ã«ãªããããã«ãŠã³ã¿ãŒã1以äžã§ãããã0ãã倧ãããã確èªããããšã¯å®éã«ã¯åçã§ãã éå»ã«èŠãä»ã®ã¯ãšãªã¿ãŒãããŒã¿ã¯ã all()
ãšfirst()
ã§ãã
ãã©ããŒããŠãããŠãŒã¶ãŒããã¡ãã»ãŒãžãåä¿¡ãã
ããŒã¿ããŒã¹å
ã®ãµãã¹ã¯ã©ã€ããŒã®ãµããŒãã¯ã»ãŒå®äºããŠããŸãããå®éã«ã¯éèŠãªæ©èœã¯ãããŸããã ã¢ããªã±ãŒã·ã§ã³ã®ã€ã³ããã¯ã¹ããŒãžã§ãç»é²ãŠãŒã¶ãŒããã©ããŒããŠãããã¹ãŠã®äººãæžããããã°ãšã³ããªã衚瀺ããããããããã®ã¡ãã»ãŒãžãè¿ãããŒã¿ããŒã¹ã¯ãšãªãçæããå¿
èŠããããŸãã
æãæãããªè§£æ±ºçã¯ããã©ããŒããŠãããŠãŒã¶ãŒã®ãªã¹ããè¿ãã¯ãšãªãå®è¡ããããšã§ããããã¯ããåãã®ãšããã user.followed.all()
ã§ãã æ¬¡ã«ããããã®ãŠãŒã¶ãŒããšã«ãã¯ãšãªãå®è¡ããŠã¡ãã»ãŒãžãåä¿¡ã§ããŸãã ãã¹ãŠã®ã¡ãã»ãŒãžãæã£ããããããã1ã€ã®ãªã¹ãã«ãŸãšããŠæ¥ä»é ã«äžŠã¹æ¿ããããšãã§ããŸãã ããã¯ããã§ããïŒ ãŸããããã§ããªãã
ãã®ã¢ãããŒãã«ã¯ããã€ãã®åé¡ããããŸãã ãŠãŒã¶ãŒã®ãµãã¹ã¯ãªãã·ã§ã³ãæ°å人ã«ãªããšã©ããªããŸããïŒ ãã¹ãŠã®ã¡ãã»ãŒãžãåéããã«ã¯ãæ°åã®ããŒã¿ããŒã¹ã¯ãšãªãå®è¡ããå¿
èŠããããŸãã ãããŠãã¡ã¢ãªå
ã®äœåãã®ãªã¹ããçµåããŠãœãŒãããå¿
èŠããããŸãã äºæ¬¡çãªåé¡ãšããŠãã¢ããªã±ãŒã·ã§ã³ã®ããŒã ããŒãžãæçµçã«ããŒãžåå²ããããããå©çšå¯èœãªãã¹ãŠã®ã¡ãã»ãŒãžã§ã¯ãªããå¿
èŠã«å¿ããŠè©³çްãååŸããããã®ãªã³ã¯ãå«ãæåã®æ°ããŒãžã®ã¿ã衚瀺ããŸãã ã¡ãã»ãŒãžãæ¥ä»é ã«ãœãŒãããŠè¡šç€ºããå Žåããã¹ãŠã®ã¡ãã»ãŒãžãååŸããŠæåã«ãœãŒãããªãéãããã©ããŒããŠããïŒãã©ããŒããŠããïŒãŠãŒã¶ãŒã®æåŸã®ã¡ãã»ãŒãžãèŠã€ããã«ã¯ã©ãããã°ããã§ããïŒ ããã¯éåžžã«äžæ°å³ãªãœãªã¥ãŒã·ã§ã³ã§ãããé©åã«æ¡åŒµã§ããŸããã
ãã®ããã°æçš¿ã®ããŒãªã³ã°ãšãœãŒããåé¿ããæ¹æ³ã¯ãããŸããããã¢ããªã±ãŒã·ã§ã³ã§ãããè¡ããšãéåžžã«éå¹ççãªããã»ã¹ã«ã€ãªãããŸãã ãã®çš®ã®äœæ¥ã¯ããªã¬ãŒã·ã§ãã«ããŒã¿ããŒã¹ãç°ãªããã®ã«ããŸãã ããŒã¿ããŒã¹ã«ã¯ãããå¹ççãªæ¹æ³ã§ã¯ãšãªãšãœãŒããå®è¡ã§ããã€ã³ããã¯ã¹ããããŸãã ãããã£ãŠãåä¿¡ãããæ
å ±ãå®çŸ©ããåäžã®ããŒã¿ããŒã¹ã¯ãšãªãäœæãããã®æ
å ±ãæãå¹ççãªæ¹æ³ã§æœåºããæ¹æ³ãããŒã¿ããŒã¹ã«çè§£ãããŸãã
ãªã¯ãšã¹ãã¯æ¬¡ã®ãšããã§ãã
class User(db.Model): #... 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())
ããã¯ããããããã®ã¢ããªã±ãŒã·ã§ã³ã§äœ¿çšããæãè€éãªã¯ãšãªã§ãã äžåºŠã«ãã®èŠæ±ãè§£èªããããšããŸãã ãã®ã¯ãšãªã®æ§é ãèŠããšãSQLAlchemyã¯ãšãªãªããžã§ã¯ãã®join()
ã filter()
ããã³order_by()
ã¡ãœããã«ãã£ãŠéçºããã3ã€ã®äž»èŠãªã»ã¯ã·ã§ã³ãããããšãããããŸãã
Post.query.join(...).filter(...).order_by(...)
ãŠããªã³æäœ-çµå
çµåæäœã®æ©èœãçè§£ããããã«ãäŸãèŠãŠã¿ãŸãããã æ¬¡ã®å
容ã®UserããŒãã«ããããšããŸãã

ç°¡åã«ããããã«ããŠãŒã¶ãŒã¢ãã«ã®ãã¹ãŠã®ãã£ãŒã«ãã§ã¯ãªãããã®èŠæ±ã«éèŠãªãã£ãŒã«ãã®ã¿ã瀺ããŸãã
followers
é¢é£ä»ãããŒãã«ã«ã john
susan
ãšsusan
ãã susan
ã¯mary
ãã mary
ã¯susan
ãããšããŸãã äžèšã®ããŒã¿ã¯æ¬¡ã®ãšããã§ãã

ãã®çµæã posts
ããŒãã«ã¯åãŠãŒã¶ãŒãã1ã€ã®æçš¿ãè¿ããŸãã
ãã®ãªã¯ãšã¹ãã«å¯ŸããŠå®çŸ©ããjoin()
åŒã³åºãã¯æ¬¡ã®ãšããã§ãã
Post.query.join(followers, (followers.c.followed_id == Post.user_id))
postsããŒãã«ã§çµåæäœãåŒã³åºããŸãã æåã®åŒæ°ã¯ãµãã¹ã¯ã©ã€ããŒã¢ãœã·ãšãŒã·ã§ã³ããŒãã«ã§ã2çªç®ã®åŒæ°ã¯çµåæ¡ä»¶ã§ãã ãã®åŒã³åºãã§æ§ç¯ããŠããã®ã¯ãæçš¿ããŒãã«ãšè³Œèªè
ããŒãã«ã®ããŒã¿ãçµåããäžæããŒãã«ãããŒã¿ããŒã¹ã§äœæããããšã§ãã ããŒã¿ã¯ãåŒæ°ãšããŠæž¡ããæ¡ä»¶ã«åŸã£ãŠçµåãããŸãã
䜿çšããæ¡ä»¶ã§ã¯ããã©ãã¯ãŒããŒãã«ã®followed_id
ãã£ãŒã«ãuser_id
ã postsããŒãã«ã®user_id
ãšçãããªããã°ãªããŸããã ãã®ããŒãžãå®è¡ããããã«ãããŒã¿ããŒã¹ã¯ã¡ãã»ãŒãžããŒãã«ïŒæ¥ç¶ã®å·ŠåŽïŒããåã¬ã³ãŒããååŸãã followers
ããŒãã«ïŒæ¥ç¶ã®å³åŽïŒããæ¡ä»¶ã«äžèŽããã¬ã³ãŒãã远å ããŸãã followers
è€æ°ã®ã¬ã³ãŒããæ¡ä»¶ãæºãããŠããå Žåãããããã®ã¬ã³ãŒããç¹°ãè¿ãããŸãã ãã©ãã¯ãŒã«ãã®ã¡ãã»ãŒãžã«äžèŽãããã®ããªãå Žåããã®ãšã³ããªã¯åå ã®äžéšã§ã¯ãããŸããã
çµåæäœã®çµæïŒ

ããã¯çµåæ¡ä»¶ã§ããããããã¹ãŠã®å Žåã§user_id
åãšfollowed_id
åãçããããšã«æ³šæããŠãã ããã johnãé¢å¿ã®ãããŠãŒã¶ãŒã§ãããµãã¹ã¯ãªãã·ã§ã³ãšã³ããªããªããããã€ãŸããjohnã¡ãã»ãŒãžããã©ããŒããŠãããŠãŒã¶ãŒãããªããããjohnããã®ã¡ãã»ãŒãžã¯åå ããŒãã«ã«è¡šç€ºãããŸããã ãã ãã2人ã®ç°ãªããŠãŒã¶ãŒããã®ãŠãŒã¶ãŒããã©ããŒããŠãããããdavidãšã³ããªã2å衚瀺ãããŸãã
ãã®ãªã¯ãšã¹ããå®äºããããšã§äœãåŸããããã¯ããã«ã¯ããããŸããããããã¯å€§ããªãªã¯ãšã¹ãã®äžéšã«éããªããããç¶ç¶ããŠããŸãã
ãã£ã«ã¿ãŒ
åå æäœã«ãããäžéšã®ãŠãŒã¶ãŒããã©ããŒããŠãããã¹ãŠã®ã¡ãã»ãŒãžã®ãªã¹ãã衚瀺ãããããã¯æ¬åœã«å¿
èŠãªããŒã¿éãè¶
ããŠããŸãã ãã®ãªã¹ãã®ãµãã»ãããã€ãŸã1人ã®ãŠãŒã¶ãŒã®ã¿ããã©ããŒããŠããã¡ãã»ãŒãžã«ã®ã¿é¢å¿ããããããäžèŠãªã¬ã³ãŒãããã¹ãŠããªãã³ã°ããå¿
èŠãããã filter()
åŒã³åºããŠãããè¡ãããšãã§ããŸãã
ã¯ãšãªãã£ã«ã¿ãŒããŒãã¯æ¬¡ã®ãšããã§ãã
filter(followers.c.follower_id == self.id)
ãã®ãªã¯ãšã¹ãã¯User
ã¯ã©ã¹ã®ã¡ãœããã«ãããããåŒself.id
ã¯ãé¢å¿ã®ãããŠãŒã¶ãŒã®ãŠãŒã¶ãŒèå¥åãåç
§ããŸãã filter()
åŒã³åºãã¯ã follower_id
åããã®ãŠãŒã¶ãŒãæãçµåããŒãã«å
ã®ã¢ã€ãã ãéžæããŸããã€ãŸãããã®ãŠãŒã¶ãŒããµãã¹ã¯ã©ã€ããŒã§ãããšã³ããªãŒã®ã¿ãä¿åããããšãæå³ããŸãã
id
ãã£ãŒã«ãã1
èšå®ãããŠãããŠãŒã¶ãŒjohn
èå³ããããšããŸãã ãã£ã«ã¿ãªã³ã°åŸã®ã¯ãšãªçµæã¯æ¬¡ã®ããã«ãªããŸãã

ãããŠããããã¯ãŸãã«ç§ãèŠããã£ãæçš¿ã§ãïŒ
ã¯ãšãªã¯Postã¯ã©ã¹ã«å¯ŸããŠéä¿¡ããããã®ã§ããããããã®ã¯ãšãªã®äžéšãšããŠããŒã¿ããŒã¹ã«ãã£ãŠäœæãããäžæããŒãã«ãåãåã£ããšããŠããçµæã¯ãã®äžæããŒãã«ã«å«ãŸããã¬ã³ãŒãã«ãªããçµåæäœã«ãã£ãŠè¿œå ã®åã远å ãããããšã¯ãããŸããã
ä»åã
æåŸã®ã¹ãããã¯ãçµæããœãŒãããããšã§ãã èªã¿åãã®ãªã¯ãšã¹ãã®éšåïŒ
order_by(Post.timestamp.desc())
ããã§ã¯ãçµæãã¡ãã»ãŒãžã®ã¿ã€ã ã¹ã¿ã³ããã£ãŒã«ãã§éé ã§äžŠã¹æ¿ããããšèšããŸãã ãã®æ¡ä»¶äžã§ã¯ãæåã®çµæã¯ææ°ã®ããã°æçš¿ã«ãªããŸãã
ç¬èªã®ã¡ãã»ãŒãžãšè³Œèªæžã¿ã¡ãã»ãŒãžã®çµã¿åãã
followed_posts ()
颿°ã§ç€ºãããªã¯ãšã¹ãã¯éåžžã«äŸ¿å©ã§ããã1ã€ã®å¶éããããŸãã 人ã
ã¯èªåã®ã¡ãã»ãŒãžã幎衚ã«çœ²åæžã¿ã®ã¡ãã»ãŒãžãšäžç·ã«èŠãããšãæåŸ
ããŠããŸãããããã§ã¯ãããŸããã§ããã ãªã¯ãšã¹ãã«ã¯ãã®æ©èœã¯ãããŸããã
ç¬èªã®ãŠãŒã¶ãŒã¬ã³ãŒããå«ããããšã«ããããã®ã¯ãšãªãæ¡åŒµããã«ã¯2ã€ã®æ¹æ³ããããŸããæãç°¡åãªæ¹æ³ã¯ããªã¯ãšã¹ãããã®ãŸãŸã«ããããšã§ããããã¹ãŠã®ãŠãŒã¶ãŒãèªåèªèº«ãèŠãŠããããšã確èªããŠãã ãããããªããããªãèªèº«ã®å å
¥è
ã§ããå Žåãäžèšã®ããã«ããªã¯ãšã¹ãã¯ããªãã«èå³ããããã¹ãŠã®äººã®ãªã¯ãšã¹ããšå
±ã«ããªãèªèº«ã®ã¡ãã»ãŒãžãèŠã€ããŸãããã®æ¹æ³ã®æ¬ ç¹ã¯ããµãã¹ã¯ã©ã€ãã«é¢ããçµ±èšã«åœ±é¿ããããšã§ãããã¹ãŠã®ã«ãŠã³ã¿ãŒã1ã€å¢ããããã衚瀺ããåã«èª¿æŽããå¿
èŠããããŸãããããè¡ã2çªç®ã®æ¹æ³ã¯ããŠãŒã¶ãŒèªèº«ã®ã¡ãã»ãŒãžãè¿ã2çªç®ã®ã¯ãšãªãäœæãããŠããªã³æŒç®åã䜿çšããŠ2ã€ã®ã¯ãšãªã1ã€ã«çµåããããšã§ãã
, . follow_posts()
, , :
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
, .
UnitTest User Model
, «» , , . , , , , . , , , , â , , .
Python unittest
, . User
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='susan') 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(u1.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)
, , . setUp()
tearDown()
â , . setUp()
, , . sqlite://
SQLAlchemy SQLite . db.create_all()
. , . , .
:
(venv) $ python tests.py 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.494s OK
, , , , . , , , .
, , , . , , , .
, :
@app.route('/follow/<username>') @login_required def follow(username): user = User.query.filter_by(username=username).first() if user is None: flash('User {} not found.'.format(username)) return redirect(url_for('index')) if user == current_user: flash('You cannot follow yourself!') return redirect(url_for('user', username=username)) current_user.follow(user) db.session.commit() flash('You are following {}!'.format(username)) return redirect(url_for('user', username=username)) @app.route('/unfollow/<username>') @login_required def unfollow(username): user = User.query.filter_by(username=username).first() if user is None: flash('User {} not found.'.format(username)) return redirect(url_for('index')) if user == current_user: flash('You cannot unfollow yourself!') return redirect(url_for('user', username=username)) current_user.unfollow(user) db.session.commit() flash('You are not following {}.'.format(username)) return redirect(url_for('user', username=username))
, , , , .
, View , . :
... <h1>User: {{ user.username }}</h1> {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %} {% if user.last_seen %}<p>Last seen on: {{ user.last_seen }}</p>{% endif %} <p>{{ user.followers.count() }} followers, {{ user.followed.count() }} following.</p> {% 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), :



, . , , URL , , . , susan
, http://localhost:5000/user/susan , . , , , .
c index , , , . , .
<<<å æ¬¡>>>