<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -227,9 +227,6 @@ class DrupalConverter(object):
 
     def get_html(self, raw_body, markup_type):
         &quot;&quot;&quot; Convert various Drupal formats to html &quot;&quot;&quot;
-
-        body = clean_multiline(raw_body)
-
         def repl(tmatch):
             if tmatch:
                 return textile.textile(tmatch.group(1))
@@ -239,10 +236,12 @@ class DrupalConverter(object):
         if markup_type == 'textile':
             pattern = re.compile('\[textile\](.*)\[/textile\]', 
                                  re.MULTILINE | re.IGNORECASE | re.DOTALL)
-            return re.sub(pattern, repl, body)
-        if markup_type == 'filtered html':
-            return re.sub('\n', '&lt;br /&gt;', body)
-        return body
+            body = re.sub(pattern, repl, raw_body)
+        elif markup_type == 'filtered html':
+            body = re.sub('\n', '&lt;br /&gt;', raw_body)
+        else:
+            body = raw_body
+        return clean_multiline(body)
 
     def go(self, num_articles=None):
         # Get all the term (tag) data and the hierarchy pattern</diff>
      <filename>dev/scripts/drupal_uploader.py</filename>
    </modified>
    <modified>
      <diff>@@ -88,14 +88,13 @@ def get_format(format_string):
         format_string = 'html'
     return format_string
 
-def get_tag_key(tag_string):
-    obj = models.blog.Tag.get_or_insert(tag_string)
+def get_tag_key(tag_name):
+    obj = models.blog.Tag.get_or_insert(tag_name)
     return obj.key()
 
 def get_tags(tags_string):
     if tags_string:
-        return [get_tag_key(s.strip()) for s in tags_string.split(&quot;,&quot;) 
-                if s != '']
+        return [s.strip() for s in tags_string.split(&quot;,&quot;) if s != '']
     return None
     
 def get_friendly_url(title):
@@ -146,9 +145,13 @@ def process_article_edit(handler, permalink):
          ('html', get_html, 'body', 'format')])
 
     if property_hash:
+        if 'tags' in property_hash:
+            property_hash['tag_keys'] = [get_tag_key(name) 
+                                         for name in property_hash['tags']]
         article = db.Query(models.blog.Article).filter('permalink =', permalink).get()
         before_tags = set(article.tags)
         for key,value in property_hash.iteritems():
+            logging.debug(&quot;  Setting %s&quot;, key)
             setattr(article, key, value)
         after_tags = set(article.tags)
         for removed_tag in before_tags - after_tags:
@@ -175,6 +178,9 @@ def process_article_submission(handler, article_type):
          ('permalink', permalink_funcs[article_type], 'title', 'published')])
 
     if property_hash:
+        if 'tags' in property_hash:
+            property_hash['tag_keys'] = [get_tag_key(name) 
+                                         for name in property_hash['tags']]
         property_hash['format'] = 'html'   # For now, convert all to HTML
         property_hash['article_type'] = article_type
         article = models.blog.Article(**property_hash)
@@ -183,8 +189,8 @@ def process_article_submission(handler, article_type):
              'amazon_items': handler.request.get('amazon_items')})
         process_embedded_code(article)
         article.put()
-        for tag in article.tags:
-            db.get(tag).counter.increment()
+        for key in article.tag_keys:
+            db.get(key).counter.increment()
         restful.send_successful_response(handler, '/' + article.permalink)
         view.invalidate_cache()
     else:
@@ -260,24 +266,32 @@ def process_comment_submission(handler, article):
 
 def render_article(handler, article):
     if article:
-        # Generate two parts of a captcha that will use
-        # display:none in between.  This step in the anti-spam
-        # war race due to the following article:
-        # http://techblog.tilllate.com/2008/07/20/ten-methods-to-obfuscate-e-mail-addresses-compared/
-        captcha = get_captcha(article.key())
-        two_columns = article.two_columns
-        if two_columns is None:
-            two_columns = article.is_big()
-        allow_comments = article.allow_comments
-        if allow_comments is None:
-            age = (datetime.datetime.now() - article.published).days
-            allow_comments = (age &lt;= config.BLOG['days_can_comment'])
-        page = view.ViewPage()
-        page.render(handler, { &quot;two_columns&quot;: two_columns,
-                               &quot;allow_comments&quot;: allow_comments,
-                               &quot;article&quot;: article,
-                               &quot;captcha1&quot;: captcha[:3],
-                               &quot;captcha2&quot;: captcha[3:6] })
+        # Check if client is requesting javascript and
+        # return json if javascript is #1 in Accept header.
+        logging.debug(&quot;article Accept: %s&quot;, handler.request.headers['Accept'])
+        accept_list = handler.request.headers['Accept']
+        if accept_list and accept_list.split(',')[0] == 'application/json':
+            handler.response.headers['Content-Type'] = 'application/json'
+            handler.response.out.write(article.to_json())
+        else:
+            # Generate two parts of a captcha that will use
+            # display:none in between.  This step in the anti-spam
+            # war race due to the following article:
+            # http://techblog.tilllate.com/2008/07/20/ten-methods-to-obfuscate-e-mail-addresses-compared/
+            captcha = get_captcha(article.key())
+            two_columns = article.two_columns
+            if two_columns is None:
+                two_columns = article.is_big()
+            allow_comments = article.allow_comments
+            if allow_comments is None:
+                age = (datetime.datetime.now() - article.published).days
+                allow_comments = (age &lt;= config.BLOG['days_can_comment'])
+            page = view.ViewPage()
+            page.render(handler, { &quot;two_columns&quot;: two_columns,
+                                   &quot;allow_comments&quot;: allow_comments,
+                                   &quot;article&quot;: article,
+                                   &quot;captcha1&quot;: captcha[:3],
+                                   &quot;captcha2&quot;: captcha[3:6] })
     else:
         # This didn't fall into any of our pages or aliases.
         # Page not found.
@@ -419,8 +433,8 @@ class BlogEntryHandler(restful.Controller):
         logging.debug(&quot;Deleting blog entry %s&quot;, permalink)
         article = db.Query(models.blog.Article). \
                      filter('permalink =', permalink).get()
-        for tag in article.tags:
-            db.get(tag).counter.decrement()
+        for key in article.tag_keys:
+            db.get(key).counter.decrement()
         article.delete()
         view.invalidate_cache()
         restful.send_successful_response(self, &quot;/&quot;)
@@ -430,13 +444,12 @@ class TagHandler(restful.Controller):
         tag =  re.sub('(%25|%)(\d\d)', 
                       lambda cmatch: chr(string.atoi(cmatch.group(2), 16)),                 
                       encoded_tag)   # No urllib.unquote in AppEngine?
-        tag_key = db.Key.from_path('Tag', tag)
         page = view.ViewPage()
         page.render_query(
             self, 'articles', 
             db.Query(models.blog.Article).filter('tags =',        
-                                           tag_key).order('-published'), 
-            {'tag': tag})
+                                                 tag).order('-published'), 
+                                                {'tag': tag})
 
 class SearchHandler(restful.Controller):
     def get(self):</diff>
      <filename>handlers/bloog/blog.py</filename>
    </modified>
    <modified>
      <diff>@@ -182,89 +182,89 @@ class MemcachedModel(SerializableModel):
             memcache.set(cls.memcache_key(), list_repr)
         return eval(list_repr)
 
-        class Counter(object):
-            &quot;&quot;&quot;A counter using sharded writes to prevent contentions.
+class Counter(object):
+    &quot;&quot;&quot;A counter using sharded writes to prevent contentions.
 
-            Should be used for counters that handle a lot of concurrent use.
-            Follows pattern described in Google I/O talk:
-                http://sites.google.com/site/io/building-scalable-web-applications-with-google-app-engine
+    Should be used for counters that handle a lot of concurrent use.
+    Follows pattern described in Google I/O talk:
+        http://sites.google.com/site/io/building-scalable-web-applications-with-google-app-engine
 
-            Memcache is used for caching counts, although you can force
-            non-cached counts.
+    Memcache is used for caching counts, although you can force
+    non-cached counts.
 
-            Usage:
-                hits = Counter('hits')
-                hits.increment()
-                hits.get_count()
-                hits.get_count(nocache=True)  # Forces non-cached count.
-                hits.decrement()
-            &quot;&quot;&quot;
-            MAX_SHARDS = 50
+    Usage:
+        hits = Counter('hits')
+        hits.increment()
+        hits.get_count()
+        hits.get_count(nocache=True)  # Forces non-cached count.
+        hits.decrement()
+    &quot;&quot;&quot;
+    MAX_SHARDS = 50
 
-            def __init__(self, name, num_shards=5, cache_time=30):
-                self.name = name
-                self.num_shards = min(num_shards, Counter.MAX_SHARDS)
-                self.cache_time = cache_time
+    def __init__(self, name, num_shards=5, cache_time=30):
+        self.name = name
+        self.num_shards = min(num_shards, Counter.MAX_SHARDS)
+        self.cache_time = cache_time
 
