Taggable - a portable mixin class for adding Tags to Google AppEngine Models

Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association

    <!--{{{-->
    <link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
    <!--}}}-->

    Background: #fff
    Foreground: #000
    PrimaryPale: #8cf
    PrimaryLight: #18f
    PrimaryMid: #04b
    PrimaryDark: #014
    SecondaryPale: #ffc
    SecondaryLight: #fe8
    SecondaryMid: #db4
    SecondaryDark: #841
    TertiaryPale: #eee
    TertiaryLight: #ccc
    TertiaryMid: #999
    TertiaryDark: #666
    Error: #f88

    /*{{{*/
    body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

    a {color:[[ColorPalette::PrimaryMid]];}
    a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
    a img {border:0;}

    h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
    h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
    h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

    .button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
    .button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
    .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

    .header {background:[[ColorPalette::PrimaryMid]];}
    .headerShadow {color:[[ColorPalette::Foreground]];}
    .headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
    .headerForeground {color:[[ColorPalette::Background]];}
    .headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

    .tabSelected{color:[[ColorPalette::PrimaryDark]];
    	background:[[ColorPalette::TertiaryPale]];
    	border-left:1px solid [[ColorPalette::TertiaryLight]];
    	border-top:1px solid [[ColorPalette::TertiaryLight]];
    	border-right:1px solid [[ColorPalette::TertiaryLight]];
    }
    .tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
    .tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
    .tabContents .button {border:0;}

    #sidebar {}
    #sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
    #sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
    #sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
    #sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
    #sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

    .wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
    .wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
    .wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
    .wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
    	border:1px solid [[ColorPalette::PrimaryMid]];}
    .wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
    .wizardFooter {background:[[ColorPalette::PrimaryPale]];}
    .wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
    .wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
    	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
    .wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
    .wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
    	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}

    #messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
    #messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

    .popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

    .popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
    .popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
    .popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
    .popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
    .popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
    .popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
    .popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
    .listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

    .tiddler .defaultCommand {font-weight:bold;}

    .shadow .title {color:[[ColorPalette::TertiaryDark]];}

    .title {color:[[ColorPalette::SecondaryDark]];}
    .subtitle {color:[[ColorPalette::TertiaryDark]];}

    .toolbar {color:[[ColorPalette::PrimaryMid]];}
    .toolbar a {color:[[ColorPalette::TertiaryLight]];}
    .selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
    .selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

    .tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
    .selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
    .tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
    .tagging .button, .tagged .button {border:none;}

    .footer {color:[[ColorPalette::TertiaryLight]];}
    .selected .footer {color:[[ColorPalette::TertiaryMid]];}

    .sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
    .sparktick {background:[[ColorPalette::PrimaryDark]];}

    .error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
    .warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
    .lowlight {background:[[ColorPalette::TertiaryLight]];}

    .zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

    .imageLink, #displayArea .imageLink {background:transparent;}

    .annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

    .viewer .listTitle {list-style-type:none; margin-left:-2em;}
    .viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
    .viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

    .viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
    .viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
    .viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

    .viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
    .viewer code {color:[[ColorPalette::SecondaryDark]];}
    .viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

    .highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

    .editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
    .editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
    .editorFooter {color:[[ColorPalette::TertiaryMid]];}

    #backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
    #backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
    #backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
    #backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
    #backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
    #backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
    #backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
    .backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
    .backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
    #backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
    /*}}}*/

    /*{{{*/
    * html .tiddler {height:1%;}

    body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

    h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
    h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
    h4,h5,h6 {margin-top:1em;}
    h1 {font-size:1.35em;}
    h2 {font-size:1.25em;}
    h3 {font-size:1.1em;}
    h4 {font-size:1em;}
    h5 {font-size:.9em;}

    hr {height:1px;}

    a {text-decoration:none;}

    dt {font-weight:bold;}

    ol {list-style-type:decimal;}
    ol ol {list-style-type:lower-alpha;}
    ol ol ol {list-style-type:lower-roman;}
    ol ol ol ol {list-style-type:decimal;}
    ol ol ol ol ol {list-style-type:lower-alpha;}
    ol ol ol ol ol ol {list-style-type:lower-roman;}
    ol ol ol ol ol ol ol {list-style-type:decimal;}

    .txtOptionInput {width:11em;}

    #contentWrapper .chkOptionInput {border:0;}

    .externalLink {text-decoration:underline;}

    .indent {margin-left:3em;}
    .outdent {margin-left:3em; text-indent:-3em;}
    code.escaped {white-space:nowrap;}

    .tiddlyLinkExisting {font-weight:bold;}
    .tiddlyLinkNonExisting {font-style:italic;}

    /* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
    a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

    #mainMenu .tiddlyLinkExisting,
    	#mainMenu .tiddlyLinkNonExisting,
    	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
    #sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

    .header {position:relative;}
    .header a:hover {background:transparent;}
    .headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
    .headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}

    .siteTitle {font-size:3em;}
    .siteSubtitle {font-size:1.2em;}

    #mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

    #sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
    #sidebarOptions {padding-top:0.3em;}
    #sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
    #sidebarOptions input {margin:0.4em 0.5em;}
    #sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
    #sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
    #sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
    #sidebarTabs .tabContents {width:15em; overflow:hidden;}

    .wizard {padding:0.1em 1em 0em 2em;}
    .wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
    .wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
    .wizardStep {padding:1em 1em 1em 1em;}
    .wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
    .wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
    .wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
    .wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

    #messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
    .messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
    #messageArea a {text-decoration:underline;}

    .tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
    .popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

    .popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
    .popup .popupMessage {padding:0.4em;}
    .popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
    .popup li.disabled {padding:0.4em;}
    .popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
    .listBreak {font-size:1px; line-height:1px;}
    .listBreak div {margin:2px 0;}

    .tabset {padding:1em 0em 0em 0.5em;}
    .tab {margin:0em 0em 0em 0.25em; padding:2px;}
    .tabContents {padding:0.5em;}
    .tabContents ul, .tabContents ol {margin:0; padding:0;}
    .txtMainTab .tabContents li {list-style:none;}
    .tabContents li.listLink { margin-left:.75em;}

    #contentWrapper {display:block;}
    #splashScreen {display:none;}

    #displayArea {margin:1em 17em 0em 14em;}

    .toolbar {text-align:right; font-size:.9em;}

    .tiddler {padding:1em 1em 0em 1em;}

    .missing .viewer,.missing .title {font-style:italic;}

    .title {font-size:1.6em; font-weight:bold;}

    .missing .subtitle {display:none;}
    .subtitle {font-size:1.1em;}

    .tiddler .button {padding:0.2em 0.4em;}

    .tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
    .isTag .tagging {display:block;}
    .tagged {margin:0.5em; float:right;}
    .tagging, .tagged {font-size:0.9em; padding:0.25em;}
    .tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
    .tagClear {clear:both;}

    .footer {font-size:.9em;}
    .footer li {display:inline;}

    .annotation {padding:0.5em; margin:0.5em;}

    * html .viewer pre {width:99%; padding:0 0 1em 0;}
    .viewer {line-height:1.4em; padding-top:0.5em;}
    .viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
    .viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
    .viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

    .viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
    .viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
    table.listView {font-size:0.85em; margin:0.8em 1.0em;}
    table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

    .viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
    .viewer code {font-size:1.2em; line-height:1.4em;}

    .editor {font-size:1.1em;}
    .editor input, .editor textarea {display:block; width:100%; font:inherit;}
    .editorFooter {padding:0.25em 0em; font-size:.9em;}
    .editorFooter .button {padding-top:0px; padding-bottom:0px;}

    .fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

    .sparkline {line-height:1em;}
    .sparktick {outline:0;}

    .zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
    .zoomer div {padding:1em;}

    * html #backstage {width:99%;}
    * html #backstageArea {width:99%;}
    #backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
    #backstageToolbar {position:relative;}
    #backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
    #backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
    #backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
    #backstage {position:relative; width:100%; z-index:50;}
    #backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
    .backstagePanelFooter {padding-top:0.2em; float:right;}
    .backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
    #backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

    .whenBackstage {display:none;}
    .backstageVisible .whenBackstage {display:block;}
    /*}}}*/

    /***
    StyleSheet for use when a translation requires any css style changes.
    This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
    ***/
    /*{{{*/
    body {font-size:0.8em;}
    #sidebarOptions {font-size:1.05em;}
    #sidebarOptions a {font-style:normal;}
    #sidebarOptions .sliderPanel {font-size:0.95em;}
    .subtitle {font-size:0.8em;}
    .viewer table.listView {font-size:0.95em;}
    /*}}}*/

    /*{{{*/
    @media print {
    #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
    #displayArea {margin: 1em 1em 0em 1em;}
    /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
    noscript {display:none;}
    }
    /*}}}*/

    <!--{{{-->
    <div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
    <div class='headerShadow'>
    <span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
    <span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
    </div>
    <div class='headerForeground'>
    <span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
    <span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
    </div>
    </div>
    <div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
    <div id='sidebar'>
    <div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
    <div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
    </div>
    <div id='displayArea'>
    <div id='messageArea'></div>
    <div id='tiddlerDisplay'></div>
    </div>
    <!--}}}-->

    <!--{{{-->
    <div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
    <div class='title' macro='view title'></div>
    <div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
    <div class='tagging' macro='tagging'></div>
    <div class='tagged' macro='tags'></div>
    <div class='viewer' macro='view text wikified'></div>
    <div class='tagClear'></div>
    <!--}}}-->

    <!--{{{-->
    <div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
    <div class='title' macro='view title'></div>
    <div class='editor' macro='edit title'></div>
    <div macro='annotations'></div>
    <div class='editor' macro='edit text'></div>
    <div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser'></span></div>
    <!--}}}-->

    To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
    * SiteTitle & SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
    * MainMenu: The menu (usually on the left)
    * DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
    You'll also need to enter your username for signing your edits: <<option txtUserName>>

    These InterfaceOptions for customising TiddlyWiki are saved in your browser

    Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)

    <<option txtUserName>>
    <<option chkSaveBackups>> SaveBackups
    <<option chkAutoSave>> AutoSave
    <<option chkRegExpSearch>> RegExpSearch
    <<option chkCaseSensitiveSearch>> CaseSensitiveSearch
    <<option chkAnimate>> EnableAnimations

    ----
    Also see AdvancedOptions

    <<importTiddlers>>

    Adam is the author of ''Taggable.''  To find out more about him and other software that he has created, visit http://www.adamcrossland.net.  You can also visit his blog, http://blog.adamcrossland.net, to see the code in action; he created ''Taggable'' as part of the blogging software project the he wrote as a vehicle to learn about [[Google AppEngine]].

    {{{
    Copyright 2008 Adam A. Crossland

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    }}}

    ''1.0''
    Initial release
    ----
    ''2.0''
    This release has major changes to both [[Tag|Tag Class]] and [[Taggable|Taggable Class]], reflecting significant and important lessons that [[the author|AdamCrossland]] learned about [[Google AppEngine]].  Specifically, this release's changes should substantially improve the performance of any code that uses tags.
    *[[Tag|Tag Class]] entities are now stored with a custom key_name that allows them to be much-more-quickly retrieved from the data store.
    *The [[Tag|Tag Class]] has been rewritten to breakdown Tag/Taggable interactions in to a series of simple, atomic transactions.
    *[[Tag|Tag Class]] has a new property, [[tagged_count|Tag tagged_count Property]], that records the number of Taggable entities in its [[tagged|Tag tagged Property]] property.
    *Two new methods, [[get_tags_by_frequency|Tag get_tags_by_frequency Method]] and [[popular_tags|Tag popular_tags Method]], provide access to lists of [[Tags|Tag Class]] based on the number of [[Taggable|Taggable Class]] entities to which they refer.
    *A new method, [[get_tags_by_name|Tag get_tags_by_name Method]], provides lists of tags in alphabetical order.
    *The [[Taggable|Taggable Class]] class has been greatly simplified.  Multiple methods for getting and setting tags have been replaced with a single property, [[tags|Taggable tags Property]], that handles both getting and setting operations.
    *A full suite of automated unit tests for both classes has been created.

    If you have any questions or suggestions about ''Taggable'', please feel free to contact the author, AdamCrossland, at adam@adamcrossland.net.  Alternatively, you could post the question in <html><a href="http://groups.google.com/group/google-appengine/topics">the Google AppEngine Group.</a></html>

    If you are using ''Taggable'' and you have some changes, improvements or bug fixes that you'd like to contribute, please contact AdamCrossland at adam@adamcrossland.net.

    Because the Tag records are stored and queried slightly differently (yet much-more efficiently) in //Taggable-mixin 2.0//, an existing application will have to go through a process of converting all of its existing tags.

    For AdamCBlog, the application from which //Taggable-mixin// is extracted, I added a new RequestHandler called UpdateTag:
    {{{
    class UpdateTag(RequestHandler):
        def get(self):
            from taggable import Tag
            tag_name = self.request.get('tag')
            tag = db.Query(Tag).filter('tag =', tag_name).fetch(1)[0]
            if tag is not None:
                new_tag = Tag.get_or_create(tag_name)
                for each_tagged in tag.tagged:
                    new_tag.add_tagged(each_tagged)
            tag.delete()

            self.redirect('/')
    }}}

    Then, I made the request /~UpdateTag?tag=//inserttaghere// for each Tag in the datastore.  Yes, it is laborious.

    Why not just create a RequestHandler that cycled through all of the tags automatically, converting them as it went?  I tried that, but there is too much processing involved; the request uses up its time quota and is killed before it can finish.

    A [[Google AppEngine]] datastore property that holds a Python //datetime//.

    http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#DateTimeProperty

    [[Introduction]]

    Google ~AppEngine is Google's platform for developing web applications that run inside Google's computing cloud.

    Find out more about it at <html><a href="http://code.google.com/appengine/">The Google AppEngine website</a></html>.

    A [[Google AppEngine]] datastore property that holds a Python //long//.

    http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#IntegerProperty

    ''Taggable'' is a <html><a href="http://www.linuxjournal.com/node/4540/print">mixin</a></html> class that AdamCrossland created in order to add [[tags|Tag Definition]] to his personal blog (http://blog.adamcrossland.net) which is built on [[Google AppEngine]].  The design seemed clean and portable, so he decided to share it with the greater [[AppEngine|Google AppEngine]] community.  It is available under [[Apache 2.0 Open Source license]].

    Users of taggable-mixin 1.0 should note that taggable-mixin 2.0 is not directly compatible with 1.0.  While it is possible to upgrade, existing Tag entities will have to be [[converted|Converting from 1.0 to 2.0]].

    A [[Google AppEngine]] class that represents a unique key for a datastore entity.

    http://code.google.com/appengine/docs/datastore/keyclass.html

    A [[Google AppEngine]] datastore property that represents a Python //list//.

    http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#ListProperty

    [[Introduction|Introduction]]
    ChangeLog
    StepByStep
    <<tag api>>
    ----
    [[License|Apache 2.0 Open Source license]]
    [[Contribute]]
    [[Contact]]

    A //~RequestHandler// is a Python class that inherits from //webapp.~RequestHandler//.  It is used to answer HTTP requests that are received by your [[Google AppEngine]] application.  For more information on this subject, please consult the <html><a href="http://code.google.com/appengine/docs/webapp/requesthandlers.html">documentation.</a></html>

    <<search>><<closeAll>><<permaview>><<newTiddler>><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">>

    a portable mixin class for adding Tags to Google ~AppEngine Models

    Taggable

    Here's a step-by-step guide to adding ''Taggable'' to your [[Google AppEngine]] application.  This presents the simplest, most straightforward path to integrating ''Taggable'' as as such, it does not cover all of the options that are available.

    All of the code examples are modified extracts from the blogging software for which I originally created ''Taggable.''  //''These examples do not represent what the author considers to be complete and secure code; please make sure that you are familiar with best practices for building secure web applications before creating a web application.  The author of this document and the accompanying code bears no responsibility whatsoever for any losses or damages that you incur as a result of failing to take the appropriate steps to create a stable, secure, well-written web application.''//

    *Copy [[taggable.py]] to your application directory.
    *Import [[taggable.py]] into the python file that defines the Model that you want to make taggable:
    {{{
    from taggable import Taggable
    }}}
    *Add //Taggable// to the list of classes from which your Model class inherits.  Taggable -- and any other mixin classes -- should come before db.Model:
    {{{
    class Post(Taggable, db.Model):
    }}}
    *Add code to your Model's //init// method to call Taggable's //init// method.  If your Model class does not already override //init//, it will have to, and you will have to explicitly call the //init// method of any other superclass -- such as db.Model:
    {{{
        def __init__(self, parent=None, key_name=None, app=None, **entity_values):
            db.Model.__init__(self, parent, key_name, app, **entity_values)
            Taggable.__init__(self)
    }}}
    *Add code to your template to express the tagging information:
    {{{
    {% if post.tags %}
        <div class="posttags">tags:&nbsp;
            {% for each_tag in post.tags %}
                <a href="/searchbytag?tag={{ each_tag.tag|escape }}">{{ each_tag.tag }}</a>{% if forloop.last %}{% else %}, {% endif %}
            {% endfor %}
        </div>
    {% endif %}
    }}}
    or
    {{{
    <tr>
        <td>Tags:</td>
        <td>
            <input type="TEXT" name="tags" size="106" value="{% if post %}{{ post.tags_string }}{% endif %}" />
        </td>
    </tr>
    }}}
    *In any RequestHandler method that updates a Model object that has tags associated with it,  assign any new value to the ''Taggable'' object's [[tags|TaggableTagsProperty]]:
    {{{
    class EditPost(SmartHandler.SmartHandler):
        def post(self):
            postid = self.request.get('id')
            post = Post.get(postid)
            post.title = self.request.get('title')
            post.body = self.request.get('body')
            post.edited = datetime.datetime.now()
            post.tags = self.request.get('tags')
            post.put()
            .
            . do whatever else you need to do in your handler...
            .
    }}}

    A [[Google AppEngine]] datastore property that holds a Python //string// of 500 characters or less.

    http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#StringProperty

    The //Tag// class is the Model class that holds the data for an individual [[tag|Tag Definition]].

    It has four properties:
    *[[tag|Tag tag Property]]
    *[[added|Tag tag_added Property]]
    *[[tagged|Tag tagged Property]]
    *[[tagged_count|Tag tagged_count Property]]

    and ten methods:
    *[[remove_tagged|Tag remove_tagged Method]]
    *[[add_tagged|Tag add_tagged Method]]
    *[[clear_tagged|Tag clear_tagged Method]]
    *[[get_by_name|Tag get_by_name Method]]
    *[[get_tags_for_key|Tag get_tags_for_key Method]]
    *[[get_or_create|Tag get_or_create Method]]
    *[[get_tags_by_frequency|Tag get_tags_by_frequency Method]]
    *[[get_tags_by_name|Tag get_tags_by_name Method]]
    *[[popular_tags|Tag popular_tags Method]]
    *[[expire_cached_tags|Tag expire_cached_tags Method]]

    A //Tag// is a word or short phrase that acts as metadata; it describes and increases the searchability and findability of data.

    http://en.wikipedia.org/wiki/Tag_(metadata)

    //tag_instance.add_tagged(key)//
    * key - a [[Key|Key Class]] for a //Taggable// Model to mark with the [[Tag|Tag Class]]
    Returns: nothing

    The //add_tagged// method adds the [[Key|Key Class]] passed in to the Tag's [[tagged|Tag tagged Property]] collection and increments the [[tagged_count|Tag tagged_count Property]].  The operations are performed inside a transaction to ensure data intergity.

    Under most circumstances, the programmer will not need to call //add_tagged// directly; it is meant to be used internally by the [[Tagged Class]].  However, it is available for use by those who wish to customize the behavior of Taggable.

    //tag_instance.clear_tagged(key)//
    * key - a [[Key|Key Class]] for a //Taggable// object.
    Returns: nothing

    The //clear_tagged// method empties the Tag's [[tagged|Tag tagged Property]] collection and sets the [[tagged_count|Tag tagged_count Property]] to zero.  The operations are performed inside a transaction to ensure data intergity.

    Under most circumstances, the programmer will not need to call //clear_tagged// directly; it is meant to be used internally by the [[Tagged Class]].  However, it is available for use by those who wish to customize the behavior of Taggable.

    //Tag.expire_cached_tags()//
    Returns: nothing

    Comments:
    This method removes from memcache any objects that have been cached by other [[Tag|Tag Class]] methods.  Usually, the programmer will not call this method directly, as it is used behind-the-scenes by code that affects the validity of the cached items.  It is available, however, in case the programmer wishes to customize existing behavior.

    //Tag.get_by_name(tag_name)//
    *tag_name - the tag, in text form
    Returns: a [[Tag|Tag Class]] or //None//

    Example:
    {{{
    class SearchByTag(RequestHandler):
        def get(self):
            from post import Post
            from taggable import Tag

            requested_tag = self.request.get('tag')
            if requested_tag is not None and len(requested_tag) > 0:
                tag = Tag.get_by_name(requested_tag)
                posts = None
                if tag is not None:
                    posts = Post.get(tag.tagged)

                self.template_values['posts'] = posts
    }}}

    //Tag.get_or_create(tag_name)//
    *tag_name - the name of the tag that is to be retrieved from or created in the datastore
    Returns: a [[Tag|Tag Class]]

    Example:
    {{{
    for each_tag in tags:
        each_tag = string.strip(each_tag)
        if len(each_tag) > 0 and each_tag not in self.__tags:
            # A tag that was not previously assigned to this entity
            # is present in the list that is being assigned, so we
            # associate this entity with the tag.
            tag = Tag.get_or_create(each_tag)
            tag.add_tagged(self.key())
            self.__tags.append(tag)
    }}}

    //Tag.get_tags_by_frequency(limit=1000)//
    *limit - the number of records to return; the maximum is 1000
    Returns: a list of [[Tag|Tag Class]] objects, ordered by the number of //Taggable// objects assigned to it.

    Example:
    {{{
    @classmethod
    def popular_tags(cls, limit=5):
        from google.appengine.api import memcache

        tags = memcache.get('popular_tags')
        if tags is None:
            tags = Tag.get_tags_by_frequency(limit)
            memcache.add('popular_tags', tags, 3600)

        return tags
    }}}

    //Tag.get_tags_by_name(tag_name)//
    *tag_name - the string value of a [[Tag|Tag Class]] to be retrieved from the datastore
    Returns: a [[Tag|Tag Class]] object or None if the given //tag_name// does not exist

    Example:
    {{{
    requested_tag = self.request.get('tag')
    if requested_tag is not None and len(requested_tag) > 0:
        tag = Tag.get_by_name(requested_tag)
        posts = None
        if tag is not None:
            posts = Post.get(tag.tagged)

        self.template_values['posts'] = posts
    }}}

    //Tag.get_tags_for_key(key)//
    *key - a [[Key|Key Class]] for a //Taggable// object.
    Returns: a //list// of [[Tag|Tag Class]] objects

    Example:
    {{{
    def __get_tags(self):
        "Get a List of Tag objects for all Tags that apply to this object."
        if self.__tags is None or len(self.__tags) == 0:
            self.__tags = Tag.get_tags_for_key(self.key())
        return self.__tags
    }}}

    //Tag.popular_tags(limit=5)//
    *limit - the number of records to return; the default is 5 and the maximum is 1000
    Returns: a list of [[Tag|Tag Class]] objects, ordered by the number of [[Taggable|Taggable Class]] objects assigned to it.

    Comments:
    This method is a thin wrapper around [[get_tags_by_frequency|Tag get_tags_by_frequency]] that caches the returned values.

    Example:
    {{{
    self.template_values['popular_tags'] = Tag.popular_tags()
    }}}

    //tag_instance.remove_tagged(key)//
    * key - a [[Key|Key Class]] for a //Taggable// object.
    Returns: nothing

    The //remove_tagged// method removes the [[Key|Key Class]] passed in from the Tag's [[tagged|Tag tagged Property]] collection and decrements the [[tagged_count|Tag tagged_count Property]].  The operations are performed inside a transaction to ensure data intergity.

    Under most circumstances, the programmer will not need to call //remove_tagged// directly; it is meant to be used internally by the [[Tagged Class]].  However, it is available for use by those who wish to customize the behavior of Taggable.

    A StringProperty that holds the name or value of the tag.

    The DateTimeProperty that records that date and time that the [[Tag|TagClass]] was first added.

    A ListProperty that holds the Keys of all of the entities that have the given [[Tag|TagClass]] assigned to them.

    An IntegerProperty that holds the number of items that have the Tag assigned to them.

    The //Taggable// class can be mixed-in to any other Python class that inherits from //db.Model// in order to associate [[Tags|Tag Definition]] with that model.  For instance, if you were creating blogging software, one of your fundamental objects would be a //Post// that would comprise all of the information about an individual entry in your blog.  Usually, a Post can have [[Tags|Tag Definition]] added to it in order to provide easily-digested information to the reader about the content.

    It has two properties:
    *[[tags|Taggable tags Property]]
    *[[tag_separator|Taggable tag_separator Property]]

    and one method:
    *[[tags_string|Taggable tags_string Method]]

    Example:
    {{{
    class Post(Taggable, db.Model):
        index = db.IntegerProperty(required=True, default=0)
        body = db.TextProperty(required = True)
        title = db.StringProperty()
        added = db.DateTimeProperty(auto_now_add=True)
        added_month = db.IntegerProperty()
        added_year = db.IntegerProperty()
        edited = db.DateTimeProperty()

        def __init__(self, parent=None, key_name=None, app=None, **entity_values):
            db.Model.__init__(self, parent, key_name, app, **entity_values)
            Taggable.__init__(self)
    }}}
    Notes:
    *Notice that [[Taggable|Taggable Class]] is placed before db.Model in the inheritance list.  While this is not strictly required for //Taggable-mixin// to function correctly, it //is// required for other mixin classes, and it is generally a good practice.
    *It //is// required that the [[Taggable|Taggable Class]] //init// method is explicitly called, so it should be placed in the inherting class's //init// method.  If the class would not otherwise have one, it should be created, and the db.Model //init// must be called as well, as shown.

    The //tag_separator// property is the string that is used to separate individual tags in a string representation of a list of tags.  It is used by the [[tags property|TaggableTagsProperty]] to parse a string that may contain one or more tags to be applied to a [[Taggable|TaggableClass]] object.  It is also used by the [[tags_string|TaggableTagsStringMethod]] method to construct a string representation of the tags for a [[Taggable|TaggableClass]] object.

    *By default, it is set to a comma (','), but it can be programatically-set to whatever value the developer desires
    *It is probably best to avoid using whitespace characters, as that would prevent users from entering multi-word tags
    *Custom separator values can be set at different levels.
    **To set one value for all [[Taggable|TaggableClass]] objects, modify the //init// method in the //Taggable// class in the taggable.py file:
    {{{
    class Taggable:
         def __init__(self):
            self.tag_separator = ";" # Made it semi-colon instead of comma
    }}}
    **To set one value for all instances of a particular [[Taggable|TaggableClass]] class, modify the //init// method of that class:
    {{{
    class Post(Taggable, db.Model):
        body = db.TextProperty(required = True)
        title = db.StringProperty()
        added = db.DateTimeProperty(auto_now_add=True)
        edited = db.DateTimeProperty()

        def __init__(self, parent=None, key_name=None, app=None, **entity_values):
            db.Model.__init__(self, parent, key_name, app, **entity_values)
            Taggable.__init__(self)
            self.tag_separator = ";" # Made it semi-colon instead of comma
    }}}
    **To set a value for one particular instance of a [[Taggable|TaggableClass]] class, set the value after creating the instance:
    {{{
    newpost = Post(title = self.request.get('title'), body = self.request.get('body'))
    newpost.tag_separator = ";" # Made it semi-colon instead of comma
    newpost.set_tags_from_string("foo; bar; bletch; this is a tag; tags rule"
    }}}

    The a [[Taggable|Taggable Class]] class's //tags// property is used to assign [[Tags|Tag Class]] to the [[Taggable|Taggable Class]] instance or retrieve a //list// of those already assigned.

    Assignment Example:
    {{{
    class Post(Taggable, db.Model):
        index = db.IntegerProperty(required=True, default=0)
        body = db.TextProperty(required = True)
        title = db.StringProperty()
        added = db.DateTimeProperty(auto_now_add=True)
        added_month = db.IntegerProperty()
        added_year = db.IntegerProperty()
        edited = db.DateTimeProperty()

        def __init__(self, parent=None, key_name=None, app=None, **entity_values):
            db.Model.__init__(self, parent, key_name, app, **entity_values)
            Taggable.__init__(self)

        @classmethod
        def new_post(cls, new_title=None, new_body=None, new_tags=[]):
            if new_title is not None and new_body is not None:

                new_post = Post(title = new_title, body = new_body)
                new_post.put()

                new_post.tags = new_tags
                new_post.save()
            else:
                raise Exception("Must supply both new_title and new_body when creating a new Post.")
    }}}

    Retrieval Example:
    {{{
    {% if post.tags %}
        <div class="posttags">tags:&nbsp;
            {% for each_tag in post.tags %}
                <a href="/searchbytag?tag={{ each_tag.tag|escape }}">{{ each_tag.tag }}</a>{% if forloop.last %}{% else %}, {% endif %}
            {% endfor %}
        </div>
    {% endif %}
    }}}

    //taggable_instance.tags_string()//
    Returns: a string representation of the tags assigned to the [[Taggable|Taggable Class]] instance.

    This method simply joins the string versions of each [[Tag|Tag Class]] in the [[Taggable|Taggable Class]] class's //list//, placing the value stored in [[tag_separator|Taggable tag_separator Property]] inbetween each element.

    <<tagging api>>

    The //get_tags_as_string// method creates a string representation of all of the tags that apply to a ''Taggable'' object.

    The //set_tags// method sets the tags for a ''Taggable'' object from a list of strings:
    {{{
    tag_list = ["foo", "bar", "bletch","this is a tag", "tags rule"]
    my_taggable_object.set_tags(tag_list)
    }}}

    The //set_tags_from_string method// sets the tags for a Taggable object from a string that contains one or more tags separated by the character or characters in //self.tag_seperator//:
    {{{
    tag_list = "foo, bar, bletch, this is a tag, tags rule"
    my_taggable_object.set_tags_from_string(tag_list)
    }}}

    By default, [[tag_separator|tag_separator]] is set to a comma (','), but it can be anything that the programmer wants.

    All of the code for the ''Taggable'' mixin class lives in the file taggable.py.  Simply copy this file in to your Google ~AppEngine main directory, and it should be available to your code.