angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Commentaires en fil de discussion utilisant Common Table Expressions (CTE) pour un blog ou un CMS MySQL Flask

8 février 2020
post main image
https://unsplash.com/@di_an_h

Maintenant que j'ai des articles de blog, des pages et un formulaire de contact, j'ai décidé de mettre en œuvre les commentaires pour les articles et les pages de blog. Il ne s'agit pas seulement de commentaires plats, mais aussi de commentaires en fil de fer, également appelés commentaires imbriqués. Il y a quelques mois, j'ai lu à ce sujet et j'ai beaucoup aimé l'article de Miguel Grinberg : Mise en œuvre des commentaires des utilisateurs avec SQLAlchemy.

Comme souvent, Miguel commence par définir le problème et une théorie de base et explique très clairement les approches de la liste d'adjacence et de la liste imbriquée. Puis il a proposé sa propre solution et a montré comment il l'a mise en œuvre. Je l'ai essayé et ça a parfaitement fonctionné. Un des commentaires ci-dessous son article suggérait d'utiliser les extensions de table commune ou CTE. Au moment de l'article, MySQL a introduit CTE. Il était déjà disponible dans PostgreSQL.

Comme mon site tourne sur un serveur géré par ISPConfig , je dois utiliser MySQL. Je sais que je peux installer PostgreSQL mais l'avantage de ISPConfig est que je peux gérer MySQL en utilisant l'administrateur. En outre, j'utilise MySQL depuis très longtemps et cela ne m'a jamais déçu. Je fais principalement du développement en amont et cela nécessite des requêtes simples et rapides.

Commentaires avec Common Table Expressions (CTEs)

J'ai examiné une solution de commentaires en fil de fer avec CTE, 'WITH RECURSIVE', et cela devient effectivement un peu plus facile. Lors de l'utilisation d'une requête récursive, nous laissons MySQL itérer sur les commentaires à l'aide de comment.id et comment.parent_id.

Le chemin est construit en concaténant le commentaire.id. Ensuite, le résultat est trié par le chemin. MySQL n'a pas de type de données de tableau comme PostgreSQL, ce qui signifie que nous ne pouvons pas créer un chemin en ajoutant des ID de commentaires à un tableau mais que nous devons concaténer des chaînes de caractères, représentant les ID de commentaires. J'ai essayé de convertir le comment.id en une chaîne de caractères, puis de l'ajouter au clavier en utilisant CONVERT() et LPAD() , mais cela n'a pas semblé fonctionner.

Je n'ai pas vu d'autre option que de stocker le comment.id également sous forme de chaîne à zéro dans un autre champ zpstring_id dans le même enregistrement. Le nombre de caractères de cette chaîne doit être suffisant pour couvrir le nombre maximum de commentaires que vous attendez de votre système de commentaires au cours de sa durée de vie. J'ai choisi un nombre de 8, ce qui signifie que je peux gérer cent millions (99.999.999) de commentaires.

Un (mauvais) effet secondaire de l'utilisation d'une colonne de tableau est que la largeur de la colonne zpstring_id doit être aussi grande que le plus grand nombre de valeurs zpstring_id concaténées. Si nous autorisons un niveau, ou une profondeur, maximum de 10, alors la taille de la colonne zpstring_id doit être au moins de 8 * 10 = 80 caractères. Nous en ajoutons d'autres pour permettre un caractère de séparation qui facilite la lecture.

Le modèle de commentaire et la requête récursive

La classe de commentaires :

class Comment(Base):
    comment_path_level_width = 6

    __tablename__ = 'comment'

    id = Column(Integer, primary_key=True)
    created_on = Column(DateTime, server_default=func.now(), index=True)

    parent_id = Column(Integer, ForeignKey('comment.id'))

    author = Column(String(64))
    text = Column(Text())

    zpstring_id = Column(String(200), server_default='', index=True)
    thread_created_on = Column(DateTime, index=True)

    content_item_id = Column(Integer, ForeignKey('content_item.id'))

    replies = relationship(
        'Comment', 
        backref=backref('parent', 
        remote_side=[id]),
        lazy='dynamic')

