บทช่วยสอน Django, Flask และ Redis: การจัดการเซสชันแอปพลิเคชันเว็บระหว่าง Python Frameworks

เผยแพร่แล้ว: 2022-03-11

Django กับ Flask: เมื่อ Django เป็นทางเลือกที่ผิด

ฉันรักและใช้ Django ในโครงการส่วนบุคคลและโครงการของลูกค้าของฉัน ส่วนใหญ่สำหรับแอปพลิเคชันเว็บแบบคลาสสิกและที่เกี่ยวข้องกับฐานข้อมูลเชิงสัมพันธ์ อย่างไรก็ตาม Django ไม่ใช่กระสุนเงิน

จากการออกแบบ Django นั้นมีความเชื่อมโยงอย่างแน่นหนากับวัตถุ ORM, Template Engine System และการตั้งค่า นอกจากนี้ นี่ไม่ใช่โครงการใหม่: มีสัมภาระจำนวนมากเพื่อให้ใช้งานร่วมกันได้แบบย้อนหลัง

นักพัฒนา Python บางคนมองว่านี่เป็นปัญหาใหญ่ พวกเขาบอกว่า Django ไม่ยืดหยุ่นเพียงพอและหลีกเลี่ยงถ้าเป็นไปได้ และใช้ไมโครเฟรมเวิร์ก Python แทน Flask แทน

ฉันไม่แบ่งปันความคิดเห็นนั้น Django นั้นยอดเยี่ยมเมื่อใช้ในสถานที่และเวลาที่เหมาะสม แม้ว่าจะไม่เหมาะกับ ทุก ข้อกำหนดของโครงการก็ตาม ตามคติที่ว่า: “ใช้เครื่องมือที่เหมาะสมสำหรับงาน”

(แม้ว่าจะไม่ใช่สถานที่และเวลาที่เหมาะสม แต่บางครั้งการเขียนโปรแกรมกับ Django ก็มีประโยชน์ที่ไม่เหมือนใคร)

ในบางกรณี อาจเป็นเรื่องดีที่จะใช้เฟรมเวิร์กที่มีน้ำหนักเบากว่า (เช่น Flask) บ่อยครั้งที่ไมโครเฟรมเวิร์กเหล่านี้เริ่มฉายแววเมื่อคุณรู้ว่าแฮ็คได้ง่ายเพียงใด

ไมโครเฟรมเวิร์กเพื่อการช่วยเหลือ

ในโครงการลูกค้าบางส่วนของฉัน เราได้พูดคุยถึงการเลิกใช้ Django และย้ายไปยังไมโครเฟรมเวิร์ก โดยทั่วไปแล้วเมื่อลูกค้าต้องการทำสิ่งที่น่าสนใจ (ในกรณีหนึ่ง เช่น การฝัง ZeroMQ ในออบเจกต์แอปพลิเคชัน) และโครงการ ดูเหมือนยากกว่าที่จะบรรลุกับ Django

โดยทั่วไปแล้ว ฉันพบว่า Flask มีประโยชน์สำหรับ:

  • แบ็กเอนด์ REST API อย่างง่าย
  • แอปพลิเคชันที่ไม่ต้องการการเข้าถึงฐานข้อมูล
  • เว็บแอปที่ใช้ NoSQL
  • เว็บแอปที่มีข้อกำหนดเฉพาะ เช่น การกำหนดค่า URL ที่กำหนดเอง

ในเวลาเดียวกัน แอพของเราต้องลงทะเบียนผู้ใช้และงานทั่วไปอื่น ๆ ที่ Django แก้ไขเมื่อหลายปีก่อน เนื่องจากน้ำหนักเบา Flask จึงไม่มีชุดเครื่องมือเดียวกัน

มีคำถามเกิดขึ้น: Django เป็นข้อตกลงทั้งหมดหรือไม่?

