changeset 294:254db4cb6a86

Changes / scripts to import forums. Other tweaks and moving other import scripts to the legacy application.
author Brian Neal <bgneal@gmail.com>
date Wed, 05 Jan 2011 04:09:35 +0000
parents c92fb89dbc7d
children 26fc9ac9a0eb
files gpp/comments/models.py gpp/core/fixtures/flatpages.json gpp/forums/fixtures/forums.json gpp/forums/management/__init__.py gpp/forums/management/commands/__init__.py gpp/forums/management/commands/sync_forums.py gpp/legacy/management/commands/import_old_links.py gpp/legacy/management/commands/import_old_news.py gpp/legacy/management/commands/import_old_news_comments.py gpp/legacy/management/commands/import_old_podcasts.py gpp/legacy/management/commands/import_old_topics.py gpp/legacy/management/commands/import_old_users.py gpp/legacy/management/commands/translate_old_posts.py gpp/legacy/phpbb.py gpp/podcast/management/__init__.py gpp/podcast/management/commands/__init__.py gpp/podcast/management/commands/import_old_podcasts.py gpp/weblinks/management/__init__.py gpp/weblinks/management/commands/__init__.py gpp/weblinks/management/commands/import_old_links.py tools/load_fixtures.bash
diffstat 15 files changed, 526 insertions(+), 161 deletions(-) [+]
line wrap: on
line diff
--- a/gpp/comments/models.py	Wed Dec 29 04:56:53 2010 +0000
+++ b/gpp/comments/models.py	Wed Jan 05 04:09:35 2011 +0000
@@ -1,6 +1,8 @@
 """
 Models for the comments application.
 """
+import datetime
+
 from django.db import models
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
@@ -34,9 +36,9 @@
     user = models.ForeignKey(User)
     comment = models.TextField(max_length=COMMENT_MAX_LENGTH)
     html = models.TextField(blank=True)
-    creation_date = models.DateTimeField(auto_now_add=True)
+    creation_date = models.DateTimeField()
     ip_address = models.IPAddressField('IP Address')
-    is_public = models.BooleanField(default=True, 
+    is_public = models.BooleanField(default=True,
             help_text='Uncheck this field to make the comment invisible.')
     is_removed = models.BooleanField(default=False,
             help_text='Check this field to replace the comment with a ' \
@@ -52,6 +54,9 @@
         return u'%s: %s...' % (self.user.username, self.comment[:50])
 
     def save(self, *args, **kwargs):
+        if not self.id:
+            self.creation_date = datetime.datetime.now()
+
         self.html = site_markup(self.comment)
         super(Comment, self).save(*args, **kwargs)
 
--- a/gpp/core/fixtures/flatpages.json	Wed Dec 29 04:56:53 2010 +0000
+++ b/gpp/core/fixtures/flatpages.json	Wed Jan 05 04:09:35 2011 +0000
@@ -25,7 +25,7 @@
             "sites": [
                 1
             ], 
-            "content": "<p>Placeholder for a colophon. Will list software used to build and maintain the site. Give thanks to list of people who helped.</p>", 
+            "content": "<p><a href=\"http://surfguitar101.com/\">SurfGuitar101.com</a> was created by Brian Neal. The server-side code was written in the <a href=\"http://python.org/\">Python</a> programming language and based upon the awesome <a href=\"http://djangoproject.com/\">Django</a> Web framework. Client-side coding was done in <a href=\"http://en.wikipedia.org/wiki/JavaScript\">Javascript</a>, making heavy use of the <a href=\"http://jquery.org/\">jQuery</a> and <a href=\"http://jqueryui.com/\">jQuery UI</a> libraries.</p>\r\n<p>The site design was created by <a href=\"http://ken.crashmatics.com/\">Ken Dushane</a> of the band <a href=\"http://crashmatics.com/\">The Crashmatics</a>. Various icons and graphics were contributed by Ferenc Dobronyi and Joseph Koch. Additional icons courtesy of <a href=\"http://www.famfamfam.com/lab/icons/silk/\">FamFamFam</a>.</p>\r\n<p>The following 3rd party libraries were leveraged in the construction of this site: <a href=\"http://sourceforge.net/projects/mysql-python/\">MySQLdb</a>, <a href=\"http://www.freewisdom.org/projects/python-markdown/\">python-markdown</a>, <a href=\"http://www.pythonware.com/products/pil/\">PIL</a>, <a href=\"http://pytz.sourceforge.net/\">pytz</a>, <a href=\"http://code.google.com/p/django-tagging/\">django-tagging</a>, <a href=\"http://github.com/leah/django-elsewhere\">django-elsewhere</a>, <a href=\"http://code.google.com/p/gdata-python-client/\">gdata-python-client</a>, <a href=\"http://www.tummy.com/Community/software/python-memcached/\">python-memcached</a>, <a href=\"http://code.google.com/p/html5lib/\">html5lib</a>, <a href=\"http://tinymce.moxiecode.com/\">tinymce</a>, <a href=\"http://markitup.jaysalvat.com/home/\">markItUp!</a>, <a href=\"http://haystacksearch.org/\">Haystack</a>, <a href=\"http://github.com/notanumber/xapian-haystack/tree/master\">xapian-haystack</a>, &amp; <a href=\"http://pypi.python.org/pypi/repoze.timeago/0.5\">repoze.timeago</a>.</p>\r\n<p>The site runs on an infrastructure powered by open-source tools: the <a href=\"http://httpd.apache.org/\">Apache server</a> with <a href=\"http://code.google.com/p/modwsgi/\">mod_wsgi</a>, a <a href=\"http://www.mysql.com/\">MySQL database</a>, and <a href=\"http://memcached.org/\">memcached</a>. The server is running <a href=\"http://www.ubuntu.com/\">Ubuntu</a>, an operating system based upon the <a href=\"http://www.debian.org/\">Debian</a> <a href=\"http://www.gnu.org/\">GNU</a> / <a href=\"http://www.kernel.org/\">Linux</a> distribution.</p>\r\n<p>Other tools used to develop this site include <a href=\"http://subversion.apache.org/\">Subversion</a>, <a href=\"http://getfirebug.com/\">Firebug</a>, <a href=\"http://www.vim.org/\">Vim</a>, &amp; <a href=\"http://www.phpmyadmin.net/\">phpMyAdmin</a>.</p>", 
             "enable_comments": false
         }
     }, 
@@ -55,7 +55,7 @@
             "sites": [
                 1
             ], 