-            def delete(self):
-                q = db.Query(CounterShard).filter('name =', self.name)
-                # Need to use MAX_SHARDS since current number of shards
-                # may be smaller than previous value.
-                shards = q.fetch(limit=Counter.MAX_SHARDS)
-                for shard in shards:
-                    shard.delete()
+    def delete(self):
+        q = db.Query(CounterShard).filter('name =', self.name)
+        # Need to use MAX_SHARDS since current number of shards
+        # may be smaller than previous value.
+        shards = q.fetch(limit=Counter.MAX_SHARDS)
+        for shard in shards:
+            shard.delete()
 
-            def memcache_key(self):
-                return 'Counter' + self.name
+    def memcache_key(self):
+        return 'Counter' + self.name
 
-            def get_count(self, nocache=False):
-                total = memcache.get(self.memcache_key())
-                if nocache or total is None:
-                    total = 0
-                    q = db.Query(CounterShard).filter('name =', self.name)  
-                    shards = q.fetch(limit=Counter.MAX_SHARDS)
-                    for shard in shards:
-                        total += shard.count
-                    memcache.add(self.memcache_key(), str(total), 
-                                 self.cache_time)
-                    return total
-                else:
-                    logging.debug(&quot;Using cache on %s = %s&quot;, self.name, total)
-                    return int(total)
-            count = property(get_count)
+    def get_count(self, nocache=False):
+        total = memcache.get(self.memcache_key())
+        if nocache or total is None:
+            total = 0
+            q = db.Query(CounterShard).filter('name =', self.name)  
+            shards = q.fetch(limit=Counter.MAX_SHARDS)
+            for shard in shards:
+                total += shard.count
+            memcache.add(self.memcache_key(), str(total), 
+                         self.cache_time)
+            return total
+        else:
+            logging.debug(&quot;Using cache on %s = %s&quot;, self.name, total)
+            return int(total)
+    count = property(get_count)
 
-            def increment(self):
-                CounterShard.increment(self.name, self.num_shards)
-                return memcache.incr(self.memcache_key()) 
+    def increment(self):
+        CounterShard.increment(self.name, self.num_shards)
+        return memcache.incr(self.memcache_key()) 
 
-            def decrement(self):
-                CounterShard.increment(self.name, self.num_shards, 
-                                       downward=True)
-                return memcache.decr(self.memcache_key()) 
+    def decrement(self):
+        CounterShard.increment(self.name, self.num_shards, 
+                               downward=True)
+        return memcache.decr(self.memcache_key()) 
 
-        class CounterShard(db.Model):
-            name = db.StringProperty(required=True)
-            count = db.IntegerProperty(default=0)
+class CounterShard(db.Model):
+    name = db.StringProperty(required=True)
+    count = db.IntegerProperty(default=0)
 
-            @classmethod
-            def increment(cls, name, num_shards, downward=False):
-                index = random.randint(1, num_shards)
-                shard_key_name = 'Shard' + name + str(index)
-                def get_or_create_shard():
-                    shard = CounterShard.get_by_key_name(shard_key_name)
-                    if shard is None:
-                        shard = CounterShard(key_name=shard_key_name, 
-                                             name=name)
-                    if downward:
-                        shard.count -= 1
-                    else:
-                        shard.count += 1
-                    key = shard.put()
-                try:
-                    db.run_in_transaction(get_or_create_shard)
-                    return True
-                except db.TransactionFailedError():
-                    logging.error(&quot;CounterShard (%s, %d) - can't increment&quot;, 
-                                  name, num_shards)
-                    return False
+    @classmethod
+    def increment(cls, name, num_shards, downward=False):
+        index = random.randint(1, num_shards)
+        shard_key_name = 'Shard' + name + str(index)
+        def get_or_create_shard():
+            shard = CounterShard.get_by_key_name(shard_key_name)
+            if shard is None:
+                shard = CounterShard(key_name=shard_key_name, 
+                                     name=name)
+            if downward:
+                shard.count -= 1
+            else:
+                shard.count += 1
+            key = shard.put()
+        try:
+            db.run_in_transaction(get_or_create_shard)
+            return True
+        except db.TransactionFailedError():
+            logging.error(&quot;CounterShard (%s, %d) - can't increment&quot;, 
+                          name, num_shards)
+            return False
 </diff>
      <filename>models/__init__.py</filename>
    </modified>
    <modified>
      <diff>@@ -70,7 +70,8 @@ class Article(search.SearchableModel):
     # To prevent full query when just showing article headlines
     num_comments = db.IntegerProperty(default=0)
     # Use keys instead of db.Category for consolidation of tag names