มีคำถามเกิดขึ้น: Django เป็นข้อตกลงทั้งหมดหรือไม่? เราควรปล่อยมัน ออก จากโปรเจ็กต์ทั้งหมด หรือเราสามารถเรียนรู้ที่จะรวมเข้ากับความยืดหยุ่นของไมโครเฟรมเวิร์กอื่นๆ หรือเฟรมเวิร์กแบบเดิมได้หรือไม่ เราสามารถเลือกชิ้นส่วนที่เราต้องการใช้และหลีกเลี่ยงชิ้นส่วนอื่นได้หรือไม่?

เราสามารถมีสิ่งที่ดีที่สุดของทั้งสองโลกได้หรือไม่? ฉันตอบว่าใช่ โดยเฉพาะอย่างยิ่งเมื่อพูดถึงการจัดการเซสชัน

(ไม่ต้องพูดถึง มีหลายโครงการสำหรับ Django ฟรีแลนซ์)

ตอนนี้การสอน Python: การแชร์ Django Sessions

เป้าหมายของโพสต์นี้คือการมอบหมายงานการตรวจสอบสิทธิ์ผู้ใช้และการลงทะเบียนให้กับ Django แต่ใช้ Redis เพื่อแชร์เซสชันผู้ใช้กับเฟรมเวิร์กอื่นๆ ฉันสามารถนึกถึงบางสถานการณ์ที่อาจมีประโยชน์ดังนี้:

  • คุณต้องพัฒนา REST API แยกต่างหากจากแอป Django ของคุณ แต่ต้องการแชร์ข้อมูลเซสชัน
  • คุณมีส่วนประกอบเฉพาะที่อาจจำเป็นต้องเปลี่ยนในภายหลังหรือขยายขนาดด้วยเหตุผลบางประการและยังต้องการข้อมูลเซสชัน

สำหรับบทช่วยสอนนี้ ฉันจะใช้ Redis เพื่อแชร์เซสชันระหว่างสองเฟรมเวิร์ก (ในกรณีนี้คือ Django และ Flask) ในการตั้งค่าปัจจุบัน ฉันจะใช้ SQLite เพื่อจัดเก็บข้อมูลผู้ใช้ แต่คุณสามารถเชื่อมโยงส่วนหลังของคุณกับฐานข้อมูล NoSQL (หรือทางเลือกอื่นที่ใช้ SQL) หากจำเป็น

ทำความเข้าใจเซสชั่น

ในการแบ่งปันเซสชันระหว่าง Django และ Flask เราจำเป็นต้องรู้เล็กน้อยว่า Django เก็บข้อมูลเซสชันอย่างไร เอกสาร Django ค่อนข้างดี แต่ฉันจะให้พื้นหลังเพื่อความสมบูรณ์

การจัดการเซสชันต่างๆ

โดยทั่วไป คุณสามารถเลือกจัดการข้อมูลเซสชันของแอป Python ได้ด้วยวิธีใดวิธีหนึ่งจากสองวิธี:

  • เซสชันที่ใช้คุกกี้ : ในสถานการณ์นี้ ข้อมูลเซสชันจะไม่ถูกจัดเก็บในที่เก็บข้อมูลที่ส่วนหลัง แต่จะถูกทำให้เป็นอนุกรม ลงนาม (ด้วย SECRET_KEY) และส่งไปยังไคลเอนต์ เมื่อไคลเอนต์ส่งข้อมูลนั้นกลับมา ความสมบูรณ์ของข้อมูลจะถูกตรวจสอบการปลอมแปลงและจะถูกดีซีเรียลไลซ์บนเซิร์ฟเวอร์อีกครั้ง

  • เซสชันตามการจัดเก็บ : ในสถานการณ์สมมตินี้ ข้อมูลเซสชันเองจะ ไม่ ถูกส่งไปยังไคลเอนต์ แต่จะส่งเพียงส่วนเล็กๆ (คีย์) เพื่อระบุตัวตนของผู้ใช้ปัจจุบัน ซึ่งจัดเก็บไว้ในที่เก็บเซสชัน