-            "content": "<div>\r\n<p>Your  use of our Internet sites is subject to these Terms of Service  (\"Terms\"). We may modify these Terms at any time without notice to you  by posting revised Terms on our sites. Your use of our sites constitutes  your binding acceptance of these Terms, including any modifications  that we make.</p>\r\n<h3><strong>Content on Our Sites</strong></h3>\r\n<p>Our sites  include a combination of content that we create and that our users  create. You are solely responsible for all materials, whether publicly  posted or privately transmitted, that you upload, post, email, transmit  or otherwise make available on our sites (\"Your Content\"). You certify  that you own all intellectual property rights in Your Content. You  hereby grant us, our affiliates and our partners a worldwide,  irrevocable, royalty-free, nonexclusive, sublicensable license to use,  reproduce, create derivative works of, distribute, publicly perform,  publicly display, transfer, transmit, distribute and publish Your  Content and subsequent versions of Your Content for the purposes of (i)  displaying Your Content on our sites, (ii) distributing Your Content,  either electronically or via other media, to users seeking to download  or otherwise acquire it, and/or (iii) storing Your Content in a remote  database accessible by end users. This license shall apply to the  distribution and the storage of Your Content in any form, medium, or  technology now known or later developed.</p>\r\n<h3><strong>Your Conduct on Our  Sites</strong></h3>\r\n<p>You agree not to post or transmit material that is  knowingly false and/or defamatory, misleading, inaccurate, abusive,  vulgar, hateful, harassing, obscene, profane, sexually oriented,  threatening or invasive of a person's privacy; that otherwise violates  any law; or that encourages conduct constituting a criminal offense.</p>\r\n<h3><strong>User  Agreement for SurfGuitar101.com Forums<br /></strong></h3>\r\n<p>This message  forum, and other user contributed/comment areas (\"Forums\") are provided  as a service to members of our community. By using or participating on  the Forums, you agree to this User Agreement including but not limited  to the Rules of Conduct and the Terms of Service stated below. For  purposes of this agreement, \"User\" refers to any individual posting on  or otherwise using the Forums and SG101 refers to the owners and staff  of SurfGuitar101.com and their authorized representatives.</p>\r\n<p>SG101  reserves the right to change the Rules of Conduct, Terms of Service and  all other parts of this User Agreement at its sole discretion and  without notice.</p>\r\n<p>As a standard operating procedure,  SG101 does not enter into correspondence, discussions or other  communication, either public or private, about SG101 policies,  individual moderators, enforcement or application of the User Agreement, bans or other sanctions,  etc.</p>\r\n<h3><strong>RULES OF CONDUCT</strong></h3>\r\n<p>User agrees not to post  material that is knowingly false and/or defamatory, misleading,  inaccurate, abusive, vulgar, hateful, harassing, obscene, profane,  sexually oriented, threatening, invasive of a person's privacy, that  otherwise violates any law, or that encourages conduct constituting a  criminal offense.</p>\r\n<p>User agrees not to post any material  that is protected by copyright, trademark or other proprietary right  without the express permission of the owner(s) of said copyright,  trademark or other proprietary right.</p>\r\n<p>User agrees not  to use nicknames that might be deemed abusive, vulgar, hateful,  harassing, obscene, profane, sexually oriented, threatening, invasive of  a person's privacy, or otherwise inappropriate. User agrees not to use  nicknames that might mislead other Users. This includes but is not  limited to using nicknames that impersonate developers, staff, or other  Users, or other individuals outside of SG101.</p>\r\n<h3><strong>TERMS OF SERVICE</strong></h3>\r\n<p>User  acknowledges and agrees that use of the SG101 is a privilege, not a  right, and that SG101 has the right, at its sole discretion, to revoke  this privilege at any time without notice or reason. User agrees that  this Agreement in its entirety applies to both public and private  messages.</p>\r\n<p>The goal of the Forums is to foster communication and  the interchange of ideas within the User community. User agrees and  acknowledges that any posts, nicknames or other material deemed  offensive, harassing, baiting or otherwise inappropriate may be removed  at the sole discretion of SG101.</p>\r\n<p>User authorizes SG101  to make use of any original stories, concepts, ideas, drawings,  photographs, opinions and other creative materials posted on the Forums  without compensation or other recourse. User also agrees to indemnify  and hold harmless SG101 and our agents with respect to any claims based  upon or arising from the transmission and/or content of your message(s).</p>\r\n<p>SG101  has the right but not the obligation to monitor and/or moderate the  Forums, and offers no assurances in this regard.</p>\r\n<p>SG101  is not responsible for messages posted on the Forums or the content  therein. We do not vouch for or warrant the accuracy, completeness or  usefulness of any message. Each message expresses the views of its  originating User, not necessarily those of SG101. Unless expressly  stated otherwise by a senior SG101 representative, this includes  messages posted by SG101 personnel, agents, delegates, representatives  et al.</p>\r\n<p>Any User who feels that a posted message is  objectionable is encouraged to contact us. We have the ability to remove  messages and we will make every effort to do so within a reasonable  time if we determine that removal is necessary. This is a manual  process, however, so please realize that we may not be able to act  immediately. Removal of messages is at the sole discretion of SG101.</p>\r\n<p>The  appropriate individual to contact is usually the editor of the site  associated with the board where the message in question is to be found.  As a standard operating procedure, SG101 does not enter into  discussions, either public or private, about Forum policies, individual  moderators, bans or other sanctions, etc.</p>\r\n<p>SG101  reserves the right to reveal the identity of and/or whatever information  we know about any User in the event of a complaint or legal action  arising from any message posted by said User.</p>\r\n<p>Advertisements,  chain letters, pyramid schemes and other commercial solicitations are  inappropriate on the Forums.</p>\r\n<p>SG101 does not permit  children under the age of 13 to become members, post home pages or web  sites on our service.</p>\r\n<p>SG101 is not responsible for the  content posted by SG101 members or visitors on any area of our site  including without limitation. The opinions and views expressed by  SG101's members or visitors do not necessarily represent those of SG101  and SG101 does not verify, endorse, or vouch for the content of such  opinions or views. Further, SG101 is not responsible for the delivery or  quality of any goods or services sold or advertised through or on SG101  members' page(s). If you believe that any of the content posted by our  members or visitors violates your proprietary rights, including  copyrights, please contact us.</p>\r\n<p>You are solely and  fully responsible for any content that you post any area of our site. We  do not regularly review the contents of materials posted by our members  or other visitors to our site. We strictly prohibit the posting of the  following types of content on all areas of our sites:</p>\r\n<ul>\r\n<li>nudity,  pornography, and sexual material of a lewd, lecherous or obscene nature  and intent or that violates local, state and national laws.</li>\r\n<li>any  material that violates or infringes in any way upon the proprietary  rights of others, including, without limitation, copyright or trademark  rights; this includes \"WAREZ\" (copyrighted software that is distributed  illegally), \"mp3\" files of copyrighted music, copyrighted photographs,  text, video or artwork. If you don't own the copyright or have express  authorization and documented permission to use it, don't put it on SG101  (if you do have express permission you must say so clearly). SG101 will  terminate the memberships of, and remove the pages of, repeat  infringers.</li>\r\n<li>any material that is threatening, abusive,  harassing, defamatory, invasive of privacy or publicity rights, vulgar,  obscene, profane, indecent, or otherwise objectionable; including  posting other peoples' private information.</li>\r\n<li>content that  promotes, encourages, or provides instructional information about  illegal activities - specifically hacking, cracking, or phreaking.</li>\r\n<li>any  software, information, or other material that contains a virus, \"Trojan  Horse\", \"worm\" corrupted data, or any other harmful or damaging  component;</li>\r\n<li>hate propaganda or hate mongering, swearing, or  fraudulent material or activity;</li>\r\n</ul>\r\n</div>\r\n<div class=\"basicCentral-elm\">\r\n<p>By  submitting your data to SG101, you represent that the data complies with  SG101's Terms of Service. If any third party brings a claim, lawsuit or  other proceeding against SG101 based on your conduct or use of SG101  services, you agree to compensate SG101 (including its officers,  directors, employees and agents) for any and all losses, liabilities,  damages or expenses, including attorney's fees, incurred by SG101 in  connection with any such claim, lawsuit or proceeding.</p>\r\n<p>SG101  is the final arbiter of what IS and IS NOT allowed on our site.  Further, SG101 reserves the right to modify or remove anything submitted  to SG101, and to cancel any membership, at any time for any reason  without prior notice. SG101 is not obliged to maintain back-ups copies  of any material submitted or posted on our site. Actions or activities  that may cause termination of your membership and/or removal of your  page(s) include, but are not limited to:</p>\r\n<ul>\r\n<li>posting  or providing links to any content which violates our Terms of Service:</li>\r\n<li>conducting  or providing links to any raffle, contest, or game which violates any  local, state or national laws;</li>\r\n<li>using in the registration of your  SG101 membership an email account that is not your own or that is or  becomes inactive.</li>\r\n<li>violating the SG101 Terms of Service. Please  read and familiarize yourself with the SG101 Terms of Service.</li>\r\n<li>sending  unsolicited email using a SG101 address</li>\r\n<li>reproducing,  distributing, republishing or retransmitting material posted by other  SG101 members without the prior permission of such members.</li>\r\n</ul>\r\n</div>\r\n<div class=\"basicCentral-elm\">\r\n<p>We  reserve the right to monitor, and to investigate any complaints  regarding any content of SG101 members' pages, message-board postings,  and to take appropriate action if SG101 finds violations of these Terms  of Service.. In the case of any such complaint, SG101 reserves the right  to remove the content complained of while the SG101 member and the  complaining party attempt to resolve their dispute. This could result in  your posts(s) being removed from SG101 for as long as it takes to  resolve the dispute.</p>\r\n<p>You grant to SG101 and its  affiliates a royalty-free, perpetual, irrevocable, nonexclusive,  worldwide, unrestricted license to use, copy, modify, transmit,  distribute, and publicly perform or display the submitted pages or other  content for the purposes of displaying such information on SG101's  sites and for the promotion and marketing of SG101's services.</p>\r\n<h3><strong>MISC.</strong></h3>\r\n<p>SG101  makes no guarantee of availability of service and reserves the right to  change, withdraw, suspend, or discontinue any functionality or feature  of the SG101 service. IN NO EVENT WILL BE LIABLE FOR ANY DAMAGES,  INCLUDING, WITHOUT LIMITATION, DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  CONSEQUENTIAL, OR PUNITIVE DAMAGES ARISING OUT OF THE USE OF OR  INABILITY TO USE SG101'S SERVICES OR ANY CONTENT THEREON FOR ANY REASON  INCLUDING, WITHOUT LIMITATION, SG101'S REMOVAL OR DELETION OF ANY  MATERIALS OR RECORDS SUBMITTED OR POSTED ON SG101'S SITE FOR ANY REASON.  THIS DISCLAIMER APPLIES, WITHOUT LIMITATION, TO ANY DAMAGES OR INJURY,  WHETHER FOR BREACH OF CONTRACT, TORT, OR OTHERWISE, CAUSED; ANY FAILURE  OF PERFORMANCE; ERROR; OMISSION; INTERRUPTION; DELETION; DEFECT; DELAY  IN OPERATION OR TRANSMISSION; COMPUTER VIRUS; FILE CORRUPTION;  COMMUNICATION-LINE FAILURE; NETWORK OR SYSTEM OUTAGE; OR THEFT, DESTRUCTION,  UNAUTHORIZED ACCESS TO, ALTERATION OF, OR USE OF ANY RECORD.</p>\r\n<p>SG101  reserves the right to change or amend these Terms of Service at any  time without prior notice. By registering and/or submitting any content,  including without limitation, message-board postings, you signify your  agreement to these Terms of Service.</p>\r\n</div>", 
+            "content": "<div>\r\n<p>Your  use of our Internet sites is subject to these Terms of Service  (\"Terms\"). We may modify these Terms at any time without notice to you  by posting revised Terms on our sites. Your use of our sites constitutes  your binding acceptance of these Terms, including any modifications  that we make.</p>\r\n<h3><strong>Content on Our Sites</strong></h3>\r\n<p>Our sites  include a combination of content that we create and that our users  create. You are solely responsible for all materials, whether publicly  posted or privately transmitted, that you upload, post, email, transmit  or otherwise make available on our sites (\"Your Content\"). You certify  that you own all intellectual property rights in Your Content. You  hereby grant us, our affiliates and our partners a worldwide,  irrevocable, royalty-free, nonexclusive, sublicensable license to use,  reproduce, create derivative works of, distribute, publicly perform,  publicly display, transfer, transmit, distribute and publish Your  Content and subsequent versions of Your Content for the purposes of (i)  displaying Your Content on our sites, (ii) distributing Your Content,  either electronically or via other media, to users seeking to download  or otherwise acquire it, and/or (iii) storing Your Content in a remote  database accessible by end users. This license shall apply to the  distribution and the storage of Your Content in any form, medium, or  technology now known or later developed.</p>\r\n<h3><strong>Your Conduct on Our  Sites</strong></h3>\r\n<p>You agree not to post or transmit material that is  knowingly false and/or defamatory, misleading, inaccurate, abusive,  vulgar, hateful, harassing, obscene, profane, sexually oriented,  threatening or invasive of a person's privacy; that otherwise violates  any law; or that encourages conduct constituting a criminal offense.</p>\r\n<h3><strong>User  Agreement for SurfGuitar101.com Forums<br /></strong></h3>\r\n<p>This message  forum, and other user contributed/comment areas (\"Forums\") are provided  as a service to members of our community. By using or participating on  the Forums, you agree to this User Agreement including but not limited  to the Rules of Conduct and the Terms of Service stated below. For  purposes of this agreement, \"User\" refers to any individual posting on  or otherwise using the Forums and SG101 refers to the owners and staff  of SurfGuitar101.com and their authorized representatives.</p>\r\n<p>SG101  reserves the right to change the Rules of Conduct, Terms of Service and  all other parts of this User Agreement at its sole discretion and  without notice.</p>\r\n<p>As a standard operating procedure,  SG101 does not enter into correspondence, discussions or other  communication, either public or private, about SG101 policies,  individual moderators, enforcement or application of the User Agreement, bans or other sanctions,  etc.</p>\r\n<h3><strong>RULES OF CONDUCT</strong></h3>\r\n<p>User agrees not to post  material that is knowingly false and/or defamatory, misleading,  inaccurate, abusive, vulgar, hateful, harassing, obscene, profane,  sexually oriented, threatening, invasive of a person's privacy, that  otherwise violates any law, or that encourages conduct constituting a  criminal offense.</p>\r\n<p>User agrees not to post any material  that is protected by copyright, trademark or other proprietary right  without the express permission of the owner(s) of said copyright,  trademark or other proprietary right.</p>\r\n<p>User agrees not  to use nicknames that might be deemed abusive, vulgar, hateful,  harassing, obscene, profane, sexually oriented, threatening, invasive of  a person's privacy, or otherwise inappropriate. User agrees not to use  nicknames that might mislead other Users. This includes but is not  limited to using nicknames that impersonate developers, staff, or other  Users, or other individuals outside of SG101.</p>\r\n<h3><strong>TERMS OF SERVICE</strong></h3>\r\n<p>User  acknowledges and agrees that use of the SG101 is a privilege, not a  right, and that SG101 has the right, at its sole discretion, to revoke  this privilege at any time without notice or reason. User agrees that  this Agreement in its entirety applies to both public and private  messages.</p>\r\n<p>The goal of the Forums is to foster communication and  the interchange of ideas within the User community. User agrees and  acknowledges that any posts, nicknames or other material deemed  offensive, harassing, baiting or otherwise inappropriate may be removed  at the sole discretion of SG101.</p>\r\n<p>User authorizes SG101  to make use of any original stories, concepts, ideas, drawings,  photographs, opinions and other creative materials posted on the Forums  without compensation or other recourse. User also agrees to indemnify  and hold harmless SG101 and our agents with respect to any claims based  upon or arising from the transmission and/or content of your message(s).</p>\r\n<p>SG101  has the right but not the obligation to monitor and/or moderate the  Forums, and offers no assurances in this regard.</p>\r\n<p>SG101  is not responsible for messages posted on the Forums or the content  therein. We do not vouch for or warrant the accuracy, completeness or  usefulness of any message. Each message expresses the views of its  originating User, not necessarily those of SG101. Unless expressly  stated otherwise by a senior SG101 representative, this includes  messages posted by SG101 personnel, agents, delegates, representatives  et al.</p>\r\n<p>Any User who feels that a posted message is  objectionable is encouraged to contact us. We have the ability to remove  messages and we will make every effort to do so within a reasonable  time if we determine that removal is necessary. This is a manual  process, however, so please realize that we may not be able to act  immediately. Removal of messages is at the sole discretion of SG101.</p>\r\n<p>The  appropriate individual to contact is usually the editor of the site  associated with the board where the message in question is to be found.  As a standard operating procedure, SG101 does not enter into  discussions, either public or private, about Forum policies, individual  moderators, bans or other sanctions, etc.</p>\r\n<p>SG101  reserves the right to reveal the identity of and/or whatever information  we know about any User in the event of a complaint or legal action  arising from any message posted by said User.</p>\r\n<p>Advertisements,  chain letters, pyramid schemes and other commercial solicitations are  inappropriate on the Forums.</p>\r\n<p>SG101 does not permit  children under the age of 13 to become members, post home pages or web  sites on our service.</p>\r\n<p>SG101 is not responsible for the  content posted by SG101 members or visitors on any area of our site  including without limitation. The opinions and views expressed by  SG101's members or visitors do not necessarily represent those of SG101  and SG101 does not verify, endorse, or vouch for the content of such  opinions or views. Further, SG101 is not responsible for the delivery or  quality of any goods or services sold or advertised through or on SG101  members' page(s). If you believe that any of the content posted by our  members or visitors violates your proprietary rights, including  copyrights, please contact us.</p>\r\n<p>You are solely and  fully responsible for any content that you post any area of our site. We  do not regularly review the contents of materials posted by our members  or other visitors to our site. We strictly prohibit the posting of the  following types of content on all areas of our sites:</p>\r\n<ul>\r\n<li>nudity,  pornography, and sexual material of a lewd, lecherous or obscene nature  and intent or that violates local, state and national laws.</li>\r\n<li>any  material that violates or infringes in any way upon the proprietary  rights of others, including, without limitation, copyright or trademark  rights; this includes \"WAREZ\" (copyrighted software that is distributed  illegally), \"mp3\" files of copyrighted music, copyrighted photographs,  text, video or artwork. If you don't own the copyright or have express  authorization and documented permission to use it, don't put it on SG101  (if you do have express permission you must say so clearly). SG101 will  terminate the memberships of, and remove the pages of, repeat  infringers.</li>\r\n<li>any material that is threatening, abusive,  harassing, defamatory, invasive of privacy or publicity rights, vulgar,  obscene, profane, indecent, or otherwise objectionable; including  posting other peoples' private information.</li>\r\n<li>content that  promotes, encourages, or provides instructional information about  illegal activities - specifically hacking, cracking, or phreaking.</li>\r\n<li>any  software, information, or other material that contains a virus, \"Trojan  Horse\", \"worm\" corrupted data, or any other harmful or damaging  component;</li>\r\n<li>hate propaganda or hate mongering, swearing, or  fraudulent material or activity;</li>\r\n</ul>\r\n</div>\r\n<div class=\"basicCentral-elm\">\r\n<p>By  submitting your data to SG101, you represent that the data complies with  SG101's Terms of Service. If any third party brings a claim, lawsuit or  other proceeding against SG101 based on your conduct or use of SG101  services, you agree to compensate SG101 (including its officers,  directors, employees and agents) for any and all losses, liabilities,  damages or expenses, including attorney's fees, incurred by SG101 in  connection with any such claim, lawsuit or proceeding.</p>\r\n<p>SG101  is the final arbiter of what IS and IS NOT allowed on our site.  Further, SG101 reserves the right to modify or remove anything submitted  to SG101, and to cancel any membership, at any time for any reason  without prior notice. SG101 is not obliged to maintain back-ups copies  of any material submitted or posted on our site. Actions or activities  that may cause termination of your membership and/or removal of your  page(s) include, but are not limited to:</p>\r\n<ul>\r\n<li>posting  or providing links to any content which violates our Terms of Service:</li>\r\n<li>conducting  or providing links to any raffle, contest, or game which violates any  local, state or national laws;</li>\r\n<li>using in the registration of your  SG101 membership an email account that is not your own or that is or  becomes inactive.</li>\r\n<li>violating the SG101 Terms of Service. Please  read and familiarize yourself with the SG101 Terms of Service.</li>\r\n<li>sending  unsolicited email using a SG101 address</li>\r\n<li>reproducing,  distributing, republishing or retransmitting material posted by other  SG101 members without the prior permission of such members.</li>\r\n</ul>\r\n</div>\r\n<div class=\"basicCentral-elm\">\r\n<p>We  reserve the right to monitor, and to investigate any complaints  regarding any content of SG101 members' pages, message-board postings,  and to take appropriate action if SG101 finds violations of these Terms of Service. In the case of any such complaint, SG101 reserves the right  to remove the content complained of while the SG101 member and the  complaining party attempt to resolve their dispute. This could result in  your posts(s) being removed from SG101 for as long as it takes to  resolve the dispute.</p>\r\n<p>You grant to SG101 and its  affiliates a royalty-free, perpetual, irrevocable, nonexclusive,  worldwide, unrestricted license to use, copy, modify, transmit,  distribute, and publicly perform or display the submitted pages or other  content for the purposes of displaying such information on SG101's  sites and for the promotion and marketing of SG101's services.</p>\r\n<h3><strong>MISC.</strong></h3>\r\n<p>SG101  makes no guarantee of availability of service and reserves the right to  change, withdraw, suspend, or discontinue any functionality or feature  of the SG101 service. IN NO EVENT WILL BE LIABLE FOR ANY DAMAGES,  INCLUDING, WITHOUT LIMITATION, DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  CONSEQUENTIAL, OR PUNITIVE DAMAGES ARISING OUT OF THE USE OF OR  INABILITY TO USE SG101'S SERVICES OR ANY CONTENT THEREON FOR ANY REASON  INCLUDING, WITHOUT LIMITATION, SG101'S REMOVAL OR DELETION OF ANY  MATERIALS OR RECORDS SUBMITTED OR POSTED ON SG101'S SITE FOR ANY REASON.  THIS DISCLAIMER APPLIES, WITHOUT LIMITATION, TO ANY DAMAGES OR INJURY,  WHETHER FOR BREACH OF CONTRACT, TORT, OR OTHERWISE, CAUSED; ANY FAILURE  OF PERFORMANCE; ERROR; OMISSION; INTERRUPTION; DELETION; DEFECT; DELAY  IN OPERATION OR TRANSMISSION; COMPUTER VIRUS; FILE CORRUPTION;  COMMUNICATION-LINE FAILURE; NETWORK OR SYSTEM OUTAGE; OR THEFT, DESTRUCTION,  UNAUTHORIZED ACCESS TO, ALTERATION OF, OR USE OF ANY RECORD.</p>\r\n<p>SG101  reserves the right to change or amend these Terms of Service at any  time without prior notice. By registering and/or submitting any content,  including without limitation, message-board postings, you signify your  agreement to these Terms of Service.</p>\r\n</div>", 
             "enable_comments": false
         }
     }