La colonne thread_created_on est l'horodatage de tous les commentaires d'un thread. Nous l'utilisons lorsque nous voulons trier par ordre de nouveauté, voir aussi l'article de Miguel. La colonne content_item_id est l'identifiant d'un billet de blog ou l'identifiant d'une page. La requête MySQL pour sélectionner les commentaires :

WITH RECURSIVE  tree_path (id, thread_created_on, parent_id, text, level, path) AS
(
   SELECT  id, thread_created_on, parent_id, text as text, 0 as level, zpstring_id as path
    FROM comment
     WHERE
          content_item_id = :content_item_id
      AND parent_id IS  NULL
   UNION  ALL
   SELECT  t.id, t.thread_created_on, t.parent_id, t.text, tp.level  +  1 AS level, CONCAT(tp.path, '/', t.zpstring_id)
    FROM tree_path AS tp JOIN comment AS t
      ON tp.id = t.parent_id
)
SELECT  * FROM tree_path
ORDER BY  path; 

Insertion de commentaires et de réponses

Pour l'insertion d'un commentaire, nous utilisons deux commits. Dans le premier commit, nous enregistrons le commentaire, puis nous utilisons cet identifiant, le convertissons en une chaîne de caractères, le mettons à zéro et le stockons dans zpstring_id. Enfin, nous nous engageons à nouveau. Le parent_id est NULL dans ce cas.

    comment = Comment(
        text = text,
        author = author, 
        content_item_id = content_item.id,
    )
    db.session.add(comment)
    db.session.commit()
    # we got the id, now set zpstring_id
    comment.zpstring_id = str(comment.id).zfill(8)
    # set thread timestamp
    comment.thread_created_on = comment.created_on
    db.session.commit()

L'insertion des réponses est légèrement différente car nous devons ajouter le parent_id. De plus, nous obtenons la valeur thread_created_on du parent ! Ce que je fais avant d'insérer une réponse, c'est obtenir la fiche parentale. C'est une bonne idée de toute façon et une vérification supplémentaire pour savoir si le parent_id soumis est valide.

    comment = Comment(
        parent = parent,
        text = text, 
        author = author, 
        content_item_id = content_item.id,
        # add thread timestamp
        thread_created_on = parent.thread_created_on
    )
    db.session.add(comment)
    db.session.commit()
    # we got the id, now set zpstring_id
    comment.zpstring_id = str(comment.id).zfill(comment_path_width)
    db.session.commit()

Bien sûr, nous pouvons combiner ces deux fonctions en une seule, mais par souci de clarté, je les montre toutes les deux.

Il est temps d'agir

Insérons quelques commentaires. Vous devriez pouvoir copier et coller les déclarations lorsque vous utilisez la ligne de commande MySQL :

# clear comments
SET FOREIGN_KEY_CHECKS=0;
delete from comment;
SET FOREIGN_KEY_CHECKS=1;

# level 0 comment
INSERT  INTO comment (text, content_item_id) VALUES ('first level 0 text', 34);
SET @level_0_comment_id = (SELECT  LAST_INSERT_ID());
SET @thread_timestamp = (SELECT  created_on FROM comment  WHERE  id = @level_0_comment_id);
UPDATE comment SET zpstring_id = LPAD(@level_0_comment_id, 8, '0'), thread_created_on = @thread_timestamp  WHERE  id = @level_0_comment_id;

# reply: parent = first level 0 comment
INSERT  INTO comment (parent_id, thread_created_on, text, content_item_id) VALUES (@level_0_comment_id, @thread_timestamp, 'reply to: first level 0 text', 34);
SET @level_1_comment_id = (SELECT   LAST_INSERT_ID());
UPDATE comment SET zpstring_id = LPAD(@level_1_comment_id, 8, '0')  WHERE  id = @level_1_comment_id;