ในตัวอย่างของเรา เราสนใจในสถานการณ์หลังมากกว่า: เราต้องการให้ข้อมูลเซสชันของเราถูกจัดเก็บไว้ที่ส่วนหลังแล้วตรวจสอบใน Flask สิ่งเดียวกันสามารถทำได้ในอดีต แต่ตามที่เอกสาร Django กล่าวถึง มีความกังวลเกี่ยวกับความปลอดภัยของวิธีแรก

เวิร์กโฟลว์ทั่วไป

เวิร์กโฟลว์ทั่วไปของการจัดการเซสชันและการจัดการจะคล้ายกับไดอะแกรมนี้:

ไดอะแกรมแสดงการจัดการเซสชันผู้ใช้ระหว่าง Flask และ Django โดยใช้ Redis

มาดูการแชร์เซสชันในรายละเอียดเพิ่มเติมกัน:

  1. เมื่อมีคำขอใหม่เข้ามา ขั้นตอนแรกคือส่งผ่านมิดเดิลแวร์ที่ลงทะเบียนในกอง Django เราสนใจคลาส SessionMiddleware ซึ่งเกี่ยวข้องกับการจัดการเซสชันและการจัดการตามที่คุณคาดหวัง:

     class SessionMiddleware(object): def process_request(self, request): engine = import_module(settings.SESSION_ENGINE) session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None) request.session = engine.SessionStore(session_key)

    ในตัวอย่างนี้ Django คว้า SessionEngine ที่ลงทะเบียนแล้ว (เราจะไปที่นั้นในไม่ช้า) แยก SESSION_COOKIE_NAME จาก request ( sessionid โดยค่าเริ่มต้น) และสร้างอินสแตนซ์ใหม่ของ SessionEngine ที่เลือกเพื่อจัดการที่เก็บข้อมูลเซสชัน

  • ภายหลัง (หลังจากที่ประมวลผลการดูของผู้ใช้แล้ว แต่ยังอยู่ในสแต็กมิดเดิลแวร์) เอ็นจินเซสชันจะเรียกวิธีการบันทึกเพื่อบันทึกการเปลี่ยนแปลงใดๆ ลงในที่เก็บข้อมูล (ระหว่างการจัดการมุมมอง ผู้ใช้อาจเปลี่ยนแปลงบางสิ่งภายในเซสชัน เช่น โดยการเพิ่มค่าใหม่ให้กับวัตถุเซสชันด้วย request.session ) จากนั้น SESSION_COOKIE_NAME จะถูกส่งไปยังไคลเอนต์ นี่คือเวอร์ชันที่เรียบง่าย:

     def process_response(self, request, response): .... if response.status_code != 500: request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response

เรามีความสนใจเป็นพิเศษในคลาส SessionEngine ซึ่งเราจะแทนที่ด้วยบางสิ่งเพื่อจัดเก็บและโหลดข้อมูลเข้าและออกจากแบ็กเอนด์ Redis

โชคดีที่มีบางโครงการที่จัดการเรื่องนี้ให้เราอยู่แล้ว นี่คือตัวอย่างจาก rediss_sessions_fork ให้ความสนใจเป็นพิเศษกับวิธีการ save และ load ซึ่งเขียนขึ้นเพื่อ (ตามลำดับ) จัดเก็บและโหลดเซสชันเข้าและออกจาก Redis:

 class SessionStore(SessionBase): """ Redis session back-end for Django """ def __init__(self, session_key=None): super(SessionStore, self).__init__(session_key) def _get_or_create_session_key(self): if self._session_key is None: self._session_key = self._get_new_session_key() return self._session_key def load(self): session_data = backend.get(self.session_key) if not session_data is None: return self.decode(session_data) else: self.create() return {} def exists(self, session_key): return backend.exists(session_key) def create(self): while True: self._session_key = self._get_new_session_key() try: self.save(must_create=True) except CreateError: continue self.modified = True self._session_cache = {} return def save(self, must_create=False): session_key = self._get_or_create_session_key() expire_in = self.get_expiry_age() session_data = self.encode(self._get_session(no_load=must_create)) backend.save(session_key, expire_in, session_data, must_create) def delete(self, session_key=None): if session_key is None: if self.session_key is None: return session_key = self.session_key backend.delete(session_key)

