Repository with sources and generator of https://larlet.fr/david/ https://larlet.fr/david/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <!doctype html>
  2. <html lang=fr>
  3. <head>
  4. <!-- Always define the charset before the title -->
  5. <meta charset=utf-8>
  6. <title>De Dotclear à Django : migration des données et redirections — Biologeek — David Larlet</title>
  7. <!-- Define a viewport to mobile devices to use - telling the browser to assume that the page is as wide as the device (width=device-width) and setting the initial page zoom level to be 1 (initial-scale=1.0) -->
  8. <meta name="viewport" content="width=device-width, initial-scale=1"/>
  9. <!-- Fake favicon, to avoid extra request to the server -->
  10. <link rel="icon" href="data:;base64,iVBORw0KGgo=">
  11. <link type="application/atom+xml" rel="alternate" title="Feed" href="/david/log/" />
  12. <link rel="manifest" href="/manifest.json">
  13. <link rel="stylesheet" href="/static/david/css/larlet-david-_J6Rv.css" data-instant-track />
  14. <noscript>
  15. <style type="text/css">
  16. /* Otherwise fonts are loaded by JS for faster initial rendering. See scripts at the bottom. */
  17. body {
  18. font-family: 'EquityTextB', serif;
  19. }
  20. h1, h2, h3, h4, h5, h6, time, nav a, nav a:link, nav a:visited {
  21. font-family: 'EquityCapsB', sans-serif;
  22. font-variant: normal;
  23. }
  24. </style>
  25. </noscript>
  26. <!-- Canonical URL for SEO purposes -->
  27. <link rel="canonical" href="https://larlet.fr/david/biologeek/archives/20070523-de-dotclear-a-django-migration-des-donnees-et-redirections">
  28. </head>
  29. <body>
  30. <div>
  31. <header>
  32. <nav>
  33. <p>
  34. <small>
  35. Je suis <a href="/david/" title="Profil public">David Larlet</a>, <a href="/david/pro/" title="Activité professionnelle">artisan</a> du web qui vous <a href="/david/pro/accompagnement/" title="Activité d’accompagnement">accompagne</a><span class="more-infos"> dans l’acquisition de savoirs pour concevoir des <a href="/david/pro/produits-essentiels/" title="Qu’est-ce qu’un produit essentiel ?">produits essentiels</a></span>. <span class="more-more-infos">Discutons ensemble d’une <a href="/david/pro/devis/" title="En savoir plus">non-demande de devis</a>.</span> Je partage ici mes <a href="/david/blog/" title="Expériences bienveillantes">réflexions</a> et <a href="/david/correspondances/2017/" title="Lettres hebdomadaires">correspondances</a>.
  36. </small>
  37. </p>
  38. </nav>
  39. </header>
  40. <section>
  41. <h1 property="schema:name">De Dotclear à Django : migration des données et redirections</h1>
  42. <article typeof="schema:BlogPosting">
  43. <div property="schema:articleBody">
  44. <img src="/static/david/biologeek/images/logos/biologeek.png" alt="vignette" style="float:left; margin: 0.5em 1em;" property="schema:thumbnailUrl" />
  45. <p>À force de me <a href="http://www.prendreuncafe.com/blog/post/2007/05/22/Tags-faciles-avec-Propel-Symfony#c10885">faire</a> <a href="https://larlet.fr/david/biologeek/archives/20070516-torture/#c22043">chambrer</a> sur le <a href="https://larlet.fr/david/biologeek/archives/20070224-objectifs-et-motivations-de-la-refonte-de-ce-blog/">retard de ma refonte</a>, j'ai décidé de prendre le taureau par les cornes en attaquant la migration des données. Je me suis rendu compte que ce n'était finalement pas si difficile que ça avec Django...</p>
  46. <h2>Migration des données</h2>
  47. <p>La première étape est de récupérer les données depuis Dotclear. J'avais commencé à faire des scripts SQL mais <strong>j'ai finalement opté pour une solution en texte brut</strong> qui m'offrait plus de souplesse. J'ai donc installé le <a href="http://doc.dotclear.net/2.0/admin/1-to-2">plugin flatExport pour Dotclear</a> qui permet de récupérer le contenu du blog sous forme de fichier texte. Quelques lignes de python et l'on converti ce fichier en dictionnaires pour faciliter l'accès aux données (c'est un peu le relationnel du pauvre...)&nbsp;:</p>
  48. <pre>for table in open('blog-backup.txt').read().split('
  49. ',1)[1].strip().split('
  50. '):
  51. header, content = table.split('
  52. ',1)
  53. table_name, column_names = header[1:-1].split(' ')
  54. column_names = tuple(column_names.split(','))
  55. blog[table_name] = {}
  56. for item in content.split('
  57. '):
  58. column_contents = tuple((decode_html(column) for column in item[1:-1].split('","')))
  59. item_dict = dict(zip(column_names, column_contents))
  60. id = item_dict[column_names[0]]
  61. blog[table_name][id] = item_dict</pre>
  62. <p><strong>La grande force de Django c'est de pouvoir utiliser votre projet dans un script python</strong> afin de pouvoir appeler directement les objets/classes/tables en python (ce sont ces petits détails qui font apprécier un framework plutôt qu'un autre), c'est ce que j'ai fait pour remplir mes nouvelles tables avec les données issues de Dotclear&nbsp;:</p>
  63. <pre>for post_dict in blog['post'].values():
  64. django_post = Post()
  65. django_post.title = post_dict['post_titre']
  66. django_post.slug = post_dict['post_titre_url']
  67. django_post.tags = blog['categorie'][post_dict['cat_id']]['cat_libelle_url'].lower()
  68. django_post.summary_html = post_dict['post_chapo']
  69. django_post.summary = post_dict['post_chapo_wiki']
  70. django_post.content_html = post_dict['post_content']
  71. django_post.content = post_dict['post_content_wiki']
  72. django_post.creation_date = post_dict['post_creadt']
  73. django_post.modification_date = post_dict['post_upddt']
  74. django_post.publication_date = post_dict['post_dt']
  75. django_post.is_online = post_dict['post_pub']
  76. django_post.is_bestof = post_dict['post_selected']
  77. django_post.markup = 'wiki2xhtml'
  78. django_post.save()</pre>
  79. <p>Et voila, je peux maintenant vérifier le nombre de billets présents avec le shell de Django&nbsp;:</p>
  80. <pre>$ python manage.py shell
  81. &gt;&gt;&gt; from biologeek.journal.models import Post
  82. &gt;&gt;&gt; Post.objects.count()
  83. 190</pre>
  84. <p><strong>Parfait !</strong></p>
  85. <p>Le script complet est disponible <a href="http://code.google.com/p/biologeek/">sur le dépôt</a>, il a fallu que je modifie un peu le plugin de Dotclear pour qu'il prenne en compte mes tables de blogmarks. Il ne me reste plus qu'à migrer les commentaires.</p>
  86. <h2>Redirections</h2>
  87. <p>Un autre point essentiel lors d'une refonte est de <strong>rediriger chaque ancienne <abbr title="Uniform Resource Locator">URL</abbr> vers la nouvelle et ce de manière permanente</strong>. Il existe une <a href="http://www.djangoproject.com/documentation/redirects/">application de redirection dans les contributions de Django</a> mais ça ne convenait pas à mes modifications au niveau des URL des billets qui nécessitent de récupérer leurs tags pour faire la redirection.</p>
  88. <p>Heureusement, oh oui <strong>heureusement, que j'ai conservé le index.php dans toutes mes URL</strong> car sinon la complexité des expressions régulières à gérer m'aurait donné un mal de crâne interminable. Du coup, ça devient vraiment plus simple car je peux rediriger toutes les URL commençant par /journal/index.php vers mon script de redirection. Le script prend en compte toutes les URL que j'ai pu répertorié (j'espère ne pas en avoir oublié, au pire je les rajouterais lors de la migration)&nbsp;:</p>
  89. <pre>urlpatterns = patterns('',
  90. ('archives', redirect('/archives/')),
  91. ('rss.php$', redirect_rss('/rss/journal/')),
  92. ('atom.php$', redirect_rss('/rss/journal/')),
  93. ('rss_ailleurs.xml$', redirect('/rss/bistrot/')),
  94. ('(?P&lt;tag&gt;[A-Z][-\w]+)/$', redirect_tag('/%(tag)s/')),
  95. ('(?P&lt;slug&gt;[-\w]+)/$', redirect_post()),
  96. ('$', redirect('/journal/')),
  97. )</pre>
  98. <p><strong>[edit du lendemain]</strong>&nbsp;: finalement c'est pas si simple car certaines URL ne sont pas derrière index.php, du coup ça donne ça&nbsp;:</p>
  99. <pre>urlpatterns = patterns('',
  100. ('index.php/archives', redirect('/archives/')),
  101. ('index.php/(?P&lt;tag&gt;[A-Z][-\w]+)/$', redirect_tag('/%(tag)s/')),
  102. ('index.php/(?P&lt;slug&gt;[-\w]+)/$', redirect_post()),
  103. ('index.php/$', redirect('/journal/')),
  104. ('rss.php$', redirect_rss('/feeds/rss/')),
  105. ('atom.php$', redirect_rss('/feeds/rss/')),
  106. ('rss_ailleurs.xml$', redirect('/feeds/rss/bistrot/')),
  107. )</pre>
  108. <p>La fonction appelée fait ensuite une redirection permanente (301) vers la nouvelle URL, par exemple dans le cas des billets&nbsp;:</p>
  109. <pre>def redirect_post():
  110. def inner(request, slug):
  111. post = Post.published.get(slug=slug)
  112. return HttpResponsePermanentRedirect(post.get_absolute_url())
  113. return inner</pre>
  114. <p>Au final il ne m'aura fallu que <strong>quelques lignes de python</strong> pour franchir cette nouvelle étape, la prochaine fois on ajoute les commentaires et les fils RSS. On avance, on avance...</p>
  115. </div>
  116. </article>
  117. <footer>
  118. <h6 property="schema:datePublished">— 23/05/2007</h6>
  119. </footer>
  120. </section>
  121. <section>
  122. <div>
  123. <h3>Articles peut-être en rapport</h3>
  124. <ul>
  125. <li><a href="/david/biologeek/archives/20080423-biologeek-enfin-propulse-par-django/" title="Accès à Biologeek (enfin) propulsé par Django">Biologeek (enfin) propulsé par Django</a></li>
  126. <li><a href="/david/biologeek/archives/20070623-ajout-des-flux-rss-du-sitemap-et-des-commentaires-avec-django/" title="Accès à Ajout des flux RSS, du sitemap et des commentaires avec Django">Ajout des flux RSS, du sitemap et des commentaires avec Django</a></li>
  127. <li><a href="/david/biologeek/archives/20070424-vues-generiques-heritage-et-templatetags-developpez-rapidement-avec-django/" title="Accès à Vues génériques, héritage et templatetags : développez rapidement avec Django">Vues génériques, héritage et templatetags : développez rapidement avec Django</a></li>
  128. </ul>
  129. </div>
  130. </section>
  131. <section>
  132. <div id="comments">
  133. <h3>Commentaires</h3>
  134. <div class="comment" typeof="schema:UserComments">
  135. <p class="comment-meta">
  136. <span class="comment-author" property="schema:creator">sebastien</span> le <span class="comment-date" property="schema:commentTime">23/05/2007</span> :
  137. </p>
  138. <div class="comment-content" property="schema:commentText">
  139. <p>Salut,<br />
  140. Très bon billet avec example à l'appui et code d'export ! <br />
  141. Place maitenant à la remarque déplaisante :)<br />
  142. J'ai récupéré le code du projet biologeek, et j'ai remarqué --depuis environ 1 mois, une petite coquille/erreur dans l'interface d'administration de Django : le fichier jsi18n ne se charge pas ! Cela semble dû à un problème de mapping d'URL, car quand je déplace la partie concernant l'interface d'administration au début du fichier urls.py, tout fonctionne...<br />
  143. Sinon, très bonne initiative pour ton projet : cela donne un exemple concret et complet d'utilisation et de réutilisation de code/projet existant. Bonne continuation !</p>
  144. </div>
  145. </div>
  146. <div class="comment" typeof="schema:UserComments">
  147. <p class="comment-meta">
  148. <span class="comment-author" property="schema:creator">David, biologeek</span> le <span class="comment-date" property="schema:commentTime">24/05/2007</span> :
  149. </p>
  150. <div class="comment-content" property="schema:commentText">
  151. <p>En effet, l'expression régulière des billets était un peu trop violente ! J'ai corrigé, merci. À force de tester le front j'en avais oublié le back...<br />
  152. <br />
  153. ps : il n'y a pas de remarque déplaisante à trouver un bug, c'est plus une aide précieuse :-)</p>
  154. </div>
  155. </div>
  156. <div class="comment" typeof="schema:UserComments">
  157. <p class="comment-meta">
  158. <span class="comment-author" property="schema:creator">Pierre-Jean</span> le <span class="comment-date" property="schema:commentTime">24/05/2007</span> :
  159. </p>
  160. <div class="comment-content" property="schema:commentText">
  161. <p>Sympa, ça me donne envie de faire la même chose chez moi !<br />
  162. <br />
  163. Merci pour la qualité de tes billets.</p>
  164. </div>
  165. </div>
  166. <div class="comment" typeof="schema:UserComments">
  167. <p class="comment-meta">
  168. <span class="comment-author" property="schema:creator">NiCoS</span> le <span class="comment-date" property="schema:commentTime">24/05/2007</span> :
  169. </p>
  170. <div class="comment-content" property="schema:commentText">
  171. <p>Ouh ouh, j'adore et ça va m'être bigrement utile :-)</p>
  172. </div>
  173. </div>
  174. <div class="comment" typeof="schema:UserComments">
  175. <p class="comment-meta">
  176. <span class="comment-author" property="schema:creator">NiKo</span> le <span class="comment-date" property="schema:commentTime">24/05/2007</span> :
  177. </p>
  178. <div class="comment-content" property="schema:commentText">
  179. <p>Hey, c'était pas méchant le chambrage ;)<br />
  180. <br />
  181. En tout cas vivement la nouvelle version :)</p>
  182. </div>
  183. </div>
  184. <div class="comment" typeof="schema:UserComments">
  185. <p class="comment-meta">
  186. <span class="comment-author" property="schema:creator">loïc m.</span> le <span class="comment-date" property="schema:commentTime">25/05/2007</span> :
  187. </p>
  188. <div class="comment-content" property="schema:commentText">
  189. <p>&quot;Au final il ne m'aura fallu que quelques lignes de python pour franchir cette nouvelle étape, la prochaine fois on ajoute les commentaires et les fils RSS. On avance, on avance...&quot;<br />
  190. <br />
  191. Reste à définir la variable &quot;temps&quot; qui sépare cette étape de la prochaine ;)<br />
  192. Depuis le temps qu'on entend parler du nouveau biologeek made in Python... :p</p>
  193. </div>
  194. </div>
  195. <div class="comment" typeof="schema:UserComments">
  196. <p class="comment-meta">
  197. <span class="comment-author" property="schema:creator">RSS xml</span> le <span class="comment-date" property="schema:commentTime">03/10/2007</span> :
  198. </p>
  199. <div class="comment-content" property="schema:commentText">
  200. <p>&quot;j'espere ne pas en avoir oublie, au pire je les rajouterais lors de la migration&quot;, euhhh !? :)</p>
  201. </div>
  202. </div>
  203. </div>
  204. </section>
  205. <footer>
  206. <nav>
  207. <p>
  208. <small>
  209. Je réponds quasiment toujours aux <a href="m&#x61;ilto:d&#x61;vid%40l&#x61;rlet&#46;fr" title="Envoyer un email">emails</a> (<a href="/david/signature/" title="Ma signature actuelle avec possibilité de chiffrement">signés</a>) et vous pouvez me rencontrer à Montréal. <span class="more-infos">N’hésitez pas à <a href="/david/log/" title="Être tenu informé des mises à jour">vous abonner</a> pour être tenu informé des publications récentes.</span>
  210. </small>
  211. </p>
  212. </nav>
  213. </footer>
  214. </div>
  215. <script src="/static/david/js/larlet-david-3ee43f.js" data-no-instant></script>
  216. <script data-no-instant>InstantClick.init()</script>
  217. </body>
  218. </html>