# reply: parent = first level 1 comment
INSERT  INTO comment (parent_id, thread_created_on, text, content_item_id) VALUES (@level_1_comment_id, @thread_timestamp, 'reply to: reply to: first level 0 text', 34);
SET @level_2_comment_id = (SELECT   LAST_INSERT_ID());
UPDATE comment SET zpstring_id = LPAD(@level_2_comment_id, 8, '0')  WHERE  id = @level_2_comment_id;

# reply: parent = first level 1 comment
INSERT  INTO comment (parent_id, thread_created_on, text, content_item_id) VALUES (@level_1_comment_id, @thread_timestamp, '2e reply to: reply to: first level 0 text', 34);
SET @level_2_comment_id = (SELECT   LAST_INSERT_ID());
UPDATE comment SET zpstring_id = LPAD(@level_2_comment_id, 8, '0')  WHERE  id = @level_2_comment_id;

Attendez un instant. Pourquoi attendre ? Parce que je veux une séparation d'au moins une seconde entre les deux commentaires de niveau 0. Nous pouvons également ajouter un horodatage MySQL avec le Fractional Seconds mais cela n'entre pas dans le cadre de ce poste. Pour ajouter un deuxième fil de discussion, copiez-collez ce qui suit :

# a second level 0 comment
INSERT  INTO comment (text, content_item_id) VALUES ('second level 0 text', 34);
SET @level_0_comment_id = (SELECT  LAST_INSERT_ID());
SET @thread_timestamp = (SELECT  created_on FROM comment  WHERE  id = @level_0_comment_id);
UPDATE comment SET zpstring_id = LPAD(@level_0_comment_id, 8, '0'), thread_created_on = @thread_timestamp  WHERE  id = @level_0_comment_id;

# reply: parent second level 0 comment
INSERT  INTO comment (parent_id, thread_created_on, text, content_item_id) VALUES (@level_0_comment_id, @thread_timestamp, 'reply to: second level 0 text', 34);
SET @level_1_comment_id = (SELECT   LAST_INSERT_ID());
UPDATE comment SET zpstring_id = LPAD(@level_1_comment_id, 8, '0')  WHERE  id = @level_1_comment_id;

Lançons maintenant la requête récursive :

WITH RECURSIVE  tree_path (id, thread_created_on, parent_id, text, level, path) AS
(
   SELECT  id, thread_created_on, parent_id, text as text, 0 as level, zpstring_id as path
    FROM comment
     WHERE
          content_item_id = 34
      AND parent_id IS  NULL
   UNION  ALL
   SELECT  t.id, t.thread_created_on, t.parent_id, t.text, tp.level  +  1 AS level, CONCAT(tp.path, '/', t.zpstring_id)
    FROM tree_path AS tp JOIN comment AS t
      ON tp.id = t.parent_id
)
SELECT  * FROM tree_path
ORDER BY  path; 

Cela devrait vous donner le résultat suivant :

+------+---------------------+-----------+-------------------------------------------+-------+----------------------------+
| id   | thread_created_on   | parent_id | text                                      | level | path                       |
+------+---------------------+-----------+-------------------------------------------+-------+----------------------------+
|  110 | 2020-02-08 20:49:19 |       NULL  | first level 0 text                        |     0 | 00000110                   |
|  111 | 2020-02-08 20:49:19 |       110 | reply to: first level 0 text              |     1 | 00000110/00000111          |
|  112 | 2020-02-08 20:49:19 |       111 | reply to: reply to: first level 0 text    |     2 | 00000110/00000111/00000112 |
|  113 | 2020-02-08 20:49:19 |       111 | 2e reply to: reply to: first level 0 text |     2 | 00000110/00000111/00000113 |
|  114 | 2020-02-08 20:49:38 |       NULL  | second level 0 text                       |     0 | 00000114                   |
|  115 | 2020-02-08 20:49:38 |       114 | reply to: second level 0 text             |     1 | 00000114/00000115          |
+------+---------------------+-----------+-------------------------------------------+-------+----------------------------+

