Pyramid & Mako - Drzewo komentarzy

Przy tworzeniu stron internetowych większość z developerów napotka problem stworzenia systemu komentarzy.

Podstawowymi wymogami są:
  • komentować można nie tylko artykul/wpis na blogu, lecz takze sam komentarz
  • kazdy komentarz mozna skomentować dowolną ilość razy
  • brak ograniczeniu w zaglebieniu komentarzy
  • funkcja zwijania/rozwijania komentarzy
  • podstawowe informacje o komentarzu - wiek, nazwa uzytkownika

Technologie użyte w tym przykladzie to:
  • Python
  • Pyramid framework
  • SQLAlchemy
  • MySQL database
  • Mako templating system

Docelowo powinno ono wygladac jak na Reddit.com:





Najpierw podstawa - baza danych. Zakladamy ze komentowane beda posty na Blogu - taki jak ten, który czytasz.

class Articles(Base):
"""This table represents list of added news"""

__tablename__ = 'articles'

id = Column(Integer, primary_key=True, index=True)
title = Column(String, nullable=False, index=True)
content = Column(UnicodeText, nullable=False)
description = Column(UnicodeText(length=250), nullable=False)
url = Column(UnicodeText, nullable=False)
image = Column(UnicodeText, nullable=True)
author_id = Column(Integer, ForeignKey(Users.id), nullable=False)
added_dt = Column(DateTime, nullable=False, default=datetime.now())
modified_dt = Column(DateTime, nullable=False, default=datetime.now())
visits = Column(Integer, nullable=False, default=0)

comments = relationship('Comments', cascade="delete", single_parent=True, lazy='subquery')
author = relationship(Users, single_parent=True, lazy='subquery')

Do tej pory chyba wszystko jest jasne. Wazna jest relacja comments do tabeli Comments zawierajacej komentarze.

Potrzebna nam również definicja klasy komentarzy:

class Comments(Base):
"""This table represents list of added comments"""

__tablename__ = 'comments'

id = Column(Integer, primary_key=True, index=True)
article_id = Column(Integer, ForeignKey(Articles.id), nullable=False)
author_id = Column(Integer, ForeignKey(Users.id), nullable=False)
content = Column(UnicodeText, nullable=False)
added_dt = Column(DateTime, nullable=False, default=datetime.now())
parent_id = Column(Integer, ForeignKey('comments.id'), nullable=True)

author = relationship(Users, single_parent=True, lazy='subquery')

children = relationship("Comments",
# cascade deletions
cascade="all, delete-orphan",

# many to one + adjacency list - remote_side
# is required to reference the 'remote'
# column in the join condition.
backref=backref("parent", remote_side=id),

# children will be represented as a dictionary
# on the "name" attribute.
collection_class=attribute_mapped_collection('id'),
)

Tutaj najwazniejszymi rzeczami sa:
  • relacja children - jest to relacja komentarzy do komentarzy-dzieci, polaczona poprzez atrybut ID. Dzieki niej komentarze polaczone sa poprzez relacje ID-PARENT_ID.

Kwestia bazy danych jest zalatwiona. Teraz czas na controller - a mianowicie funkcje widoku:

@view_config(route_name='article_view', request_method='GET', renderer='project:templates/articles/view.mako')
def view(self):
try:
#TUTAJ POBIERAMY ARTYKUL Z BAZY DANYCH
article = DBSession.query(Articles).get(self.article_id)

#FUNKCJA POBIERAJACA WSZYSTKIE KOMENTARZE DLA ARTYKULU
comments = blogfacade.get_comments_for_article(self.article_id, self.page, self.comments_page_size)

return dict(status=True, article=article, comments=comments, user=user)

except Exception, ex:
h.exc_wrap(ex)
return dict(status=False, message=ex)

Pora na template Mako. Proponuje stworzyc osobny plik szablonu generujacy drzewo komentarzy:

<%def name="build_comment(c)">    <header class="uk-comment">        <div class="uk-comment-meta">            % if c.children:            <a class="collapse">[ - ]</a>            <a class="unroll uk-hidden">[ + ]</a>            % endif            <span class="uk-text-bold">${c.author.username}</span> |            ${c.added_dt}            <button type="submit" class="comment-reply-button">${_("reply")}</button>        </div>        <div class="uk-comment-body">            ${c.content}        </div>        <form action="${request.route_url('comment_reply', article_id=article.id, parent_id=c.id)}" method="post" class="uk-position-relative comment-reply uk-display-block uk-hidden" accept-charset="utf-8" enctype="multipart/form-data">            <textarea name="content" cols="50" rows="2" placeholder="${_("Reply this comment")}" required></textarea>            <input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}">            <input class="uk-position-top comment-reply-button" type="submit" value="${_("Save")}">        </form>                # TUTAJ GLOWNA CZESC - REKURSYWNE WYWOLANIE FUNKCJI GENERUJACEJ POTOMNE KOMENTARZE (CHILDY)        % if hasattr(c, 'children') and len(c.children):            % for child in c.children:                ${build_comment(child)}            % endfor        % endif    </header></%def>

Mamy już gotowa funkcje tworzaca drzewo komentarzy, wystarczy ja uzyc w jakims szablonie:

<article class="uk-article">   # TUTAJ TRESC ARTYKULU   <span>${c.content}</span>   % if comments:   # IMPORTUJEMY FUNKCJE TWORZACA DRZEWO KOMENTARZY   <%namespace name="comment_tree" file="comment_tree.mako"/>   <ul class="uk-comment-list">      # I DLA KAZDEGO KOMENTARZA JA ODPALAMY (rekursywnie bedzie sie ona wywolywac, az "DO SPODU"      % for c in comments:          ${comment_tree.build_comment(c)}      % endfor   </ul>   % endif</article>

GOTOWE. Mam nadzieje, ze ten artykul pomoze Wam w tworzeniu stron internetowych.