--- a/gpp/forums/fixtures/forums.json	Wed Dec 29 04:56:53 2010 +0000
+++ b/gpp/forums/fixtures/forums.json	Wed Jan 05 04:09:35 2011 +0000
@@ -1,5 +1,13 @@
 [
     {
+        "pk": 2, 
+        "model": "auth.group", 
+        "fields": {
+            "name": "Forum Moderators", 
+            "permissions": []
+        }
+    },
+    {
         "pk": 1, 
         "model": "forums.category", 
         "fields": {
@@ -47,7 +55,9 @@
             "description": "For general discussion about this site only, including news and rules. Start here. Anything relating to surf music should go to the Surf Music General Discussion forum, below.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 0, 
             "last_post": null, 
             "slug": "surfguitar101-website", 
@@ -62,7 +72,9 @@
             "description": "Main surf music discussion forum. Insert glissando sound here.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 0, 
             "last_post": null, 
             "slug": "surf-music", 
@@ -77,7 +89,9 @@
             "description": "For sale and trading of surf music related items only.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 0, 
             "last_post": null, 
             "slug": "for-sale-trade", 
@@ -92,7 +106,9 @@
             "description": "General off-topic chit-chat. Grab a cool drink and hop in. New members please introduce yourselves here. This forum is dedicated to the memory of Rip Thrillby and Spanky Twangler.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 0, 
             "last_post": null, 
             "slug": "shallow-end", 
@@ -107,7 +123,9 @@
             "description": "Need someone to play with? Starting a band? Need a gig? Post here.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 1, 
             "last_post": null, 
             "slug": "musicians-gigs-wanted", 
@@ -122,7 +140,9 @@
             "description": "Please post show announcements here.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 1, 
             "last_post": null, 
             "slug": "gigs", 
@@ -137,7 +157,9 @@
             "description": "Got an idea for the site? Something not working? Post here.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 1, 
             "last_post": null, 
             "slug": "suggestion-box", 
@@ -152,7 +174,9 @@
             "description": "Playing, performing, and writing surf music. All instruments welcome.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 2, 
             "last_post": null, 
             "slug": "surf-musician", 
@@ -167,7 +191,9 @@
             "description": "Feedback, suggestions, playlists, and discussions about the SurfGuitar101 podcast.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 2, 
             "last_post": null, 
             "slug": "sg101-podcast", 
@@ -182,7 +208,9 @@
             "description": "For questions and discussions about instruments, amplifiers, and yes, outboard reverb units!", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 3, 
             "last_post": null, 
             "slug": "gear", 
@@ -197,7 +225,9 @@
             "description": "For discussion of recording techniques.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 4, 
             "last_post": null, 
             "slug": "recording-corner", 
@@ -212,7 +242,9 @@
             "description": "Got a link to a surf or surf-related video? Post it here.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 5, 
             "last_post": null, 
             "slug": "surf-videos", 
@@ -227,11 +259,30 @@
             "description": "Please post your reviews of surf music releases here.", 
             "post_count": 0, 
             "topic_count": 0, 
-            "moderators": [], 
+            "moderators": [
+                2
+            ], 
             "position": 6, 
             "last_post": null, 
             "slug": "music-reviews", 
             "name": "Music Reviews"
         }
+    },
+    {
+        "pk": 16, 
+        "model": "forums.forum", 
+        "fields": {
+            "category": 2, 
+            "description": "This forum contains some classic and important threads from our history, preserved here for historical reasons! These threads are still live, so please keep posting to them.", 
+            "post_count": 0, 
+            "topic_count": 0, 
+            "moderators": [
+                2
+            ], 
+            "position": 7, 
+            "last_post": null, 
+            "slug": "best-sg101", 
+            "name": "Best-Of SG101"
+        }
     }
-]
\ No newline at end of file
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/forums/management/commands/sync_forums.py	Wed Jan 05 04:09:35 2011 +0000
@@ -0,0 +1,42 @@
+"""
+sync_forums.py - A management command to synchronize the forums by recomputing
+the de-normalized fields in the forum and topic objects.
+
+"""
+import optparse
+
+from django.core.management.base import NoArgsCommand, CommandError
+
+from forums.models import Forum
+from forums.models import Topic
+
+
+class Command(NoArgsCommand):
+    help = """\
+This command synchronizes the forum application's forums and topic objects
+by updating their de-normalized fields.
+"""
+    option_list = NoArgsCommand.option_list + (
+        optparse.make_option("-p", "--progress", action="store_true",
+            help="Output a . after every 50 topics to show progress"),
+    )
+
+    def handle_noargs(self, **opts):
+
+        show_progress = opts.get('progress', False) or False
+
+        n = 0
+        for topic in Topic.objects.iterator():
+            topic.post_count_update()
+            topic.save()
+            n += 1
+            if n % 50 == 0:
+                self.stdout.write('.')
+                self.stdout.flush()
+
+        for forum in Forum.objects.all():
+            forum.sync()
+            forum.save()
+
+        self.stdout.write('\n')
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/legacy/management/commands/import_old_links.py	Wed Jan 05 04:09:35 2011 +0000
@@ -0,0 +1,83 @@
+"""
+import_old_links.py - For importing links from SG101 1.0 as csv files.
+"""
+import csv
+import datetime
+
+from django.core.management.base import LabelCommand, CommandError
+from django.contrib.auth.models import User
+
+from weblinks.models import Link, Category
+
+
+class Command(LabelCommand):
+    args = '<filename filename ...>'
+    help = 'Imports weblinks from the old database in CSV format'
+
+    def handle_label(self, filename, **options):
+        """
+        Process each line in the CSV file given by filename by
+        creating a new weblink object and saving it to the database.
+
+        """
+        self.cats = {}
+        try:
+            self.default_user = User.objects.get(pk=2)
+        except User.DoesNotExist:
+            raise CommandError("Need a default user with pk=1")
+
+        try:
+            with open(filename, "rb") as f:
+                self.reader = csv.DictReader(f)
+                try:
+                    for row in self.reader:
+                        self.process_row(row)
+                except csv.Error, e:
+                    raise CommandError("CSV error: %s %s %s" % (
+                        filename, self.reader.line_num, e))
+
+        except IOError:
+            raise CommandError("Could not open file: %s" % filename)
+
+    def get_category(self, row):
+        """
+        Return the Category object for the row.
+
+        """
+        cat_id = row['cid']
+        if cat_id not in self.cats:
+            try:
+                cat = Category.objects.get(pk=cat_id)
+            except Category.DoesNotExist:
+                raise CommandError("Category does not exist: %s on line %s" % (
+                    cat_id, self.reader.line_num))
+            else:
+                self.cats[cat_id] = cat
+        return self.cats[cat_id]
+
+    def get_user(self, username):
+        """
+        Return the user object for the given username.
+        If the user cannot be found, self.default_user is returned.
+
+        """
+        try:
+            return User.objects.get(username=username)
+        except User.DoesNotExist:
+            return self.default_user
+
+    def process_row(self, row):
+        """
+        Process one row from the CSV file: create an object for the row
+        and save it in the database.
+
+        """
+        link = Link(category=self.get_category(row),
+            title=row['title'].decode('latin-1'),
+            url=row['url'].decode('latin-1'),
+            description=row['description'].decode('latin-1'),
+            user=self.get_user(row['submitter']),
+            date_added=datetime.datetime.strptime(row['date'], "%Y-%m-%d %H:%M:%S"),
+            hits=int(row['hits']),
+            is_public=True)
+        link.save()
--- a/gpp/legacy/management/commands/import_old_news.py	Wed Dec 29 04:56:53 2010 +0000
+++ b/gpp/legacy/management/commands/import_old_news.py	Wed Jan 05 04:09:35 2011 +0000
@@ -86,11 +86,11 @@
             return
 
         story = Story(id=int(row['sid']),
-                title=unescape(row['title']),
+                title=unescape(row['title'].decode('latin-1')),
                 submitter=submitter,
                 category=self.topics[int(row['topic'])],
-                short_text=row['hometext'],
-                long_text=row['bodytext'],
+                short_text=row['hometext'].decode('latin-1'),
+                long_text=row['bodytext'].decode('latin-1'),
                 date_submitted=datetime.strptime(row['time'], "%Y-%m-%d %H:%M:%S"),
                 allow_comments=True)
 