L'ordre est "le plus ancien d'abord". Si nous voulons trier par "le plus récent d'abord", nous modifions la clause ORDER BY :

WITH RECURSIVE  tree_path (id, thread_created_on, parent_id, text, level, path) AS
(
   SELECT  id, thread_created_on, parent_id, text as text, 0 as level, zpstring_id as path
    FROM comment
     WHERE
          content_item_id = 34
      AND parent_id IS  NULL
   UNION  ALL
   SELECT  t.id, t.thread_created_on, t.parent_id, t.text, tp.level  +  1 AS level, CONCAT(tp.path, '/', t.zpstring_id)
    FROM tree_path AS tp JOIN comment AS t
      ON tp.id = t.parent_id
)
SELECT  * FROM tree_path
ORDER BY  thread_created_on DESC, path; 

Comparaison des deux solutions

La solution de Miguel n'est pas vraiment très différente de celle de CTE . Il met en œuvre le chemin d'une autre manière et le niveau aussi. Notez que vous pouvez implémenter le niveau très facilement en ajoutant à un commentaire de réponse : level = parent.level + 1. Les deux solutions nécessitent une double soumission car il n'y a pas de type de champ de type tableau dans MySQL ( ?).

Qu'en est-il des questions Flask, SQLALchemy et Bootstrap 4 ? Vous vous demandez peut-être quel est le rapport entre ce qui précède et la question Flask ? Enfin, pas tant que ça. Ce site web est construit avec Flask et SQLAlchemy, sans l'extension Flask-SQLAlchemy , voir la classe Commentaire.

Qu'en est-il de SQLAlchemy ? Je ne suis pas sûr que la requête CTE puisse être transformée en une pure requête SQLAlchemy . Les développeurs de MySQL déclarent qu'ils ne veulent pas implémenter les requêtes non conformes à SQL , je dois donc me pencher sur la question. Les requêtes ci-dessus peuvent être exécutées dans SQLAlchemy en tant que requêtes "brutes" de la même manière :

    db.session.execute(text(sql), {
        'content_item_id': self.content_item_id, 
    })

Et que dire de Bootstrap 4 ? Nous pouvons utiliser le système de grille pour mettre en retrait le niveau des commentaires :

    {% if comment_level == 0 %}
        <div class="col-12 mb-1">
    {% elif comment_level == 1 %}
        <div class="col-11 offset-1 mb-1">
    {% elif comment_level == 2 %}
        <div class="col-10 offset-2 mb-1">
    {% elif comment_level == 3 %}
        <div class="col-9 offset-3 mb-1">
    {% elif comment_level == 4 %}
        <div class="col-8 offset-4 mb-1">
    {% else %}
        <div class="col-7 offset-5 mb-1">
    {% endif %}

Résumé

Ce qui précède est une première implémentation des commentaires en fil de discussion utilisant CTE pour ce site web. MySQL n'est peut-être pas la base de données parfaite pour mettre en œuvre la requête CTE "WITH RECURSIVE", mais elle est très utilisée par de nombreux sites web et nous devons donc vivre avec ses limites.

Obtenir les commentaires n'est qu'une petite partie de la mise en œuvre des commentaires pour un site web. Il y a tellement plus de points à traiter comme le statut d'un commentaire, la suppression, la dissimulation, la (dé)modération, le vote. Et nous pouvons autoriser les commentaires, à condition d'être connectés. Il y a aussi le courrier électronique, l'envoi d'un courrier lorsque quelqu'un répond, l'envoi de courriers électroniques pour modération. Peut-être qu'un jour, j'écrirai la deuxième partie de ce billet.

Liens / crédits