สิ่งสำคัญคือต้องเข้าใจว่าคลาสนี้ทำงานอย่างไร เนื่องจากเราจำเป็นต้องใช้บางสิ่งที่คล้ายกันใน Flask เพื่อโหลดข้อมูลเซสชัน มาดูรายละเอียดกันด้วยตัวอย่าง REPL:

 >>> from django.conf import settings >>> from django.utils.importlib import import_module >>> engine = import_module(settings.SESSION_ENGINE) >>> engine.SessionStore() <redis_sessions_fork.session.SessionStore object at 0x3761cd0> >>> store["count"] = 1 >>> store.save() >>> store.load() {u'count': 1}

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

หมายเหตุ: คุณอาจถามว่า “ทำไมไม่คัดลอก SessionEngine ลงใน Flask ล่ะ” พูดง่ายกว่าทำ. ดังที่เราได้พูดคุยกันในตอนต้นว่า Django นั้นมีความเชื่อมโยงอย่างแน่นแฟ้นกับวัตถุการตั้งค่า ดังนั้นคุณจึงไม่สามารถนำเข้าโมดูล Django บางตัวและใช้งานได้โดยไม่ต้องดำเนินการใดๆ เพิ่มเติม

Django เซสชัน (De-) การทำให้เป็นอนุกรม

อย่างที่ฉันพูดไป Django ทำงานมากมายเพื่อปกปิดความซับซ้อนของการจัดเก็บเซสชัน มาตรวจสอบคีย์ Redis ที่จัดเก็บไว้ในตัวอย่างด้านบนกัน:

 >>> store.session_key u"ery3j462ezmmgebbpwjajlxjxmvt5adu"

ตอนนี้ให้ค้นหาคีย์นั้นบน redis-cli:

 redis 127.0.0.1:6379> get "django_sessions:ery3j462ezmmgebbpwjajlxjxmvt5adu" "ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ=="

สิ่งที่เราเห็นในที่นี้คือสตริงที่เข้ารหัส Base64 ที่ยาวมาก เพื่อให้เข้าใจถึงจุดประสงค์ เราต้องดูที่คลาส SessionBase ของ Django เพื่อดูว่ามีการจัดการอย่างไร:

 class SessionBase(object): """ Base class for all Session classes. """ def encode(self, session_dict): "Returns the given session dictionary serialized and encoded as a string." serialized = self.serializer().dumps(session_dict) hash = self._hash(serialized) return base64.b64encode(hash.encode() + b":" + serialized).decode('ascii') def decode(self, session_data): encoded_data = base64.b64decode(force_bytes(session_data)) try: hash, serialized = encoded_data.split(b':', 1) expected_hash = self._hash(serialized) if not constant_time_compare(hash.decode(), expected_hash): raise SuspiciousSession("Session data corrupted") else: return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions if isinstance(e, SuspiciousOperation): logger = logging.getLogger('django.security.%s' % e.__class__.__name__) logger.warning(force_text(e)) return {}

วิธีการเข้ารหัสจะทำให้ข้อมูลเป็นอนุกรมในลำดับแรกด้วยเครื่องซีเรียลไลเซอร์ที่ลงทะเบียนไว้ในปัจจุบัน กล่าวคือ จะแปลงเซสชันเป็นสตริง ซึ่งสามารถแปลงกลับเป็นเซสชันได้ในภายหลัง (ดูเอกสารประกอบของ SESSION_SERIALIZER สำหรับข้อมูลเพิ่มเติม) จากนั้นแฮชข้อมูลที่ต่อเนื่องกันและใช้แฮชนี้ในภายหลังเป็นลายเซ็นเพื่อตรวจสอบความสมบูรณ์ของข้อมูลเซสชัน สุดท้าย จะส่งคืนคู่ข้อมูลนั้นให้กับผู้ใช้เป็นสตริงที่เข้ารหัส Base64