--- a/gpp/legacy/management/commands/import_old_news_comments.py	Wed Dec 29 04:56:53 2010 +0000
+++ b/gpp/legacy/management/commands/import_old_news_comments.py	Wed Jan 05 04:09:35 2011 +0000
@@ -118,7 +118,7 @@
         self.md_writer.reset()
 
         if not isinstance(s, unicode):
-            s = s.decode('utf-8', 'replace')
+            s = s.decode('latin-1', 'replace')
 
         self.md_writer.feed(s)
         return self.md_writer.markdown()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/legacy/management/commands/import_old_podcasts.py	Wed Jan 05 04:09:35 2011 +0000
@@ -0,0 +1,62 @@
+"""
+import_old_podcasts.py - For importing podcasts from SG101 1.0 as csv files.
+"""
+import csv
+import datetime
+
+from django.core.management.base import LabelCommand, CommandError
+
+from podcast.models import Channel, Item
+
+
+class Command(LabelCommand):
+    args = '<filename filename ...>'
+    help = 'Imports podcasts from the old database in CSV format'
+
+    def handle_label(self, filename, **options):
+        """
+        Process each line in the CSV file given by filename by
+        creating a new weblink object and saving it to the database.
+
+        """
+        try:
+            self.channel = Channel.objects.get(pk=1)
+        except Channel.DoesNotExist:
+            raise CommandError("Need a default channel with pk=1")
+
+        try:
+            with open(filename, "rb") as f:
+                self.reader = csv.DictReader(f)
+                try:
+                    for row in self.reader:
+                        self.process_row(row)
+                except csv.Error, e:
+                    raise CommandError("CSV error: %s %s %s" % (
+                        filename, self.reader.line_num, e))
+
+        except IOError:
+            raise CommandError("Could not open file: %s" % filename)
+
+    def process_row(self, row):
+        """
+        Process one row from the CSV file: create an object for the row
+        and save it in the database.
+
+        """
+        item = Item(channel=self.channel,
+            title=row['title'],
+            author=row['author'],
+            subtitle=row['subtitle'],
+            summary=row['summary'],
+            enclosure_url=row['enclosure_url'],
+            alt_enclosure_url='',
+            enclosure_length=int(row['enclosure_length']),
+            enclosure_type=row['enclosure_type'],
+            guid=row['guid'],
+            pubdate=datetime.datetime.strptime(row['pubdate'],
+                "%Y-%m-%d %H:%M:%S"),
+            duration=row['duration'],
+            keywords=row['keywords'],
+            explicit=row['explicit'])
+
+        item.save()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/legacy/management/commands/import_old_topics.py	Wed Jan 05 04:09:35 2011 +0000
@@ -0,0 +1,117 @@
+"""
+import_old_topics.py - For importing forum topics (threads) from SG101 1.0 as
+csv files.
+
+"""
+from __future__ import with_statement
+import csv
+import optparse
+import sys
+from datetime import datetime
+
+from django.core.management.base import LabelCommand, CommandError
+from django.contrib.auth.models import User
+
+from forums.models import Forum, Topic
+from legacy.phpbb import unescape
+
+
+class Command(LabelCommand):
+    args = '<filename filename ...>'
+    help = 'Imports forum topics from the old database in CSV format'
+    option_list = LabelCommand.option_list + (
+        optparse.make_option("-p", "--progress", action="store_true",
+            help="Output a . after every 20 topics to show progress"),
+    )
+
+    def handle_label(self, filename, **options):
+        """
+        Process each line in the CSV file given by filename by
+        creating a new topic.
+
+        """
+        self.show_progress = options.get('progress')
+        self.users = {}
+
+        # Create a mapping from the old database's forums to our
+        # forums
+        self.forums = {}
+        try:
+            self.forums[2] = Forum.objects.get(slug='suggestion-box')
+            self.forums[3] = Forum.objects.get(slug='surf-music')
+            self.forums[4] = Forum.objects.get(slug='surf-musician')
+            self.forums[5] = Forum.objects.get(slug='gear')
+            self.forums[6] = Forum.objects.get(slug='recording-corner')
+            self.forums[7] = Forum.objects.get(slug='shallow-end')
+            self.forums[8] = Forum.objects.get(slug='surfguitar101-website')
+            self.forums[9] = Forum.objects.get(id=15)
+            self.forums[10] = Forum.objects.get(slug='for-sale-trade')
+            self.forums[11] = Forum.objects.get(slug='musicians-gigs-wanted')
+            self.forums[12] = Forum.objects.get(slug='surf-videos')
+            self.forums[13] = Forum.objects.get(slug='sg101-podcast')
+            self.forums[14] = Forum.objects.get(slug='gigs')
+            self.forums[15] = Forum.objects.get(slug='music-reviews')
+            self.forums[18] = Forum.objects.get(slug='best-sg101')
+        except Forum.DoesNotExist:
+            sys.exit("Forum does not exist; check forum mapping.")
+
+        try:
+            with open(filename, "rb") as f:
+                self.reader = csv.DictReader(f)
+                num_rows = 0
+                try:
+                    for row in self.reader:
+                        self.process_row(row)
+                        num_rows += 1
+                        if self.show_progress and num_rows % 20 == 0:
+                            sys.stdout.write('.')
+                            sys.stdout.flush()
+                except csv.Error, e:
+                    raise CommandError("CSV error: %s %s %s" % (
+                        filename, self.reader.line_num, e))
+
+                print
+
+        except IOError:
+            raise CommandError("Could not open file: %s" % filename)
+
+    def process_row(self, row):
+        """
+        Process one row from the CSV file: create a Story object for
+        the row and save it in the database.
+
+        """
+        row = dict((k, v if v != 'NULL' else '') for k, v in row.iteritems())
+
+        if row['topic_moved_id'] != '0':
+            return
+
+        try:
+            user = User.objects.get(id=int(row['topic_poster']))
+        except User.DoesNotExist:
+            print "Could not find user %s for topic %s; skipping." % (
+                    row['topic_poster'], row['topic_id'])
+            return
+
+        creation_date = datetime.fromtimestamp(float(row['topic_time']))
+
+        title = row['topic_title'].decode('latin-1', 'replace')
+
+        try:
+            forum = self.forums[int(row['forum_id'])]
+        except KeyError:
+            print 'skipping topic "%s"' % title
+            return
+
+        topic = Topic(id=int(row['topic_id']),
+                forum=forum,
+                name=unescape(title),
+                creation_date=creation_date,
+                user=user,
+                view_count=int(row['topic_views']),
+                sticky=(int(row['topic_type']) != 0),
+                locked=(int(row['topic_status']) != 0),
+                update_date=creation_date)
+
+        topic.save()
+
--- a/gpp/legacy/management/commands/import_old_users.py	Wed Dec 29 04:56:53 2010 +0000
+++ b/gpp/legacy/management/commands/import_old_users.py	Wed Jan 05 04:09:35 2011 +0000
@@ -140,9 +140,9 @@
         u.save()
 
         p = u.get_profile()