Adjacency List Model vs Nested Set Model for MySQL hierarchical data?
https://stackoverflow.com/questions/31641504/adjacency-list-model-vs-nested-set-model-for-mysql-hierarchical-data

Adjacency list vs. nested sets: PostgreSQL
https://explainextended.com/2009/09/24/adjacency-list-vs-nested-sets-postgresql/

Cannot use ROW_NUMBER() in recursive block of CTE
https://bugs.mysql.com/bug.php?id=96538

Creating Threaded Comments With PHP And Postgresql Recursive Query
https://phpro.org/tutorials/Creating-Threaded-Comments-With-PHP-And-Postgresql-Recursive-Query.html

How do I create nested categories in a Database?
https://stackoverflow.com/questions/926175/how-do-i-create-nested-categories-in-a-database

Implementing User Comments with SQLAlchemy
https://blog.miguelgrinberg.com/post/implementing-user-comments-with-sqlalchemy

Is there any array data type in MySQL like in PostgreSQL?
https://stackoverflow.com/questions/5541175/is-there-any-array-data-type-in-mysql-like-in-postgresql

Managing Hierarchical Data in MySQL
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

Storing and retrieving tree structures in relational databases using Python
https://medium.com/@spybugg/storing-and-retrieving-tree-structures-in-relational-databases-using-python-django-7480f40c24b

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires (73)

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.

avatar

fgfdghfsdagfdaghag

avatar
fre 2 ans il y a Visiteur anonyme (non connecté)

bfsdhb

avatar

reply to bfsdhb

avatar

first comment

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

reply to first comment

avatar
user39164362 2 ans il y a Visiteur anonyme (non connecté)

asaqwer

avatar

xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxx xdxxxxxxxxxxxxxxxxxxxxxxxxxxxx xdxxxxxxxxxxxxxxxxxxxxxxx

avatar

yyyyyyyyyy yyy yy

avatar

reply to asaqwer

avatar

my reply to asaqwer: try again

avatar
user39164362 2 ans il y a Visiteur anonyme (non connecté)

vdcdsa

avatar

sacacdsa

avatar

reply to vdcdsa

avatar
user39164362 2 ans il y a Visiteur anonyme (non connecté)

IIIIIIIIIIIIIIIIIIIIIIIIIII

avatar
fre 2 ans il y a Visiteur anonyme (non connecté)

reply to first comment reply to first comment

avatar
Visiteur anonyme (non connecté) 1 an il y a Visiteur anonyme (non connecté)

test reply to check indentation

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

Just my reply

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

and just replying to Just my reply

avatar
user39164362 2 ans il y a Visiteur anonyme (non connecté)

isit the same

avatar

not same remember

avatar

now wazxza mi ta meskita

avatar
user39164362 2 ans il y a Visiteur anonyme (non connecté)

asadfsagafsd

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

ssdagasdsg

avatar
user39164362 2 ans il y a

aszadcdsda

avatar
user39164362 2 ans il y a

Erste Nachricht

avatar
user39164362 2 ans il y a

asxxxxx

avatar
user39164362 2 ans il y a

leveltje NUL

avatar

reply to levetje NULLLL

avatar
user39164362 2 ans il y a

Gimme LOLOL

avatar

LOL reply

avatar
user39164362 2 ans il y a

bla die bla

avatar
user39164362 2 ans il y a

blibla

avatar

aaaaaaaa

avatar

leffeltjuhnul

avatar

Is this working?

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

Lets reply

avatar

? ?? ????? ?????, ??? ??? ?? ???????.

------
<a href=https://tel-number.ru/our-services/1823-korea-direct-number>????? ????? ????? ????????</a> | https://tel-number.ru/

avatar

I apologise, but, in my opinion, you commit an error.


-----
<a href=https://www.anal4us.com/latest-updates>https://www.anal4us.com/latest-updates</a> | https://www.anal4us.com

avatar

Excuse, that I can not participate now in discussion - it is very occupied. I will return - I will necessarily express the opinion on this question.