-    tags = db.ListProperty(db.Key)
+    tags = db.StringListProperty(default=[])
+    tag_keys = db.ListProperty(db.Key, default=[])
     two_columns = db.BooleanProperty()
     allow_comments = db.BooleanProperty()
     # A list of languages for code embedded in article.</diff>
      <filename>models/blog.py</filename>
    </modified>
    <modified>
      <diff>@@ -312,7 +312,7 @@ class SearchableModel(models.SerializableModel):
     &quot;&quot;&quot;Wraps db.Model._populate_internal_entity() and injects
     SearchableEntity.&quot;&quot;&quot;
     entity = db.Model._populate_internal_entity(self,
-                                                _entity_class=SearchableEntity)
+                                            _entity_class=SearchableEntity)
     entity.unsearchable_properties = self.__class__.unsearchable_properties
     return entity
 </diff>
      <filename>models/search.py</filename>
    </modified>
    <modified>
      <diff>@@ -47,17 +47,25 @@ YAHOO.bloog.initAdmin = function() {
                 hdr.setContent('Submit Edit');
                 YAHOO.bloog.http.action = '?_method=PUT';
                 YAHOO.bloog.http.verb = 'POST';
-                // Parse the current article HTML into title, tags, and body.
-                var blog_title = document.getElementById(&quot;blogtitle&quot;).innerHTML;
-                var blog_body =  document.getElementById(&quot;blogbody&quot;).innerHTML;
-                document.getElementById(&quot;postTitle&quot;).value = blog_title;
-                YAHOO.bloog.editor.setEditorHTML(blog_body);
+                // Get the current article content and populate the dialog
+                YAHOO.util.Connect.initHeader('Accept', 'application/json');
+                YAHOO.util.Connect.asyncRequest('GET', '#', {
+                    success: YAHOO.bloog.populateDialog,
+                    failure: YAHOO.bloog.handleFailure
+                }, null);
                 break;
         }
         YAHOO.bloog.postDialog.render();
         YAHOO.bloog.postDialog.show();
     }
 
+    YAHOO.bloog.populateDialog = function(o) {
+        var article = eval('(' + o.responseText + ')')
+        document.getElementById(&quot;postTitle&quot;).value = article.title;
+        document.getElementById(&quot;postTags&quot;).value = article.tags.join(', ');
+        YAHOO.bloog.editor.setEditorHTML(article.body);
+    }
+
     var handleSubmit = function() {
         YAHOO.bloog.editor.saveHTML();
         var html = YAHOO.bloog.editor.get('element').value;</diff>
      <filename>static/default/js/bloog_admin.js</filename>
    </modified>
    <modified>
      <diff>@@ -30,7 +30,7 @@
     &lt;div id=&quot;more_reading&quot;&gt;
         &lt;p class=&quot;tags&quot;&gt;Category: 
             {% for tag in article.tags %}
-                &lt;a href=&quot;/tag/{{ tag.name|urlencode }}&quot;&gt;{{ tag.name }}&lt;/a&gt;
+                &lt;a href=&quot;/tag/{{ tag|urlencode }}&quot;&gt;{{ tag }}&lt;/a&gt;
             {% endfor %}
         &lt;/p&gt;
     &lt;/div&gt;</diff>
      <filename>views/default/bloog/blog/article.html</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>22e92f4d15d4c8e31adb3d5c709e569810bf61c4</id>
    </parent>
  </parents>
  <author>
    <name>Bill Katz</name>
    <email>billkatz@gmail.com</email>
  </author>
  <url>http://github.com/DocSavage/bloog/commit/16a90c4b9f499826146b68ec014ac5ddf37c10ee</url>
  <id>16a90c4b9f499826146b68ec014ac5ddf37c10ee</id>
  <committed-date>2008-08-21T03:09:33-07:00</committed-date>
  <authored-date>2008-08-21T03:09:33-07:00</authored-date>
  <message>Now using json serialization to populate RTE.  Still has error dealing with chars not in ASCII range(128).</message>
  <tree>08828b181a7255d605be405d7eaf043df9347434</tree>
  <committer>
    <name>Bill Katz</name>
    <email>billkatz@gmail.com</email>
  </committer>
</commit>