-        p.location = row['user_from']
-        p.occupation = row['user_occ']
-        p.interests = row['user_interests']
+        p.location = row['user_from'].decode('latin-1')
+        p.occupation = row['user_occ'].decode('latin-1')
+        p.interests = row['user_interests'].decode('latin-1')
         p.profile_text = u''
         p.hide_email = True if row['user_viewemail'] != '1' else False
         p.signature = self.to_markdown(row['user_sig']) if row['user_sig'] else u''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/legacy/management/commands/translate_old_posts.py	Wed Jan 05 04:09:35 2011 +0000
@@ -0,0 +1,134 @@
+"""
+translate_old_posts.py - A management command to join the bbposts and 
+bbposts_text tables together and output as a .csv file, suitable for use as an
+input to mysqlimport into the new database. This method bypasses the Django ORM
+as it was too slow given the number of old posts to import.
+
+"""
+from __future__ import with_statement
+import csv
+import optparse
+from datetime import datetime
+
+import MySQLdb
+import postmarkup
+
+from django.core.management.base import NoArgsCommand, CommandError
+
+from legacy.phpbb import unphpbb
+from legacy.html2md import MarkdownWriter
+from core.markup import SiteMarkup
+
+
+def convert_ip(s):
+    """
+    Converts a hex string representing an IP address into dotted notation.
+    """
+    n = int(s, 16)
+    return "%d.%d.%d.%d" % (
+            ((n >> 24) & 0xff),
+            ((n >> 16) & 0xff),
+            ((n >> 8) & 0xff),
+            n & 0xff)
+
+
+class Command(NoArgsCommand):
+    help = """\
+This command joins converts the SG101 1.0 posts to 2.0 format and outputs the
+data as a .csv file suitable for importing into the new database scheme with
+the mysqlimport utility.
+"""
+    option_list = NoArgsCommand.option_list + (
+        optparse.make_option("-s", "--progress", action="store_true",
+            help="Output a . after every 100 posts to show progress"),
+        optparse.make_option("-a", "--host", help="set MySQL host name"),
+        optparse.make_option("-u", "--user", help="set MySQL user name"),
+        optparse.make_option("-p", "--password", help="set MySQL user password"),
+        optparse.make_option("-d", "--database", help="set MySQL database name"),
+        optparse.make_option("-o", "--out-file", help="set output filename"),
+    )
+    bb_parser = postmarkup.create(use_pygments=False, annotate_links=False)
+    md_writer = MarkdownWriter()
+    site_markup = SiteMarkup()
+
+    def handle_noargs(self, **opts):
+
+        host = opts.get('host', 'localhost') or 'localhost'
+        user = opts.get('user', 'root') or 'root'
+        password = opts.get('password', '') or ''
+        database = opts.get('database')
+        out_filename = opts.get('out_file', 'forums_post.csv') or 'forums_post.csv'
+
+        if database is None:
+            raise CommandError("Please specify a database option")
+
+        out_file = open(out_filename, "wb")
+
+        # database columns (fieldnames) for the output CSV file:
+        cols = ('id', 'topic_id', 'user_id', 'creation_date', 'update_date',
+                'body', 'html', 'user_ip')
+        self.writer = csv.writer(out_file)
+
+        # Write an initial row of fieldnames to the output file 
+        self.writer.writerow(cols)
+
+        # connect to the legacy database
+        try:
+            db = MySQLdb.connect(host=host,
+                    user=user,
+                    passwd=password,
+                    db=database)
+        except MySQLdb.DatabaseError, e:
+            raise CommandError(str(e))
+
+        c = db.cursor(MySQLdb.cursors.DictCursor)
+
+        # query the legacy database
+        sql = ('SELECT * FROM sln_bbposts as p, sln_bbposts_text as t WHERE '
+                'p.post_id = t.post_id ORDER BY p.post_id')
+        c.execute(sql)
+
+        # convert the old data and write the output to the file
+        while True:
+            row = c.fetchone()
+            if row is None:
+                break
+
+            self.process_row(row)
+
+        c.close()
+        db.close()
+        out_file.close()
+
+    def to_html(self, s):
+        return self.bb_parser.render_to_html(unphpbb(s), cosmetic_replace=False)
+
+    def to_markdown(self, s):
+        self.md_writer.reset()
+        self.md_writer.feed(self.to_html(s))
+        return self.md_writer.markdown()
+
+    def process_row(self, row):
+        """
+        This function accepts one row from the legacy database and converts the
+        contents to the new database format, and calls the writer to write the new
+        row to the output file.
+        """
+        creation_date = datetime.fromtimestamp(float(row['post_time']))
+
+        if row['post_edit_time']:
+            update_date = datetime.fromtimestamp(float(row['post_edit_time'])) 
+        else:
+            update_date = creation_date
+
+        body = self.to_markdown(row['post_text'])
+        html = self.site_markup.convert(body)
+
+        self.writer.writerow([row['post_id'],
+                row['topic_id'],
+                row['poster_id'],
+                creation_date,
+                update_date,
+                body.encode("utf-8"),
+                html.encode("utf-8"),
+                convert_ip(row['poster_ip'])])
--- a/gpp/legacy/phpbb.py	Wed Dec 29 04:56:53 2010 +0000
+++ b/gpp/legacy/phpbb.py	Wed Jan 05 04:09:35 2011 +0000
@@ -48,7 +48,7 @@
     return re.sub("&#?\w+;", fixup, text)
 
 