-----
<a href=https://www.analibiza.com/videos>https://www.analibiza.com/videos</a> | https://www.analibiza.com

avatar

http://mewkid.net/when-is-xaxlop/ - Amoxicillin 500 Mg Dosage <a href="http://mewkid.net/when-is-xaxlop/">Amoxicillin Online</a> scj.frdm.peterspython.com.sjg.sx http://mewkid.net/when-is-xaxlop/

avatar

http://mewkid.net/when-is-xaxlop/ - Amoxicillin On Line <a href="http://mewkid.net/when-is-xaxlop/">Amoxicillin Online</a> iqf.ejgj.peterspython.com.vus.sy http://mewkid.net/when-is-xaxlop/

avatar

http://mewkid.net/when-is-xaxlop/ - Dosage For Amoxicillin 500mg <a href="http://mewkid.net/when-is-xaxlop/">Amoxicillin</a> mep.gznv.peterspython.com.yaj.fo http://mewkid.net/when-is-xaxlop/

avatar

http://mewkid.net/when-is-xaxlop/ - Dosage For Amoxicillin 500mg <a href="http://mewkid.net/when-is-xaxlop/">Amoxicillin</a> smu.ulag.peterspython.com.ryc.ho http://mewkid.net/when-is-xaxlop/

avatar

http://mewkid.net/when-is-xaxlop/ - Amoxicillin <a href="http://mewkid.net/when-is-xaxlop/">Amoxicillin 500 Mg</a> hcd.cumj.peterspython.com.epd.sm http://mewkid.net/when-is-xaxlop/

avatar

http://mewkid.net/when-is-xaxlop/ - Amoxicillin 500mg Capsules <a href="http://mewkid.net/when-is-xaxlop/">Amoxicillin Without Prescription</a> llb.egyu.peterspython.com.uso.lt http://mewkid.net/when-is-xaxlop/

avatar

Потоковые комментарии с использованием блога Common Table Expressions (CTE) для блога MySQL Flask или CMS

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

Большинство просмотренных:

avatar

and another message on tuesday
and may be another one?

avatar

fdgfdsghsdstrhsdharst

avatar

vfxbdafbafbdab

avatar

dfgfdasgfdagardeg

avatar

AAAAAAAAAAAAAAAAAAAAAa

avatar

vcdsagdasgasg

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

vvdxvbcxzbvzcxBGv

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE REPLYKE

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

Its a reply folks! This cannot be TRUE!

avatar
Visiteur anonyme (non connecté) 1 an il y a Visiteur anonyme (non connecté)

cxbvcdsbsf

avatar
Visiteur anonyme (non connecté) 2 mois il y a Visiteur anonyme (non connecté)

новый ответ

avatar
Visiteur anonyme (non connecté) 2 mois il y a Visiteur anonyme (non connecté)

и еще один новый ответ

avatar
Visiteur anonyme (non connecté) 3 semaines il y a Visiteur anonyme (non connecté)

Test tttttttt

avatar

I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest. I really know this is the newest.

avatar

aacbsdfhjnafgsngfasns

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

Youre not my reply

avatar

cdsfdsaf VVVV cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf cdsfdsaf

avatar
peter 2 ans il y a

xzxzdsvdsavbsa

X
Ce commentaire a été supprimé.
avatar

blible

avatar

This is a comment

avatar

Now another test

avatar
Visiteur anonyme (non connecté) 2 ans il y a Visiteur anonyme (non connecté)

Reply to another test

avatar
user73721008 2 mois il y a

Hello, I've been reading your post and must say that it's excellent. But I'm having difficulty. I can't figure out how to use Flask-SQLAlchemy and WTF forms to enter these comment replies. I did the same thing with Miguel's piece. I'd appreciate some assistance with this. If at all possible, use an example. Thanks.

avatar

Lets try new comment 29

avatar

Hello, testing in 2021