โดยวิธีการ: ก่อนเวอร์ชัน 1.6 Django ตั้งค่าเริ่มต้นให้ใช้ pickle สำหรับการทำให้เป็นอนุกรมของข้อมูลเซสชัน เนื่องจากข้อกังวลด้านความปลอดภัย วิธีการทำให้เป็นอันดับเริ่มต้นในขณะนี้คือ django.contrib.sessions.serializers.JSONSerializer

การเข้ารหัสเซสชันตัวอย่าง

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

 >>> store.encode({'count': 1}) u'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ==' >>> base64.b64decode(encoded) 'fe1964e1d2cf8069d9f1823afd143400b6d3736f:{"count":1}'

ผลลัพธ์ของวิธีการจัดเก็บ (u'ZmUxOTY…==') คือสตริงที่เข้ารหัสซึ่งมีเซสชันผู้ใช้ที่จัดลำดับ และ แฮช เมื่อเราถอดรหัส เราจะได้ทั้งแฮช ('fe1964e…') และเซสชัน ( {"count":1} ) กลับมา

โปรดทราบว่าวิธีการถอดรหัสจะตรวจสอบเพื่อให้แน่ใจว่าแฮชนั้นถูกต้องสำหรับเซสชันนั้น ซึ่งรับประกันความสมบูรณ์ของข้อมูลเมื่อเรานำไปใช้ใน Flask ในกรณีของเรา เราไม่ได้วิตกกังวลเกินไปเกี่ยวกับเซสชันของเราที่จะถูกแก้ไขในฝั่งไคลเอ็นต์ เนื่องจาก:

  • เราไม่ได้ใช้เซสชันที่ใช้คุกกี้ กล่าวคือ เราไม่ได้ส่งข้อมูลผู้ใช้ ทั้งหมด ไปยังไคลเอนต์

  • ใน Flask เราจำเป็นต้องมี SessionStore แบบอ่านอย่างเดียวซึ่งจะบอกเราว่าคีย์ที่ระบุมีอยู่หรือไม่ และส่งคืนข้อมูลที่เก็บไว้

ขยายไปยัง Flask

ต่อไป มาสร้างเวอร์ชันที่เรียบง่ายของกลไกเซสชัน Redis (ฐานข้อมูล) เพื่อทำงานกับ Flask เราจะใช้ SessionStore เดียวกัน (ที่กำหนดไว้ด้านบน) เป็นคลาสพื้นฐาน แต่เราจำเป็นต้องลบฟังก์ชันการทำงานบางอย่างออก เช่น ตรวจหาลายเซ็นที่ไม่ถูกต้องหรือแก้ไขเซสชัน เราสนใจ SessionStore แบบอ่านอย่างเดียวที่จะโหลดข้อมูลเซสชันที่บันทึกจาก Django มาดูกันว่ามันมารวมกันได้อย่างไร:

 class SessionStore(object): # The default serializer, for now def __init__(self, conn, session_key, secret, serializer=None): self._conn = conn self.session_key = session_key self._secret = secret self.serializer = serializer or JSONSerializer def load(self): session_data = self._conn.get(self.session_key) if not session_data is None: return self._decode(session_data) else: return {} def exists(self, session_key): return self._conn.exists(session_key) def _decode(self, session_data): """ Decodes the Django session :param session_data: :return: decoded data """ encoded_data = base64.b64decode(force_bytes(session_data)) try: # Could produce ValueError if there is no ':' hash, serialized = encoded_data.split(b':', 1) # In the Django version of that they check for corrupted data # I don't find it useful, so I'm removing it return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions. If any of # these happen, return an empty dictionary (ie, empty session). return {}

เราต้องการวิธีการ load เท่านั้นเพราะเป็นการใช้งานที่เก็บข้อมูลแบบอ่านอย่างเดียว นั่นหมายความว่าคุณไม่สามารถออกจากระบบได้โดยตรงจาก Flask; คุณอาจต้องการเปลี่ยนเส้นทางงานนี้ไปที่ Django แทน โปรดจำไว้ว่า เป้าหมายที่นี่คือการจัดการเซสชันระหว่างเฟรมเวิร์ก Python ทั้งสองนี้ เพื่อให้คุณมีความยืดหยุ่นมากขึ้น