-def unphpbb(s):
+def unphpbb(s, encoding='latin-1'):
     """Converts BBCode from phpBB database data into 'pure' BBCode.
 
     phpBB doesn't store plain BBCode in its database. The BBCode tags have
@@ -56,9 +56,12 @@
     This function removes the uid stuff and undoes the entity'ification and
     returns the result as a unicode string.
 
+    If the input 's' is not already unicode, it will be decoded using the
+    supplied encoding.
+
     """
     if not isinstance(s, unicode):
-        s = s.decode('utf-8', 'replace')
+        s = s.decode(encoding, 'replace')
     for start, end in BBCODE_RES:
         s = re.sub(start, r'\1', s, re.MULTILINE)
         s = re.sub(end, r'\1]', s, re.MULTILINE)
--- a/gpp/podcast/management/commands/import_old_podcasts.py	Wed Dec 29 04:56:53 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-"""
-import_old_podcasts.py - For importing podcasts from SG101 1.0 as csv files.
-"""
-import csv
-import datetime
-
-from django.core.management.base import LabelCommand, CommandError
-
-from podcast.models import Channel, Item
-
-
-class Command(LabelCommand):
-    args = '<filename filename ...>'
-    help = 'Imports podcasts from the old database in CSV format'
-
-    def handle_label(self, filename, **options):
-        """
-        Process each line in the CSV file given by filename by
-        creating a new weblink object and saving it to the database.
-
-        """
-        try:
-            self.channel = Channel.objects.get(pk=1)
-        except Channel.DoesNotExist:
-            raise CommandError("Need a default channel with pk=1")
-
-        try:
-            with open(filename, "rb") as f:
-                self.reader = csv.DictReader(f)
-                try:
-                    for row in self.reader:
-                        self.process_row(row)
-                except csv.Error, e:
-                    raise CommandError("CSV error: %s %s %s" % (
-                        filename, self.reader.line_num, e))
-
-        except IOError:
-            raise CommandError("Could not open file: %s" % filename)
-
-    def process_row(self, row):
-        """
-        Process one row from the CSV file: create an object for the row
-        and save it in the database.
-
-        """
-        item = Item(channel=self.channel,
-            title=row['title'],
-            author=row['author'],
-            subtitle=row['subtitle'],
-            summary=row['summary'],
-            enclosure_url=row['enclosure_url'],
-            alt_enclosure_url='',
-            enclosure_length=int(row['enclosure_length']),
-            enclosure_type=row['enclosure_type'],
-            guid=row['guid'],
-            pubdate=datetime.datetime.strptime(row['pubdate'],
-                "%Y-%m-%d %H:%M:%S"),
-            duration=row['duration'],
-            keywords=row['keywords'],
-            explicit=row['explicit'])
-
-        item.save()
--- a/gpp/weblinks/management/commands/import_old_links.py	Wed Dec 29 04:56:53 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-"""
-import_old_links.py - For importing links from SG101 1.0 as csv files.
-"""
-import csv
-import datetime
-
-from django.core.management.base import LabelCommand, CommandError
-from django.contrib.auth.models import User
-
-from weblinks.models import Link, Category
-
-
-class Command(LabelCommand):
-    args = '<filename filename ...>'
-    help = 'Imports weblinks from the old database in CSV format'
-
-    def handle_label(self, filename, **options):
-        """
-        Process each line in the CSV file given by filename by
-        creating a new weblink object and saving it to the database.
-
-        """
-        self.cats = {}
-        try:
-            self.user = User.objects.get(pk=1)
-        except User.DoesNotExist:
-            raise CommandError("Need a default user with pk=1")
-
-        try:
-            with open(filename, "rb") as f:
-                self.reader = csv.DictReader(f)
-                try:
-                    for row in self.reader:
-                        self.process_row(row)
-                except csv.Error, e:
-                    raise CommandError("CSV error: %s %s %s" % (
-                        filename, self.reader.line_num, e))
-
-        except IOError:
-            raise CommandError("Could not open file: %s" % filename)
-
-    def get_category(self, row):
-        """
-        Return the Category object for the row.
-
-        """
-        cat_id = row['cid']
-        if cat_id not in self.cats:
-            try:
-                cat = Category.objects.get(pk=cat_id)
-            except Category.DoesNotExist:
-                raise CommandError("Category does not exist: %s on line %s" % (
-                    cat_id, self.reader.line_num))
-            else:
-                self.cats[cat_id] = cat
-        return self.cats[cat_id]
-
-    def process_row(self, row):
-        """
-        Process one row from the CSV file: create an object for the row
-        and save it in the database.
-
-        """
-        link = Link(category=self.get_category(row),
-            title=row['title'],
-            url=row['url'],
-            description=row['description'],
-            user=self.user,
-            date_added=datetime.datetime.strptime(row['date'], "%Y-%m-%d %H:%M:%S"),
-            hits=int(row['hits']),
-            is_public=True)
-        link.save()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/load_fixtures.bash	Wed Jan 05 04:09:35 2011 +0000
@@ -0,0 +1,2 @@
+#!/bin/bash
+python manage.py loaddata ./forums/fixtures/private.json ./forums/fixtures/forums.json ./news/fixtures/news_categories.json ./podcast/fixtures/channels.json ./core/fixtures/flatpages.json ./smiley/fixtures/smilies.json ./downloads/fixtures/downloads_extensions.json ./downloads/fixtures/downloads_categories.json ./bio/fixtures/badges.json ./oembed/fixtures/providers.json ./weblinks/fixtures/weblinks_categories.json ./accounts/fixtures/accounts.json