เซสชั่นขวด

ไมโครเฟรมเวิร์กของ Flask รองรับเซสชันที่ใช้คุกกี้ ซึ่งหมายความว่าข้อมูลเซสชันทั้งหมดจะถูกส่งไปยังไคลเอนต์ เข้ารหัส Base64 และลงนามด้วยการเข้ารหัส แต่ที่จริงแล้ว เราไม่ค่อยสนใจการสนับสนุนเซสชันของ Flask มากนัก

สิ่งที่เราต้องการคือการรับ ID เซสชันที่สร้างโดย Django และตรวจสอบกับ Redis back-end เพื่อให้เรามั่นใจได้ว่าคำขอนั้นเป็นของผู้ใช้ที่ลงชื่อล่วงหน้า โดยสรุป กระบวนการในอุดมคติคือ (ซึ่งซิงค์กับไดอะแกรมด้านบน):

  • เราคว้ารหัสเซสชัน Django จากคุกกี้ของผู้ใช้
  • หากพบ ID เซสชันใน Redis เราจะส่งคืนเซสชันที่ตรงกับ ID นั้น
  • หากไม่เป็นเช่นนั้น เราจะเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ

มันจะสะดวกถ้ามีมัณฑนากรเพื่อตรวจสอบข้อมูลนั้นและตั้งค่า user_id ปัจจุบันเป็นตัวแปร g ใน Flask:

 from functools import wraps from flask import g, request, redirect, url_for def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): djsession_id = request.cookies.get("sessionid") if djsession_id is None: return redirect("/") key = get_session_prefixed(djsession_id) session_store = SessionStore(redis_conn, key) auth = session_store.load() if not auth: return redirect("/") g.user_id = str(auth.get("_auth_user_id")) return f(*args, **kwargs) return decorated_function

ในตัวอย่างข้างต้น เรายังคงใช้ SessionStore ที่เรากำหนดไว้ก่อนหน้านี้เพื่อดึงข้อมูล Django จาก Redis หากเซสชันมี _auth_user_id เราจะส่งคืนเนื้อหาจากฟังก์ชันมุมมอง มิฉะนั้น ผู้ใช้จะถูกเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ เหมือนกับที่เราต้องการ

ติดกาวสิ่งต่าง ๆ เข้าด้วยกัน

ในการแบ่งปันคุกกี้ ฉันพบว่าสะดวกที่จะเริ่มต้น Django และ Flask ผ่านเซิร์ฟเวอร์ WSGI และติดกาวเข้าด้วยกัน ในตัวอย่างนี้ ฉันเคยใช้ CherryPy:

 from app import app from django.core.wsgi import get_wsgi_application application = get_wsgi_application() d = wsgiserver.WSGIPathInfoDispatcher({ "/":application, "/backend":app }) server = wsgiserver.CherryPyWSGIServer(("127.0.0.1", 8080), d)

ด้วยเหตุนี้ Django จะให้บริการบน “/” และ Flask จะให้บริการบนจุดปลาย “/backend”

สรุปแล้ว

แทนที่จะตรวจสอบ Django กับ Flask หรือสนับสนุนให้คุณเรียนรู้แค่ไมโครเฟรมเวิร์กของ Flask ฉันได้เชื่อม Django และ Flask เข้าด้วยกัน เพื่อให้พวกเขาแชร์ข้อมูลเซสชันเดียวกันสำหรับการตรวจสอบสิทธิ์โดยมอบหมายงานให้กับ Django เนื่องจาก Django มาพร้อมกับโมดูลมากมายเพื่อแก้ปัญหาการลงทะเบียนผู้ใช้ การเข้าสู่ระบบ และออกจากระบบ (เพียงไม่กี่ชื่อ) การรวมเฟรมเวิร์กทั้งสองนี้จะช่วยให้คุณประหยัดเวลาอันมีค่า ในขณะที่ให้โอกาสคุณในการแฮ็กบนไมโครเฟรมเวิร์กที่จัดการได้ เช่น Flask