<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;D08HQX4yfyp7ImA9WhFTEko.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520</id><updated>2013-06-03T18:10:30.097+02:00</updated><category term="plotutils" /><category term="Python" /><category term="adaptive navigation" /><category term="Google Reader" /><category term="Circuit Breaker Pattern" /><category term="howto" /><category term="apple" /><category term="free" /><category term="eps2svg" /><category term="ER-model" /><category term="badge" /><category term="AppEngine" /><category term="GAE" /><category term="voip" /><category term="sip" /><category term="gadget" /><category term="epstosvg" /><category term="django" /><category term="Java" /><category term="ghostscript" /><category term="App Engine SDK" /><category term="Google" /><category term="Lotus" /><category term="CouchDB" /><category term="entity relationship model" /><category term="iphone" /><category term="captcha" /><category term="RCP" /><category term="Symphony" /><category term="eps" /><category term="PyDev" /><category term="svg" /><category term="web 2.0" /><category term="situational applications" /><category term="context awareness" /><category term="Expeditor" /><category term="pstoedit" /><category term="eclipse" /><category term="Apache" /><category term="inkscape" /><category term="enterprisity" /><category term="usability" /><category term="GMail" /><category term="sipgate" /><title>The Daily Profeth</title><subtitle type="html">– intentionally left blank –</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://daily.profeth.de/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>14</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/TheDailyProfeth" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="thedailyprofeth" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CkUHRHc7cCp7ImA9Wx9REk4.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-5004920954906251580</id><published>2009-12-26T03:13:00.002+01:00</published><updated>2010-12-13T09:50:35.908+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-13T09:50:35.908+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="CouchDB" /><category scheme="http://www.blogger.com/atom/ns#" term="Apache" /><title>Apache CouchDB: External Process using Java and serving file</title><content type="html">&lt;a href="http://couchdb.apache.org/"&gt;Apache CouchDB&lt;/a&gt; supports invoking &lt;a href="http://wiki.apache.org/couchdb/ExternalProcesses"&gt;external processes&lt;/a&gt; - here is a very simple example in Java, which returns an image (or another arbitrary binary file):&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;import java.io.FilterOutputStream;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;import java.io.IOException;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt; &lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;import sun.misc.BASE64Encoder;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;public class ExternalProcessTest {&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;  public static void main(String[] args) throws IOException {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:small;"&gt;    &lt;/span&gt;&lt;span style="font-size:small;"&gt;System.out.print("{\"code\":200,\"base64\":\"");&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt; &lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;    new BASE64Encoder().encodeBuffer(&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:small;"&gt;      ExternalProcessTest.class.getResourceAsStream("logo.png"),&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;      new FilterOutputStream(System.out) {&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;        @Override&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;        public void write(int b) throws IOException {&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;          if (b != '\n' &amp;amp;&amp;amp; b != '\r') { &lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;            super.write(b);&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;          }&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;        }&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;      });&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;    System.out.print("\",\"headers\":{\"Content-Type\":\"image/png\"}}");&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;    System.out.println();&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:small;"&gt;    System.exit(0);&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;  }&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div  style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;span style="font-size:small;"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;This can be bundled into a runnable JAR file.&lt;br /&gt;Some hints:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;You might want to make your output return another status code in case an error ocurred&lt;/li&gt;&lt;li&gt;You might want to replace the BASE64Encoder class which is part of sun.misc with your favorite implementation.&lt;/li&gt;&lt;li&gt;Most base64 implementations add CRLF every 76 characters, which need to be stripped, because CouchDB expects to get exactly one line returned.&lt;/li&gt;&lt;li&gt;The Content-Type header should match the MIME-type of the returned file. There are also &lt;a href="http://www.rgagnon.com/javadetails/java-0487.html"&gt;automatic ways to recognize the MIME-type&lt;/a&gt; of a file.&lt;/li&gt;&lt;li&gt;Don't forget the ending newline after your output.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Within your&lt;span style="font-size:small;"&gt; &lt;/span&gt;&lt;span style="font-size:small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;local.ini&lt;/span&gt;&lt;/span&gt; of CouchDB (to be found within &lt;span style="Courier New&amp;quot;,Courier,monospace; font-family:&amp;quot;;font-size:small;"&gt;etc\couchdb&lt;/span&gt;), you need to register your external application:&lt;br /&gt;&lt;div face="&amp;quot;" style="Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;span style="font-size:small;"&gt;[external]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:small;"&gt;test=java -jar C:\ExternalProcessTest.jar 2&amp;gt;1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:small;"&gt;[httpd_db_handlers]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:small;"&gt;_test = {couch_httpd_external, handle_external_req, &amp;lt;&amp;lt;"test"&amp;gt;&amp;gt;}&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Java needs to be within your PATH variable.&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;2&gt;&amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-size:small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;1&lt;/span&gt;&lt;/span&gt; suppresses error output to be logged to the CouchDB log.&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;http_db_handlers&lt;/span&gt;&lt;/span&gt; registers the handler on database level, not globally.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Additional hints:&lt;br /&gt;CouchDB pipes some request data to the external process, you can receive this by reading from &lt;span style="font-size:small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;System.in&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:x-small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;&lt;/span&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace; font-family:&amp;quot;;font-size:small;"&gt;String json = &lt;/span&gt;&lt;/span&gt;&lt;span style="font-size:small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;new BufferedReader(new InputStreamReader(System.in))&lt;/span&gt;&lt;/span&gt;&lt;span style="font-size:small;"&gt;&lt;span style="Courier New&amp;quot;,Courier,monospace;font-family:&amp;quot;;"&gt;.readLine();&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In action:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_lBJLUOoqxdY/SzVwnxZUkUI/AAAAAAAAiHU/Lv8U6bj9GIc/s1600-h/couchdb_external_process_screenshot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="206" src="http://4.bp.blogspot.com/_lBJLUOoqxdY/SzVwnxZUkUI/AAAAAAAAiHU/Lv8U6bj9GIc/s320/couchdb_external_process_screenshot.png" width="320" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;a href="http://uploads.feth.com/blog/ExternalProcessTest.jar"&gt;Download the source code and the runnable JAR file.&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/N092kvOeRMI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/5004920954906251580/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/12/apache-couchdb-external-process-using.html#comment-form" title="1 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5004920954906251580?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5004920954906251580?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/12/apache-couchdb-external-process-using.html" title="Apache CouchDB: External Process using Java and serving file" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_lBJLUOoqxdY/SzVwnxZUkUI/AAAAAAAAiHU/Lv8U6bj9GIc/s72-c/couchdb_external_process_screenshot.png" height="72" width="72" /><thr:total>1</thr:total><georss:featurename>Metzingen, Germany</georss:featurename><georss:point>48.536466 9.285074</georss:point><georss:box>48.479634 9.1683445 48.593298 9.4018035</georss:box></entry><entry gd:etag="W/&quot;DE4HQXcyfyp7ImA9WxBTFks.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-2737852169324185985</id><published>2009-12-13T02:15:00.000+01:00</published><updated>2009-12-13T02:15:30.997+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-13T02:15:30.997+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="situational applications" /><category scheme="http://www.blogger.com/atom/ns#" term="adaptive navigation" /><category scheme="http://www.blogger.com/atom/ns#" term="iphone" /><category scheme="http://www.blogger.com/atom/ns#" term="context awareness" /><category scheme="http://www.blogger.com/atom/ns#" term="apple" /><title>Apple, please add adaptive navigation and context-awareness!</title><content type="html">My iPhone keeps on filling itself with apps (I wonder how it does that :-D) - some of them get removed directly after install if I don't like them, others are getting my favorites and again others I know I will use them in the future, probably when I don't have wifi or a good reception available, but I just don't need them now. You know about which type of apps I am talking about: white pages, hotel booker, dictionaries, traffic information, converter tools, etc.&lt;br /&gt;
Some of them are really useful, but I only need them at a certain time once, or over a certain period of time (mostly for some days or sometimes weeks) - but they still clutter my iPhone - and even more: new apps are added behind them, whether the newly installed app will become one of my favorites or not.&lt;br /&gt;
&lt;br /&gt;
Now this is nothing new and exceptionally specific to the iPhone, we can also see this with webpages and/or information within webpages, also. I might check on eBay 10 times a day if I want to bid on something. But then again I might not use eBay for weeks if I don't want to bid on anything.&lt;br /&gt;
&lt;br /&gt;
It's some sort of &lt;b&gt;situational correlation.&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Now with the iPhone,&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;the situation might be that I am currently travelling in a foreign country - let's take France for example, I would need my french dictionary often and definitely my other dictionaries are more or less useless while I am travelling - so are the apps I have for public transportation schedules in Germany.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;the situation might be, that I have a certain task to do - search for a suitable name of an yet unborn child for example - I don't know if there is an app for that (there probably is!), but it definitely gets used less frequently once I found a name or the child was born.&lt;/li&gt;
&lt;li&gt;the situation might be, that I am at home - in which case I probably will use my computer to check my newsreader or do some instant messaging instead of the according apps on my iPhone - but I might be still interested in my games or the birthdays of my contacts.&lt;/li&gt;
&lt;li&gt;I am probably less interested in booking a hotel when I am in a 50 miles range to my home.&lt;/li&gt;
&lt;li&gt;I probably won't need my compass when being at home.&lt;/li&gt;
&lt;li&gt;etc.&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;For all of these cases, there are some keywords which might lead to an answer: &lt;b&gt;context-aware computing, adaptive navigation and situational applications&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
Now why do people use none of these keywords together in one sentence with the iPhone? It seems like a perfect candidate for such technologies:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;My iPhone has the ability to know where I am.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;My iPhone has the ability to make a good guess about which language I prefer.&lt;/li&gt;
&lt;li&gt;My iPhone (probably) has the ability to know whats on my mind when I checked for a baby name in the [put a name here] app, the thousandth time this week.&lt;/li&gt;
&lt;li&gt;My iPhone has the ability to know, that I didn't use this nameless application with the hideous icon within the last five weeks.&lt;/li&gt;
&lt;/ol&gt;&lt;b&gt;I want my iPhone to act accordingly.&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Now in order to let my iPhone do this, there are some requirements.&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Some sort of&amp;nbsp; matching, using evidence sources to find out where exactly I am (what my current context is) - like &lt;a href="http://www.symonds.id.au/marcopolo/"&gt;MarcoPolo &lt;/a&gt;does it for example.&lt;/li&gt;
&lt;li&gt;Some sort of awareness how often which application is used and in what context (see 1.).&lt;/li&gt;
&lt;li&gt;Some sort of mechanism to adapt the navigation dynamically (e.g. re-sort and/or hide my apps) based on 1. and 2.&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;Those three requirements would allow for a full-fledged context-aware computing on the iPhone.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Example&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
By default my iPhone screen looks like this:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_lBJLUOoqxdY/SyQ8i1mYyXI/AAAAAAAAiGM/9WUd4WOhmUo/s1600-h/iPhone_standard.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/_lBJLUOoqxdY/SyQ8i1mYyXI/AAAAAAAAiGM/9WUd4WOhmUo/s400/iPhone_standard.png" width="248" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;Lets make the assumption, that:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;A is my text messaging app, which I use wherever I am. &lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;I am currently at home and I definitely do not need apps I to L because they do something I can do better at my laptop and my laptop is currently also started. &lt;/li&gt;
&lt;li&gt;I definitely don't need my car navigation, which is B.&lt;/li&gt;
&lt;li&gt;There is a game R which I bought at the beginning of this week and have played this for some time when being at home.&lt;/li&gt;
&lt;/ul&gt;Then the iPhone screen could look like this:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_lBJLUOoqxdY/SyQ_iOVDoqI/AAAAAAAAiGk/Zn9pvwECd50/s1600-h/iPhone_adapted.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SyQ_iOVDoqI/AAAAAAAAiGk/Zn9pvwECd50/s400/iPhone_adapted.png" width="246" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/HSice4Jl_lg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/2737852169324185985/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/12/apple-please-add-adaptive-navigation.html#comment-form" title="0 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2737852169324185985?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2737852169324185985?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/12/apple-please-add-adaptive-navigation.html" title="Apple, please add adaptive navigation and context-awareness!" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_lBJLUOoqxdY/SyQ8i1mYyXI/AAAAAAAAiGM/9WUd4WOhmUo/s72-c/iPhone_standard.png" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Stuttgart, Germany</georss:featurename><georss:point>48.7771056 9.1807688</georss:point><georss:box>48.6639821 8.947309299999999 48.8902291 9.4142283</georss:box></entry><entry gd:etag="W/&quot;D0ECR3wzfSp7ImA9WxNaFE8.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-3381833883638312408</id><published>2009-11-28T17:26:00.001+01:00</published><updated>2009-11-28T17:27:46.285+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-28T17:27:46.285+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Lotus" /><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="RCP" /><category scheme="http://www.blogger.com/atom/ns#" term="Expeditor" /><category scheme="http://www.blogger.com/atom/ns#" term="Symphony" /><title>Lotus Expeditor: Using custom launch items</title><content type="html">When working with Lotus Expeditor and you want to add a custom launcher item to the "new" section in let's say Lotus Symphony for example, there is not much help available out there.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_lBJLUOoqxdY/SxFKoDedETI/AAAAAAAAiA4/862v5zY_muE/s1600/symphony_new_button.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://1.bp.blogspot.com/_lBJLUOoqxdY/SxFKoDedETI/AAAAAAAAiA4/862v5zY_muE/s320/symphony_new_button.png" border="0" height="152" width="320" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Sure, there is the &lt;a href="http://publib.boulder.ibm.com/infocenter/ledoc/v6r2/index.jsp?topic=/com.ibm.rcp.doc.schemas/reference/extension-points/com_ibm_rcp_ui_launcherSet.html"&gt;description of different launchers in the Information center&lt;/a&gt;, but it only explains how to use &lt;span style=";font-family:&amp;quot;;" &gt;urlLaunchItem&lt;/span&gt;, &lt;span style=";font-family:&amp;quot;;" &gt;nativeProgramLaunchItem &lt;/span&gt;and &lt;span style=";font-family:&amp;quot;;" &gt;perspectiveLaunchItem &lt;/span&gt;in detail.&lt;br /&gt;&lt;br /&gt;So this article is for you out there searching Google for a how-to.&lt;br /&gt;&lt;br /&gt;First define the Launcher in your plugin.xml:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SxFOuc5ubYI/AAAAAAAAiBQ/uTUPmdhiHOc/s1600/extension_point_code.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SxFOuc5ubYI/AAAAAAAAiBQ/uTUPmdhiHOc/s400/extension_point_code.png" border="0" height="135" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;(sorry Blogger didn't let me input XML here...)&lt;br /&gt;&lt;br /&gt;Now this registers a new item (&lt;span style=";font-family:&amp;quot;;" &gt;launchItem&lt;/span&gt;) and defines a launch item type (&lt;span style=";font-family:&amp;quot;;" &gt;launchItemType&lt;/span&gt;) which gets invoked when clicking on it (see &lt;span style=";font-family:&amp;quot;;" &gt;type &lt;/span&gt;attribute of &lt;span style=";font-family:&amp;quot;;" &gt;launchItem &lt;/span&gt;and &lt;span style=";font-family:&amp;quot;;" &gt;name &lt;/span&gt;atttribute of &lt;span style=";font-family:&amp;quot;;" &gt;launchItemType&lt;/span&gt;).&lt;br /&gt;The &lt;span style=";font-family:&amp;quot;;" &gt;launchItemType &lt;/span&gt;invokes a class (see &lt;span style=";font-family:&amp;quot;;" &gt;class &lt;/span&gt;attribute of &lt;span style=";font-family:&amp;quot;;" &gt;launchItemType&lt;/span&gt;) which needs to be a subclass of &lt;span style=";font-family:&amp;quot;;" &gt;com.ibm.rcp.ui.launcher.LauncherContributionItem&lt;/span&gt; (&lt;b style="color: red;"&gt;attention&lt;/b&gt;: &lt;strike&gt;&lt;span style=";font-family:&amp;quot;;" &gt;com.ibm.rcp.jface.launcher.LauncherContributionItem&lt;/span&gt;&lt;/strike&gt; is deprecated and should not be used).&lt;br /&gt;&lt;br /&gt;The referenced class in &lt;span style=";font-family:&amp;quot;;" &gt;launchItemType&lt;/span&gt; itself should look like this:&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;package com.xyz;&lt;br /&gt;&lt;br /&gt;public class MyLaunchItemTypeClass extends com.ibm.rcp.ui.launcher.LauncherContributionItem {&lt;br /&gt;    @Override&lt;br /&gt;    public void launch(int arg0) {&lt;br /&gt;        super.launch(arg0);&lt;br /&gt;        System.out.println("LAUNCHED!");&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;also don't forget to put &lt;span style="font-family: courier new;"&gt;com.ibm.rcp.swtex&lt;/span&gt; and &lt;span style="font-family: courier new;"&gt;com.ibm.rcp.ui&lt;/span&gt; on your required bundles list in MANIFEST.MF.&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/V9QkUpkocfE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/3381833883638312408/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/11/lotus-expeditor-using-custom-launch.html#comment-form" title="1 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/3381833883638312408?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/3381833883638312408?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/11/lotus-expeditor-using-custom-launch.html" title="Lotus Expeditor: Using custom launch items" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_lBJLUOoqxdY/SxFKoDedETI/AAAAAAAAiA4/862v5zY_muE/s72-c/symphony_new_button.png" height="72" width="72" /><thr:total>1</thr:total><georss:featurename>Stuttgart, Germany</georss:featurename><georss:point>48.7771056 9.1807688</georss:point><georss:box>48.6639821 8.947309299999999 48.8902291 9.4142283</georss:box></entry><entry gd:etag="W/&quot;DUYMSX8-eCp7ImA9WxNaEEs.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-2896386227677160137</id><published>2009-11-24T13:53:00.000+01:00</published><updated>2009-11-24T13:53:08.150+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-24T13:53:08.150+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="GMail" /><category scheme="http://www.blogger.com/atom/ns#" term="gadget" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Reader" /><title>Google Reader integration for GMail</title><content type="html">Today I spent some minutes on writing a small Google Gadget which can be used to integrate the &lt;a href="http://reader.google.com/"&gt;Google Reader&lt;/a&gt; into &lt;a href="http://mail.google.com/"&gt;GMail&lt;/a&gt;.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SwvU_olzbsI/AAAAAAAAh-g/nYVZaJ2vDFQ/s1600/add_any_gadget_gmail.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="77" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SwvU_olzbsI/AAAAAAAAh-g/nYVZaJ2vDFQ/s400/add_any_gadget_gmail.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div class="" style="clear: both; text-align: left;"&gt;You can add it to your GMail by enabling the "Add any gadget by URL" Labs feature within GMail and then add the gadget with this URL: &lt;a href="http://gadgets.feth.com/reader/reader-gmail.xml"&gt;http://gadgets.feth.com/reader/reader-gmail.xml&lt;/a&gt;.&lt;br /&gt;
&lt;/div&gt;&lt;div class="" style="clear: both; text-align: left;"&gt;&lt;br /&gt;
&lt;/div&gt;&lt;a href="http://3.bp.blogspot.com/_lBJLUOoqxdY/SwvVY6DE-EI/AAAAAAAAh-o/QbGD9pK6vic/s1600/google_reader_sidebar_tap.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SwvVY6DE-EI/AAAAAAAAh-o/QbGD9pK6vic/s1600/google_reader_sidebar_tap.png" /&gt;&lt;/a&gt;It will display a small tab in your sidebar from which you can access Google Reader. The Reader will open in the area where you normally read your mails and will look like this:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_lBJLUOoqxdY/SwvWb48nPBI/AAAAAAAAh_A/Xi9bx9myCJ0/s1600/GMail_Google_Reader.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="155" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SwvWb48nPBI/AAAAAAAAh_A/Xi9bx9myCJ0/s400/GMail_Google_Reader.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;Please feel free to &lt;a href="http://www.google.com/ig/directory?type=gadgets&amp;amp;url=gadgets.feth.com/reader/reader-gmail.xml"&gt;write a comment and/or rate this gadget&lt;/a&gt;. If you encounter any problems, feel free to send me a note or use the comments section!&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/pGaD00Zolh8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/2896386227677160137/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/11/google-reader-integration-for-gmail.html#comment-form" title="5 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2896386227677160137?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2896386227677160137?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/11/google-reader-integration-for-gmail.html" title="Google Reader integration for GMail" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_lBJLUOoqxdY/SwvU_olzbsI/AAAAAAAAh-g/nYVZaJ2vDFQ/s72-c/add_any_gadget_gmail.png" height="72" width="72" /><thr:total>5</thr:total><georss:featurename>Stuttgart, Germany</georss:featurename><georss:point>48.7771056 9.1807688</georss:point><georss:box>48.6639821 8.947309299999999 48.8902291 9.4142283</georss:box></entry><entry gd:etag="W/&quot;AkcNQHg-fSp7ImA9WxNbFk8.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-5697756185644589585</id><published>2009-11-18T22:18:00.001+01:00</published><updated>2009-11-19T11:54:51.655+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-19T11:54:51.655+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="enterprisity" /><title>The Enterprisity</title><content type="html">I am so frustrated today...I definitely needed to translate an (&lt;a href="http://blog.256bit.org/archives/429-Enterprisitaet.html"&gt;relatively unknown&lt;/a&gt;) urban definition to English which exists in German since 2007 (Thanks to Michael Kukat):&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;b&gt;Enterprisity&lt;/b&gt; - Measure for the adequacy of a method, a software or a person for his/her/its application in an enterprise, especially in major corporations. Important distinguishing features regarding enterprisity are the artificially boosted complexity, the resistance to counseling (primarily those of persons), the high demand for process orientation simultaneously paired with the preferred total circumnavigation of it, as well as the most miserable performance in comparison to competitive products (or persons) at the most terrible price.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Possible uses:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;What the heck... I think the &lt;b&gt;enterprisity&lt;/b&gt; got me - I didn't get any work done today!&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;God I love this company, there is such a low &lt;b&gt;enterprisity&lt;/b&gt;, it almost feels like working on an Open source project.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Shoot! Why do I need to work with this rubbish - are they still trying to maximize &lt;b&gt;enterprisity&lt;/b&gt;?&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;If you are a native speaker, please use the comments to help me remove any expression flaws from the definition. Thanks ;-)&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/yo8cpc4rkes" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/5697756185644589585/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/11/enterprisity.html#comment-form" title="2 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5697756185644589585?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5697756185644589585?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/11/enterprisity.html" title="The Enterprisity" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><thr:total>2</thr:total><georss:featurename>Stuttgart, Germany</georss:featurename><georss:point>48.7771056 9.1807688</georss:point><georss:box>48.6639821 8.947309299999999 48.8902291 9.4142283</georss:box></entry><entry gd:etag="W/&quot;C0cESH09cCp7ImA9Wx9SEko.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-8139543681859992450</id><published>2009-10-30T22:12:00.002+01:00</published><updated>2010-12-02T07:23:29.368+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-02T07:23:29.368+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="usability" /><category scheme="http://www.blogger.com/atom/ns#" term="iphone" /><title>Possible iPhone slide to unlock usability enhancement</title><content type="html">&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;span style="font-family:inherit;"&gt;Update on 2 Dec 2010: Just found out that Sony Ericssons Xperia x10 actually has an ergonomic slide to unlock area. Yay!&lt;br /&gt;&lt;br /&gt;Did you ever try to place a call with your iPhone while carrying your notebook?&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;span style="font-family:inherit;"&gt;Did you ever receive a call on your iPhone when carrying your meal?&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;span style="font-family:inherit;"&gt;It's not so easy to unlock the iPhone or accept that call while being only in possession of one hand, right?!&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;span style="font-family:inherit;"&gt;And also while it is possible to just do that little slide with only the thumb of the very hand also holding the iPhone, it is just not very convenient. Feels more like having played &lt;/span&gt;&lt;a href="http://www.consoleclassix.com/gbc/f1_race.html"&gt;&lt;span style="font-family:inherit;"&gt;F1 Race&lt;/span&gt;&lt;/a&gt;&lt;span style="font-family:inherit;"&gt; on a Game Boy for the last 4 hours than using a phone designed by a company known for its excellence in creating products with an extraordinary usability.&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;span style="font-family:inherit;"&gt;When looking at a sequence of images about unlocking an iPhone with one hand, one can see, that the problem actually seems that the thumb itself needs to be bended too much to stay within the slider area:&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_lBJLUOoqxdY/SutEPqOEZNI/AAAAAAAAh7E/pcxhBWor9Ns/s1600-h/1.1,3,5,7.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://4.bp.blogspot.com/_lBJLUOoqxdY/SutEPqOEZNI/AAAAAAAAh7E/pcxhBWor9Ns/s400/1.1,3,5,7.jpg" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The reason for this is that hitting the slider area implies moving the finger in a straight line, which is just not a natural movement for the thumb. A wipe movement in a curvy line is much easier to accomplish and also feels more natural:&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SutEQQN3UpI/AAAAAAAAh7M/cYXFax5uNJo/s1600-h/2.2,5,7,9.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SutEQQN3UpI/AAAAAAAAh7M/cYXFax5uNJo/s400/2.2,5,7,9.jpg" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;So one possibility to improve unlocking with one hand would be realigning the slider area from a straight line (see "traditional" in image below) to a slightly bended line (see "thumb-friendly" in image below):&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SutOSpi6bsI/AAAAAAAAh7o/-cdN6Ft9OnI/s1600-h/old_vs_new.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SutOSpi6bsI/AAAAAAAAh7o/-cdN6Ft9OnI/s320/old_vs_new.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;Realigning the slider area has one major drawback though: the background image gets overlayed by it. While I would still favor that approach and also would be willing to have my background image overlayed with a semi-transparent slider area, but I know that many of you will disagree with that, so the logical continuation of my approach to improve the slider usability is using a slider "knob" which can be dragged freely over they iPhone's display in any direction and with any bending:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SutTRt46kOI/AAAAAAAAh7w/Lo60pOC-Y-Q/s1600-h/free_movement.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SutTRt46kOI/AAAAAAAAh7w/Lo60pOC-Y-Q/s320/free_movement.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;As soon as the knob has been dragged far enough from its initial position this will unlock the iPhone (or accept a call certainly). This would maximize the ergonomics of the movement, as people with different hand sizes and/or different likes in thumb movement can choose the one which is the most natural to them.&lt;br /&gt;&lt;br /&gt;To carry that thought a bit further, this could even allow for unlocking the iPhone not via entering a pass phrase or some numbers, but only by dragging the knob in a previously defined gesture:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_lBJLUOoqxdY/SutVkPmq1GI/AAAAAAAAh74/zT4iLtIKo2k/s1600-h/gesture_unlock.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://4.bp.blogspot.com/_lBJLUOoqxdY/SutVkPmq1GI/AAAAAAAAh74/zT4iLtIKo2k/s320/gesture_unlock.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;Are these ideas feasible? Any additions? I'd love to hear what you think in the comments!&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/gp9ChzwXlZg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/8139543681859992450/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/10/possible-iphone-slide-to-unlock.html#comment-form" title="4 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/8139543681859992450?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/8139543681859992450?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/10/possible-iphone-slide-to-unlock.html" title="Possible iPhone slide to unlock usability enhancement" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_lBJLUOoqxdY/SutEPqOEZNI/AAAAAAAAh7E/pcxhBWor9Ns/s72-c/1.1,3,5,7.jpg" height="72" width="72" /><thr:total>4</thr:total><georss:featurename>Metzingen, Germany</georss:featurename><georss:point>48.53911372949725 9.270765781402588</georss:point><georss:box>48.53733772949725 9.267117781402588 48.54088972949725 9.274413781402588</georss:box></entry><entry gd:etag="W/&quot;CE4DQ304eSp7ImA9WxNQEU0.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-2370747914728203989</id><published>2009-09-16T14:09:00.000+02:00</published><updated>2009-09-16T14:09:32.331+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-16T14:09:32.331+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="sip" /><category scheme="http://www.blogger.com/atom/ns#" term="free" /><category scheme="http://www.blogger.com/atom/ns#" term="voip" /><category scheme="http://www.blogger.com/atom/ns#" term="sipgate" /><category scheme="http://www.blogger.com/atom/ns#" term="iphone" /><title>Free VoIP calls with sipgate on iPhone using XSPhone</title><content type="html">&lt;div style="text-align: left;"&gt;Today nothing developerish (sorry to all you frequent readers out there), but something not minor interesting. Yet, only for all of you out there being in possession of an iPhone or iPod touch (2nd generation).&lt;/div&gt;&lt;div&gt;&lt;span style="color: black;"&gt;&lt;/span&gt;&lt;br /&gt;
On my search throughout the Apple App Store for neat applications I might miss, I came across a very simple (but nonetheless interesting) free application, which is called &lt;a href="http://linktoapp.com/xsphone"&gt;XSPhone&lt;/a&gt;.&lt;br /&gt;
&lt;div&gt;&lt;/div&gt;&lt;div&gt;XSPhone is a phone application officially designed for use with the dutch ISP XS4ALL, but implements the standard Session Initiation Protocol (better known as SIP) and is (&lt;a href="http://www.xs4all.nl/nieuws/bericht.php?taal=en&amp;amp;id=1040&amp;amp;msect=nieuws"&gt;according to XS4ALL&lt;/a&gt;) &lt;q&gt;&lt;i&gt;[...] suitable for use with XS4ALL’s Talk subscription, but also for other VoIP providers that use the SIP protocol [...]&lt;/i&gt;&lt;/q&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
I personally use &lt;a href="http://www.sipgate.de/"&gt;sipgate&lt;/a&gt;, which is popular in Germany, but also is &lt;a href="http://www.sipgate.com/"&gt;available in the U.S.&lt;/a&gt;. So I thought I should give XSPhone a try with sipgate. And, behold, it works! Even incoming calls are possible.&lt;br /&gt;
Due to the restrictions for calling apps on the iPhone, everything in this article only works over a WiFi connection, not with a mobile internet connection, though.&lt;br /&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;So let's get this up and working.&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;First, install the app (if you didn't already). You can either search for "XSPhone" or click the App Store link provided above.&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5356965274149231746" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SlfDiM_EnII/AAAAAAAAe5s/gSXcJh7_IRg/s320/sipgate_settings.png" style="cursor: pointer; float: right; height: 153px; margin: 0pt 0pt 10px 10px; width: 309px;" /&gt;&lt;/div&gt;&lt;div&gt;Then, let's get your VoIP credentials.&lt;/div&gt;&lt;div&gt;For sipgate you can find them in your settings after logging into your sipgate account.&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Sorry, screenshots are in german, too lazy to search if I can switch to english ;-)&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Within your settings page, you should see a screen with two grey boxes, one of which reads "Account data":&lt;/div&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5356966434554820386" src="http://4.bp.blogspot.com/_lBJLUOoqxdY/SlfElv1UtyI/AAAAAAAAe50/AxzH_RjhgKc/s320/sipgate_account_data.png" style="cursor: pointer; display: block; height: 80px; margin: 0px auto 10px; text-align: center; width: 320px;" /&gt;&lt;br /&gt;
&lt;div&gt;&lt;div style="text-align: center;"&gt;and the other one "Server data":&lt;/div&gt;&lt;a href="http://4.bp.blogspot.com/_lBJLUOoqxdY/SlfElv1UtyI/AAAAAAAAe50/AxzH_RjhgKc/s1600-h/sipgate_account_data.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5356966437564121730" src="http://4.bp.blogspot.com/_lBJLUOoqxdY/SlfEl7CzAoI/AAAAAAAAe58/RbCL_TclT_I/s320/sipgate_server_data.png" style="cursor: pointer; display: block; height: 92px; margin: 0px auto 10px; text-align: center; width: 298px;" /&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;/div&gt;&lt;span style="color: #0000ee;"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5356978032744511010" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SlfPI2dOtiI/AAAAAAAAe6s/4htHrDkyfqU/s320/XSPhone_settings.png.png" style="cursor: pointer; float: right; height: 320px; margin: 0px 0px 10px 10px; width: 214px;" /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;Now go to your XSPhone settings (you can find them in the iPhone settings menu at the bottom, where the settings of any installed app can be configured) and enter the gathered credentials.&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Number 1 is your user name (corresponds to your sipgate SIP-ID).&lt;/li&gt;
&lt;li&gt;Number 2 is your password (corresponds to your SIP password).&lt;/li&gt;
&lt;li&gt;Number 3 is the VoIP server to register to (this is sipgate.de in my case).&lt;/li&gt;
&lt;li&gt;Leave the voicemail number empty (I couldn't get it working correctly with sipgate, it might work with other providers, though).&lt;/li&gt;
&lt;li&gt;Check the experimental option "Allow incoming calls" - at least with sipgate it works. The app needs to be running to accept incoming calls, certainly (Doh! :-).&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;After you configured the app, you can fire it up. If everything was configured correctly, you should see a screen which looks like this:&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;a href="http://1.bp.blogspot.com/_lBJLUOoqxdY/SlfI29HApII/AAAAAAAAe6U/up8WSEN6cm0/s1600-h/img_0076.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5356971128222950530" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/SlfI29HApII/AAAAAAAAe6U/up8WSEN6cm0/s320/img_0076.png" style="cursor: pointer; display: block; height: 320px; margin: 0px auto 10px; text-align: center; width: 214px;" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;In case of sipgate you can either try calling the test number (which is 10000), the echo service (which is 10005) or your mailbox (which is 50000). All of those numbers are free of charge when calling over sipgate.&lt;/div&gt;&lt;div style="text-align: left;"&gt;Unfortunately the mailbox button does not work correctly, but you can add an entry for your sipgate voicemail to your address book and add it to the "Favorites" tab from XSPhone. Calling this favorite works fine (I wonder what difference using the mailbox button makes).&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;div style="text-align: left;"&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5356978630460151922" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SlfPrpHvmHI/AAAAAAAAe68/M5EshCx97P8/s200/img_0078.png" style="cursor: pointer; display: inline; height: 200px; margin: 0px auto 10px; text-align: center; width: 134px;" /&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5356978617315776306" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/SlfPq4J4VzI/AAAAAAAAe60/yh8MVj_E8og/s200/img_0077.png" style="cursor: pointer; display: inline; height: 200px; margin: 0px auto 10px 20px; text-align: center; width: 134px;" /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;That's it, you are all set for VoIP calling (and being called) over sipgate.&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;There are some drawbacks, however, I need to tell you about this solution:&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;ul&gt;&lt;li&gt;You can only be called if the app is open.&lt;/li&gt;
&lt;li&gt;XSPhone only supports one single VoIP registrar at any time.&lt;/li&gt;
&lt;li&gt;XSPhone does not look as nice as some iPhone applications do (there is some ugly background picture I did not dare to make a screenshot of, when you are calling someone) - although all the other VoIP apps currently in the App Store are also not exactly beautiful either, and at least, this one is free of charge!&lt;/li&gt;
&lt;li&gt;No SIP-finetuning with proxy, STUN server, etc.&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;Apart from that: yep, that is a cool freebie!&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;I'd love to hear about your experience with XSPhone and your ISP (if you got it working, please include a short settings description for all the others out there) in the comments!&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;
Update (2009-09-16): as of now there is an application from sipgate available within the App Store (search for "sipgate") - currently only working with sipgate team edition - in some weeks a version for the other sipgate plans should be available.&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/cwoxODV-NSo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/2370747914728203989/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/07/free-voip-calls-with-sipgate-on-iphone.html#comment-form" title="2 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2370747914728203989?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2370747914728203989?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/07/free-voip-calls-with-sipgate-on-iphone.html" title="Free VoIP calls with sipgate on iPhone using XSPhone" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_lBJLUOoqxdY/SlfDiM_EnII/AAAAAAAAe5s/gSXcJh7_IRg/s72-c/sipgate_settings.png" height="72" width="72" /><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;CEIMR3oyeCp7ImA9WxJTEU0.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-8338211604301305386</id><published>2009-04-19T02:38:00.004+02:00</published><updated>2009-04-19T02:56:26.490+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-19T02:56:26.490+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="GAE" /><category scheme="http://www.blogger.com/atom/ns#" term="Circuit Breaker Pattern" /><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="App Engine SDK" /><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><title>Circuit Breaker Pattern on Google App Engine</title><content type="html">This week I came across the &lt;a href="http://devzone.zend.com/article/4434-Why-your-PHP-App-NEEDS-a-Circuit-Breaker"&gt;Circuit Breaker Pattern&lt;/a&gt;, which seems to be a good approach for handling one or more dependencies to external services in cloud/mashup applications.&lt;br /&gt;
As applications on GAE tend to have such dependencies, I wrote a memcached and datastore-backed Circuit Breaker Pattern implementation for easy and straightforward usage on GAE Java (sorry, no Python this time - but should actually be easy to adapt and I am glad to post a link here to anyone who migrates the Java implementation).&lt;br /&gt;
&lt;h4&gt;So what exactly is that Circuit Breaker Pattern I am talking about?!&lt;/h4&gt;Think of your GAE application as a washing machine (pretty profane I know) - and think of your external service you like to use with your GAE application as the water you need for getting your clothes clean (thats what a GAE application should do, at last). So if you start your washing machine without the water, your clothes probably won't get much cleaner than they were before you put them into the machine - or worse: your washing machine may break down because its running hot and your clothes burst into flames.&lt;br /&gt;
To minimize casualties on that scenario, the Circuit Breaker Pattern comes into play: it basically tracks the health of your external services (the water, electricity and detergent) and trips if they are not healthy or completely unavailable (e.g. your washing machine will only start if all "external services" are available and healthy).&lt;br /&gt;
&lt;h4&gt;Technical stuff&lt;/h4&gt;From a technical perspective, it basically tracks how often a service request failed in the past and prevents you from making unnecessary calls to that service in the future (or act accordingly by informing the visitor to come back later, sending you an email, kicking the service in the butt or [you name it]).&lt;br /&gt;
To make this a little more dynamic, the Circuit Breaker Pattern allows you to add some sort of fuzziness to your logic, by not shutting down your whole app when an arbitrary external service fails only once (even though that could be done achieved the pattern) but only after a specific given number of times (the failure threshold).&lt;br /&gt;
Also, the Circuit Breaker Pattern defines a time period (timeout) which needs to elapse until the next call to that external service is allowed to get through and check if the service is back up again.&lt;br /&gt;
&lt;h4&gt;So how does this look in your GAE code?&lt;/h4&gt;Java pseudocode:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;int failureThreshold = 3;
int defaultTimeout = 5; // seconds
ServiceCircuit ebayCircuit = CircuitBreaker
&amp;nbsp;&amp;nbsp; .getOrCreateServiceCircuit("ebay", failureThreshold,
&amp;nbsp;&amp;nbsp;&amp;nbsp; defaultTimeout);

if (CircuitBreaker.isClosed(ebayCircuit)) {
&amp;nbsp;&amp;nbsp;&amp;nbsp; // circuit is closed,
&amp;nbsp;&amp;nbsp;&amp;nbsp; // we are allowed to request the ebay service
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp; // Here comes the call to your external service
&amp;nbsp;&amp;nbsp;&amp;nbsp; boolean success = ?;
&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&amp;nbsp;&amp;nbsp;&amp;nbsp; if(success) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Service request returned successfully.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // inform the circuit breaker,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // the request was a success
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CircuitBreaker.success(ebayCircuit);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //do something with your data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.out.println(yourData);
&amp;nbsp;&amp;nbsp;&amp;nbsp; } else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Service failed
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // tell the circuit breaker, the request failed
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CircuitBreaker.fail(ebayCircuit);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // inform your visitor
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.out.println("ebay could not be reached!");
&amp;nbsp;&amp;nbsp;&amp;nbsp; }
} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp; // circuit was not closed, so we are not allowed
&amp;nbsp;&amp;nbsp;&amp;nbsp; // to make a call to
&amp;nbsp;&amp;nbsp;&amp;nbsp; // our external service for now.
&amp;nbsp;&amp;nbsp;&amp;nbsp; // We need to wait for the timeout to
&amp;nbsp;&amp;nbsp;&amp;nbsp; // elapse
&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&amp;nbsp;&amp;nbsp;&amp;nbsp; // inform your visitor to come back later
&amp;nbsp;&amp;nbsp;&amp;nbsp; System.out.println("Outage: Please come back later!");
}
&lt;/pre&gt;&lt;br /&gt;
Thats more or less all the magic you need to add to your app to make use of the Circuit Breaker Pattern.&lt;br /&gt;
To use more than one service, you can either instantiate another circuit, or create a compound circuit (just change the ID from "ebay" to "ebay_and_anotherservice") to track the vitality of both services (depends on how they are needed: sequentially or parallel).&lt;br /&gt;
&lt;h4&gt;GWT sample application&lt;/h4&gt;Beside the code I also wrote a small GAE GWT sample application, which shows you how to use the Circuit Breaker Pattern and comes with some example code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_lBJLUOoqxdY/SepufTfn3bI/AAAAAAAAcUk/6IhD3bU28ao/s1600-h/circuit_breaker_pattern_screen_service_responded.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/SepufTfn3bI/AAAAAAAAcUk/6IhD3bU28ao/s320/circuit_breaker_pattern_screen_service_responded.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_lBJLUOoqxdY/SepuoGhgL2I/AAAAAAAAcUs/qqNd_E1bsls/s1600-h/circuit_breaker_pattern_screen_service_failed_once.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/SepuoGhgL2I/AAAAAAAAcUs/qqNd_E1bsls/s320/circuit_breaker_pattern_screen_service_failed_once.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_lBJLUOoqxdY/SepuqlMY5II/AAAAAAAAcU0/k_2dwuA68Qs/s1600-h/circuit_breaker_pattern_screen_circuit_tripped.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SepuqlMY5II/AAAAAAAAcU0/k_2dwuA68Qs/s320/circuit_breaker_pattern_screen_circuit_tripped.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h4&gt;Code&lt;/h4&gt;&lt;a href="http://uploads.feth.com/blog/CircuitBreakerPatternExample_0.1.zip"&gt;Download the complete eclipse project&lt;/a&gt;. This includes the GWT sample and a whole lot of comments.&lt;br /&gt;
&lt;br /&gt;
The Pattern implementation itself is in two files:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;/CircuitBreakerExample/src/com/feth/appengine/circuitbreaker/CircuitBreaker.java&lt;/li&gt;
&lt;li&gt;/CircuitBreakerExample/src/com/feth/appengine/circuitbreaker/ServiceCircuit.java&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Please let me know what you think, if the Circuit Breaker is helpful or not, if you have additions or comments or encounter bugs...&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/v42jeMdv5cM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/8338211604301305386/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2009/04/circuit-breaker-pattern-on-google-app.html#comment-form" title="1 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/8338211604301305386?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/8338211604301305386?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2009/04/circuit-breaker-pattern-on-google-app.html" title="Circuit Breaker Pattern on Google App Engine" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_lBJLUOoqxdY/SepufTfn3bI/AAAAAAAAcUk/6IhD3bU28ao/s72-c/circuit_breaker_pattern_screen_service_responded.png" height="72" width="72" /><thr:total>1</thr:total><georss:featurename>Stuttgart, Germany</georss:featurename><georss:point>48.7771056 9.1807688</georss:point><georss:box>48.6639821 8.947309299999999 48.8902291 9.4142283</georss:box></entry><entry gd:etag="W/&quot;D0cMSH48fyp7ImA9WxRVFk0.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-1516592747800469265</id><published>2008-04-30T13:07:00.014+02:00</published><updated>2008-11-13T20:38:09.077+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-13T20:38:09.077+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="captcha" /><category scheme="http://www.blogger.com/atom/ns#" term="GAE" /><category scheme="http://www.blogger.com/atom/ns#" term="App Engine SDK" /><category scheme="http://www.blogger.com/atom/ns#" term="howto" /><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>Using reCAPTCHA with Google App Engine</title><content type="html">We have no image creation capabilities in Google App Engine (yet). So if you want to display a CAPTCHA (&lt;span style="font-weight: bold;"&gt;C&lt;/span&gt;ompletely &lt;span style="font-weight: bold;"&gt;A&lt;/span&gt;utomated &lt;span style="font-weight: bold;"&gt;P&lt;/span&gt;ublic &lt;span style="font-weight: bold;"&gt;T&lt;/span&gt;uring test to tell &lt;span style="font-weight: bold;"&gt;C&lt;/span&gt;omputers and &lt;span style="font-weight: bold;"&gt;H&lt;/span&gt;umans &lt;span style="font-weight: bold;"&gt;A&lt;/span&gt;part) within a GAE application, you need to do this by using an external service provider.&lt;br /&gt;&lt;br /&gt;One of these providers is &lt;a href="http://recaptcha.net/"&gt;reCAPTCHA&lt;/a&gt; - and the nice thing about reCAPTCHA is, that &lt;a href="http://recaptcha.net/learnmore.html"&gt;your visitors are helping to digitize books&lt;/a&gt; by solving the CAPTCHA.&lt;br /&gt;&lt;br /&gt;So how do we start?&lt;br /&gt;At first, you should &lt;a href="http://recaptcha.net/api/getkey"&gt;get a service key&lt;/a&gt; on recaptcha.net.&lt;br /&gt;After you registered, you can create an unlimited number of service keys for your domain.&lt;br /&gt;Note: If you want to use reCAPTCHA with your dev environment, you need to get keys for localhost.&lt;br /&gt;After generating one pair of keys, the screen should look like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lBJLUOoqxdY/SBhXGtPCKWI/AAAAAAAAAOE/m9wzHNAB51A/s1600-h/recaptcha_yourdomain.PNG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SBhXGtPCKWI/AAAAAAAAAOE/m9wzHNAB51A/s400/recaptcha_yourdomain.PNG" alt="" id="BLOGGER_PHOTO_ID_5194997942905350498" border="0" /&gt;&lt;/a&gt;Write down your public and private key - we will need them later. If you forget them, you can return to recaptcha.net to find them out. Don't tell anyone your private key.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SBhqddPCKbI/AAAAAAAAAOs/F_Lvw7wbT_4/s1600-h/helloworld_package_structure.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SBhqddPCKbI/AAAAAAAAAOs/F_Lvw7wbT_4/s320/helloworld_package_structure.png" alt="" id="BLOGGER_PHOTO_ID_5195019224468302258" border="0" /&gt;&lt;/a&gt;After having registered for the service, you should get a simple service class I &lt;a href="http://pypi.python.org/pypi/recaptcha-client"&gt;adapted from python.org&lt;/a&gt; to GAE. You can &lt;a href="http://dev.feth.com/Python/Google%20App%20Engine/recaptcha/captcha.source"&gt;download the adapted class here&lt;/a&gt;. Rename it to captcha.py and copy it somewhere (I prefer putting it in a subdirectory named recaptcha/client/ - remember to add an empty __init__.py in every directory if you do this) within your App Engine project.&lt;br /&gt;&lt;br /&gt;For demonstration, I will be extending the helloworld example from GAE.&lt;br /&gt;&lt;br /&gt;Go to your helloworld.py and import the captcha and environ modules:&lt;pre&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;from &lt;/span&gt;os &lt;span style="color: rgb(51, 102, 255);"&gt;import &lt;/span&gt;environ&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;from &lt;/span&gt;recaptcha.client &lt;span style="color: rgb(51, 102, 255);"&gt;import &lt;/span&gt;captcha&lt;/pre&gt;Then go to your &lt;span style="font-family:courier new;"&gt;MainPage&lt;/span&gt; controller and add the following lines:&lt;pre&gt;chtml = captcha.displayhtml(&lt;br /&gt;  public_key = &lt;span style="color: rgb(51, 204, 0);"&gt;"YOUR-PUBLIC-KEY"&lt;/span&gt;,&lt;br /&gt;  use_ssl = &lt;span style="color: rgb(51, 102, 255);"&gt;False&lt;/span&gt;,&lt;br /&gt;  error = &lt;span style="color: rgb(51, 102, 255);"&gt;None&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;template_values = {&lt;br /&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;   ...&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;   'captchahtml': chtml&lt;/span&gt;&lt;br /&gt;}&lt;/pre&gt;Exchange &lt;span style="font-family:courier new;"&gt;YOUR-PUBLIC-KEY&lt;/span&gt; with your public key - if you don't, you'll get a message:&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;"Invalid public key. Make sure you copy and pasted it correctly."&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;Now switch to your HTML template and add the &lt;span style="font-family:courier new;"&gt;captchahtml&lt;/span&gt; output &lt;span style="font-weight: bold;"&gt;within &lt;/span&gt;your form tags:&lt;pre&gt;&amp;lt;form&amp;gt;&lt;br /&gt;  ...&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    {{ captchahtml }}&lt;/span&gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;When you open the site in your browser, you'll see the recaptcha iframe:&lt;/p&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lBJLUOoqxdY/SBhhD9PCKYI/AAAAAAAAAOU/78Lp0rAsBjk/s1600-h/recaptcha_example_box.PNG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_lBJLUOoqxdY/SBhhD9PCKYI/AAAAAAAAAOU/78Lp0rAsBjk/s320/recaptcha_example_box.PNG" alt="" id="BLOGGER_PHOTO_ID_5195008890776988034" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Now when the form gets submitted, we need to check if the captcha input was correct. Therefore, we need to change into the &lt;span style="font-family:courier new;"&gt;post&lt;/span&gt; method of our &lt;span style="font-family:courier new;"&gt;Guestbook&lt;/span&gt; object and add some code:&lt;/p&gt;&lt;pre&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;def &lt;/span&gt;post(&lt;span style="font-style: italic;"&gt;self&lt;/span&gt;):&lt;br /&gt;  challenge = &lt;span style="font-style: italic; color: rgb(0, 0, 0);"&gt;self.&lt;/span&gt;request.get(&lt;span style="color: rgb(51, 204, 0);"&gt;'recaptcha_challenge_field'&lt;/span&gt;)&lt;br /&gt;  response  = &lt;span style="color: rgb(0, 0, 0); font-style: italic;"&gt;self&lt;/span&gt;.request.get(&lt;span style="color: rgb(51, 204, 0);"&gt;'recaptcha_response_field'&lt;/span&gt;)&lt;br /&gt;  remoteip  = environ[&lt;span style="color: rgb(51, 204, 0);"&gt;'REMOTE_ADDR'&lt;/span&gt;]&lt;br /&gt;&lt;br /&gt;  cResponse = captcha.submit(&lt;br /&gt;                 challenge,&lt;br /&gt;                 response,&lt;br /&gt;                 &lt;span style="color: rgb(51, 204, 0);"&gt;"YOUR-PRIVATE-KEY"&lt;/span&gt;,&lt;br /&gt;                 remoteip)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    if &lt;/span&gt;cResponse.is_valid:&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153);"&gt;        # response was valid&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153);"&gt;        # other stuff goes here&lt;br /&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;    &lt;span style="color: rgb(51, 102, 255);"&gt;else&lt;/span&gt;:&lt;br /&gt;  &lt;/span&gt;&lt;/span&gt;    &lt;span style="color: rgb(153, 153, 153);"&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;error = cResponse.error_code&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;    &lt;span style="color: rgb(153, 153, 153);"&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;...&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Exchange &lt;span style="font-family:courier new;"&gt;YOUR-PRIVATE-KEY&lt;/span&gt; with your previously generated private key.&lt;/p&gt;&lt;p&gt;The user inputs to the reCAPTCHA iframe will be validated together with the remote IP (your visitor's IP) and the challenge. We get a response from the reCAPTCHA API server and a &lt;span style="font-family:courier new;"&gt;RecaptchaResponse&lt;/span&gt; object will hold the answer.&lt;/p&gt;&lt;p&gt;The &lt;span style="font-family:courier new;"&gt;RecaptchaResponse&lt;/span&gt; object has two properties:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;span style="font-family:courier new;"&gt;is_valid&lt;/span&gt; is set to True if the test was successful (otherwise it'l bee False)&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;error_code&lt;/span&gt; will hold an &lt;a href="http://recaptcha.net/apidocs/captcha/"&gt;API error code&lt;/a&gt; if there was a problem.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Note:  In the Getting Started Guide for GAE, the form gets submitted to the Guestbook controller. Normally you would submit to the MainPage controller and pass the error code from the RecaptchaResponse object (if there is one) to the &lt;span style="font-family:courier new;"&gt;displayhtml&lt;/span&gt; method of the captcha class:&lt;/p&gt;&lt;pre&gt;chtml = captcha.displayhtml(&lt;br /&gt;  public_key = &lt;span style="color: rgb(51, 204, 0);"&gt;"YOUR-PUBLIC-KEY"&lt;/span&gt;,&lt;br /&gt;  use_ssl = &lt;span style="color: rgb(51, 102, 255);"&gt;False&lt;/span&gt;,&lt;br /&gt;  &lt;span style="color: rgb(255, 0, 0);"&gt;error = &lt;/span&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;cResponse.error_code&lt;/span&gt;)&lt;/pre&gt;&lt;p&gt;That will display a human readable error message to the user:&lt;/p&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SBhmGdPCKZI/AAAAAAAAAOc/JTuR2GDmQVg/s1600-h/recaptcha_example_box_wrong.PNG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SBhmGdPCKZI/AAAAAAAAAOc/JTuR2GDmQVg/s320/recaptcha_example_box_wrong.PNG" alt="" id="BLOGGER_PHOTO_ID_5195014431284799890" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;and lets your visitor redoing the CAPTCHA without losing his/her previously entered values in the other form fields.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Keep in mind: For every submitted CAPTCHA, a request to the reCAPTCHA server is made. This request is &lt;span style="font-weight: bold;"&gt;synchronous&lt;/span&gt;, so the response to your visitor will get delayed by the time it takes to fetch the response. If the reCAPTCHA server can not be reached, the error code &lt;span style="font-family:courier new;"&gt;recaptcha-not-reachable&lt;/span&gt; will be returned.&lt;br /&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/eC7x3FvKx9w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/1516592747800469265/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2008/04/using-recaptcha-with-google-app-engine.html#comment-form" title="25 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/1516592747800469265?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/1516592747800469265?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2008/04/using-recaptcha-with-google-app-engine.html" title="Using reCAPTCHA with Google App Engine" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_lBJLUOoqxdY/SBhXGtPCKWI/AAAAAAAAAOE/m9wzHNAB51A/s72-c/recaptcha_yourdomain.PNG" height="72" width="72" /><thr:total>25</thr:total></entry><entry gd:etag="W/&quot;D0cMSHo-cSp7ImA9WxRVFk0.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-2621797490584693787</id><published>2008-04-21T21:13:00.009+02:00</published><updated>2008-11-13T20:38:09.459+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-13T20:38:09.459+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="GAE" /><category scheme="http://www.blogger.com/atom/ns#" term="App Engine SDK" /><category scheme="http://www.blogger.com/atom/ns#" term="django" /><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>Using custom django template helpers with Google App Engine</title><content type="html">This is dedicated to all you &lt;a href="http://www.djangoproject.com/"&gt;django&lt;/a&gt; lovers out there. If you wondered how to use custom filter functions with your Google App Engine applications, here you go:&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SAztOhCtgcI/AAAAAAAAANg/COX9tYGpUxE/s1600-h/templatefilters.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SAztOhCtgcI/AAAAAAAAANg/COX9tYGpUxE/s320/templatefilters.png" alt="" id="BLOGGER_PHOTO_ID_5191785304095031746" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;&lt;br /&gt;Create a new file (I created mine in &lt;span style="font-style: italic;"&gt;common/templatefilters.py&lt;/span&gt;) to hold your template helper functions.&lt;br /&gt;&lt;br /&gt;In this file, we need to register our custom filters, so:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# import the webapp module&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;from &lt;/span&gt;google.appengine.ext &lt;span style="color: rgb(51, 102, 255);"&gt;import &lt;/span&gt;webapp&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# get registry, we need it to register our filter later.&lt;/span&gt;&lt;br /&gt;register = webapp.template.create_template_register()&lt;br /&gt;&lt;/pre&gt;After we got the registry, we now can define our custom function:&lt;br /&gt;&lt;pre&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;def &lt;/span&gt;truncate(value,maxsize,stopper = &lt;span style="color: rgb(51, 204, 0);"&gt;'...'&lt;/span&gt;):&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    """ truncates a string to a given maximum&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;        size and appends the stopper if needed """&lt;/span&gt;&lt;br /&gt;    stoplen = len(stopper)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    if &lt;/span&gt;len(value) &gt; maxsize &lt;span style="color: rgb(51, 102, 255);"&gt;and &lt;/span&gt;maxsize &gt; stoplen:&lt;br /&gt;       &lt;span style="color: rgb(51, 102, 255);"&gt;return &lt;/span&gt;value[:(maxsize-stoplen)] + stopper&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    else&lt;/span&gt;:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;       return &lt;/span&gt;value[:maxsize]&lt;/pre&gt;Then, we need to register our filter as following:&lt;br /&gt;&lt;pre&gt;register.filter(truncate)&lt;/pre&gt;Now go to your bootstrap file (the one with your main function - in my case &lt;span style="font-style: italic;"&gt;base.py&lt;/span&gt; - see image above) and add the library (if you used a different module than &lt;span style="font-style: italic;"&gt;common.templatefilters&lt;/span&gt;, you need to specifiy it here):&lt;br /&gt;&lt;pre&gt;webapp.template.register_template_library(&lt;br /&gt; &lt;span style="color: rgb(51, 204, 0);"&gt;'common.templatefilters'&lt;/span&gt;)&lt;/pre&gt;After adding this line of code, your template functions will get added to django and are available to use within your html templates:&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lBJLUOoqxdY/SAztrRCtgeI/AAAAAAAAANw/7K-zaw2KScY/s1600-h/truncate_example.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 341px; height: 25px;" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/SAztrRCtgeI/AAAAAAAAANw/7K-zaw2KScY/s400/truncate_example.png" alt="" id="BLOGGER_PHOTO_ID_5191785798016270818" border="0" /&gt;&lt;/a&gt;This will truncate the variable &lt;span style="font-family:courier new;"&gt;somevar &lt;/span&gt;to 20 characters and add "..." to the end, if needed.&lt;br /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/HrmHTw9COiY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/2621797490584693787/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2008/04/using-custom-django-template-helpers.html#comment-form" title="9 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2621797490584693787?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/2621797490584693787?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2008/04/using-custom-django-template-helpers.html" title="Using custom django template helpers with Google App Engine" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_lBJLUOoqxdY/SAztOhCtgcI/AAAAAAAAANg/COX9tYGpUxE/s72-c/templatefilters.png" height="72" width="72" /><thr:total>9</thr:total></entry><entry gd:etag="W/&quot;CU4GQnk_eCp7ImA9WxZbE0U.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-5405819694095326879</id><published>2008-04-16T16:08:00.003+02:00</published><updated>2008-04-17T00:12:03.740+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-04-17T00:12:03.740+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="GAE" /><category scheme="http://www.blogger.com/atom/ns#" term="App Engine SDK" /><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="ER-model" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><category scheme="http://www.blogger.com/atom/ns#" term="entity relationship model" /><title>ER-Modeling with Google App Engine (updated)</title><content type="html">&lt;p&gt;&lt;span style="font-size:85%;"&gt;3rd updated version with &lt;a href="http://groups.google.com/group/google-appengine/browse_frm/thread/aa26d529d66ac376"&gt;a lot of input&lt;/a&gt; from the GAE group. Thanks to everyone!&lt;/span&gt;&lt;/p&gt;&lt;p&gt;ER-Modeling with Google App Engine is somewhat different to "normal" modeling for a relational database.&lt;/p&gt;&lt;p&gt;Here is a small tutorial on how to create well-known relationship models One-to-One (1:1), One-to-Many (1:n), Many-to-Many (m:n) and a special one (Cascading relations) with  GAE:&lt;/p&gt; &lt;p&gt;We need the following Model classes for our example. Also if you want to run it, don't forget to import the sys module - we need it for printing to stdout:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;import &lt;/span&gt;sys &lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;#import sys module for printing to stdout&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;class &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;Car&lt;/span&gt;(db.Model):&lt;br /&gt;   brand  =  db.StringProperty(required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;   wheels =  db.ListProperty(db.Key)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;class &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;Human&lt;/span&gt;(db.Model):&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;name    = db.StringProperty(required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;drives  = db.ReferenceProperty(reference_class=Car)&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;spouse  = db.SelfReferenceProperty()&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;owns    = db.ListProperty(db.Key)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;class &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;Wheel&lt;/span&gt;(db.Model):&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;isBroken =  db.BooleanProperty(default=&lt;span style="color: rgb(51, 102, 255);"&gt;False&lt;/span&gt;)&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;position =  db.StringProperty(choices=set([&lt;span style="color: rgb(51, 204, 0);"&gt;"left_front"&lt;/span&gt;,&lt;br /&gt;                        &lt;/code&gt;&lt;code&gt;                      &lt;/code&gt;&lt;code&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;"left_back"&lt;/span&gt;,&lt;br /&gt;                        &lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;                  "right_front"&lt;/span&gt;,&lt;br /&gt;                        &lt;/code&gt;&lt;code&gt;     &lt;/code&gt;&lt;code&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;                 "right_back"&lt;/span&gt;]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style="font-size:180%;"&gt;One-to-One (1:1)&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;A simple relationship between two entities.&lt;/p&gt;&lt;p&gt;Let's say a human called Jack drives one car.&lt;br /&gt;We can model this relationship as following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# one-to-one&lt;/span&gt;&lt;br /&gt;jack        = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Jack"&lt;/span&gt;)&lt;br /&gt;mercedes    = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Mercedes"&lt;/span&gt;)&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;jack.drives = mercedes.put()&lt;/span&gt;&lt;br /&gt;jack.put()&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print&lt;/span&gt; &gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Jack drives a "&lt;/span&gt;+jack.drives.brand&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# Jack drives a Mercedes&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you can see, we create a human called "Jack" and a car "Mercedes". After that we assign the car to the &lt;span style="font-family:courier new;"&gt;drives&lt;/span&gt;-property of Jack and save Jack.&lt;/p&gt;&lt;p&gt;Hint (Thanks to Miguel for pointing this out): be careful with 1:1 relationships done with &lt;span style="font-family:courier new;"&gt;ReferenceProperty&lt;/span&gt;-properties - you could easily write something like this:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;jack        = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Jack"&lt;/span&gt;)&lt;br /&gt;mike        = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Mike"&lt;/span&gt;)&lt;br /&gt;mercedes    = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Mercedes"&lt;/span&gt;)&lt;br /&gt;mercedesid  = mercedes.put()&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;jack.drives = mercedesid&lt;/span&gt;&lt;br /&gt;jack.put()&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;mike.drives = mercedesid&lt;/span&gt;&lt;br /&gt;mike.put()&lt;/code&gt;&lt;/pre&gt;which won't be a 1:1 relation any more. So if you really need to make sure that an entity is only referenced once, you need to do this by your code design (by searching within your existing model kinds and throwing exceptions).&lt;br /&gt;Even using a cascading-relationship with a parent entity (see later in this article) does not make sure, that there is only one car per human and therefore includes the same difficulty just the other way round.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:130%;"&gt;Special: self references&lt;/span&gt;&lt;/p&gt;Now a special sort of references are self-references. That means a Model references an entity of the same Model class (e.g. a Human references a Human):&lt;pre&gt;&lt;code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# one-to-one self&lt;/span&gt;&lt;br /&gt;bob     = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Bob"&lt;/span&gt;)&lt;br /&gt;jane    = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Jane"&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;bob.spouse = jane.put()&lt;/span&gt;&lt;br /&gt;bob.put()&lt;br /&gt;b_spouse = Human.get(bob.spouse.key())&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Bob's spouse is "&lt;/span&gt;+b_spouse.name&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# Bob's spouse is Jane&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;We created two humans (Bob and Jane) and set Bob's &lt;span style="font-family:courier new;"&gt;spouse&lt;/span&gt;-property to reference Jane.&lt;br /&gt;Now we can easily find out who Bob's spouse is.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;span style="font-size:130%;"&gt;Special: mutual references&lt;/span&gt;&lt;/p&gt;But if Bob is married to Jane, Jane is also married to Bob, isn't she?&lt;br /&gt;So let's do this semantically correct:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# one-to-one self mutual&lt;/span&gt;&lt;br /&gt;bob     = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Bob"&lt;/span&gt;)&lt;br /&gt;jane    = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Jane"&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;bob.spouse  = jane.put()&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;jane.spouse = bob.put()&lt;/span&gt;&lt;br /&gt;jane.put()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;code&gt;j_spouse = Human.get(jane.spouse.key())&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Jane's spouse is "&lt;/span&gt;+j_spouse.name&lt;br /&gt;&lt;/code&gt;&lt;code&gt;b_spouse = Human.get(bob.spouse.key())&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Bob's spouse is "&lt;/span&gt;+b_spouse.name&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# Jane's spouse is Bob&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# Bob's spouse is Jane&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Be careful to check if reflexive references are semantically allowed or not – in our case it wouldn't be valid if bob's spouse is himself.&lt;br /&gt;&lt;br /&gt;Also in a monogamous society, it wouldn't be valid if Bob is the spouse of more than one other Human entity (!)&lt;br /&gt;If you don't want this behavior, you need to prevent it by throwing exceptions. You could use the &lt;span style="font-family:courier new;"&gt;validator&lt;/span&gt;-parameter of every &lt;span style="font-family:courier new;"&gt;Property &lt;/span&gt;class.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:180%;"&gt;One-to-Many (1:n)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Sometimes we need to reference more than one entity from another.&lt;br /&gt;When modeling 1:n relationships, a special of the Model class comes in handy: parent models (or ancestors).&lt;br /&gt;Let's think of a car having four wheels. Those wheels belong exactly to one car, so we define the car as parent for every wheel we create:&lt;span style="font-size:180%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# one-to-many using parent&lt;/span&gt;&lt;br /&gt;bmw = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"BMW"&lt;/span&gt;)&lt;br /&gt;bmw.put()&lt;br /&gt;&lt;br /&gt;lf = Wheel(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=bmw&lt;/span&gt;,position=&lt;span style="color: rgb(51, 204, 0);"&gt;"left_front"&lt;/span&gt;)&lt;br /&gt;lf.put()&lt;br /&gt;&lt;br /&gt;lb = Wheel(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=bmw&lt;/span&gt;,position=&lt;span style="color: rgb(51, 204, 0);"&gt;"left_back"&lt;/span&gt;)&lt;br /&gt;lb.put()&lt;br /&gt;&lt;br /&gt;rf = Wheel(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=bmw&lt;/span&gt;,position=&lt;span style="color: rgb(51, 204, 0);"&gt;"right_front"&lt;/span&gt;)&lt;br /&gt;rf.put()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;code&gt;&lt;span&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# uh, snap, the 4th wheel is broken!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;rb = Wheel(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=bmw&lt;/span&gt;,position=&lt;span style="color: rgb(51, 204, 0);"&gt;"right_back"&lt;/span&gt;,isBroken=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;rb.put()&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# from car to wheels&lt;/span&gt;&lt;br /&gt;bmwWheels = Wheel.all().ancestor(bmw)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"The BMW has the wheels: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;wheel &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;bmwWheels:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"- "&lt;/span&gt;+wheel.position&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# The BMW has the wheels: &lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - left_front&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - right_back&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - right_front&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - left_back&lt;/span&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# from wheel to car&lt;/span&gt;&lt;br /&gt;brokenWheels = Wheel.gql(&lt;span style="color: rgb(51, 204, 0);"&gt;"WHERE isBroken = :broken"&lt;/span&gt;,&lt;br /&gt;broken=True)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"The following cars are broken: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;wheel &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;brokenWheels:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"- "&lt;/span&gt;+wheel.parent().brand&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# The following cars are broken:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - BMW&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We can see: it is easy to get the wheels for a car and it is also possible to get the car a certain wheel belongs to. Also an entity can only have one parent at a time - so we made sure, that a wheel is not used by more than one car at the same time. You could even add some spare wheels to a car (there is no way to define a maximum number an entity can be used as parent entity).&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:130%;"&gt;The other way round&lt;/span&gt;&lt;/p&gt;&lt;p&gt;It is also possible to do this the other way round. Lets say we have an additional Model like this:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;class &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;OwnedCar&lt;/span&gt;(db.Model):&lt;br /&gt;   brand  =  db.StringProperty(required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;   owner  =  db.ReferenceProperty(Human, required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;then we could add cars to a person as following:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;paul = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Paul"&lt;/span&gt;)&lt;br /&gt;paul.put()&lt;br /&gt;&lt;br /&gt;pauls_bmw = OwnedCar(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"BMW"&lt;/span&gt;, &lt;span style="color: rgb(255, 0, 0);"&gt;owner=paul&lt;/span&gt;)&lt;br /&gt;pauls_bmw.put()&lt;br /&gt;&lt;br /&gt;pauls_mercedes = OwnedCar(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Mercedes"&lt;/span&gt;, &lt;span style="color: rgb(255, 0, 0);"&gt;owner=paul&lt;/span&gt;)&lt;br /&gt;pauls_mercedes.put()&lt;br /&gt;&lt;br /&gt;pauls_cars = &lt;span style="color: rgb(255, 0, 0);"&gt;paul.ownedcar_set&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Paul's cars: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;car &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;pauls_cars:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"- "&lt;/span&gt;+car.brand&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# Paul's cars:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - BMW&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - Mercedes&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;This makes for example sure, that one car is only owned by one human at a time.&lt;/p&gt;&lt;p&gt;Also notice the part on how to get Paul's cars. You don't even need to create a GQL query - the &lt;span style="font-family:courier new;"&gt;property &lt;/span&gt;&lt;span style="font-style: italic;"&gt;modelname_set&lt;/span&gt; holds the references. You can find &lt;a href="http://code.google.com/appengine/docs/datastore/entitiesandmodels.html"&gt;more about this in the docs&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Thanks to Aprigio for his input!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:130%;"&gt;Special: One-to-Many using a list&lt;/span&gt;&lt;/p&gt;&lt;p&gt;You can also create 1:n relationships using a list:&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# one-to-many using list&lt;/span&gt;&lt;br /&gt;dodge = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Dodge"&lt;/span&gt;)&lt;br /&gt;w1 = Wheel(position=&lt;span style="color: rgb(51, 204, 0);"&gt;"left_front"&lt;/span&gt;)&lt;br /&gt;w2 = Wheel(position=&lt;span style="color: rgb(51, 204, 0);"&gt;"left_back"&lt;/span&gt;)&lt;br /&gt;w3 = Wheel(position=&lt;span style="color: rgb(51, 204, 0);"&gt;"right_front"&lt;/span&gt;)&lt;br /&gt;w4 = Wheel(position=&lt;span style="color: rgb(51, 204, 0);"&gt;"right_back"&lt;/span&gt;)&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;dodge.wheels = [w1.put(),w2.put(),w3.put(),w4.put()]&lt;/span&gt;&lt;br /&gt;dodge.put()&lt;br /&gt;&lt;br /&gt;dodgeWheels = Wheel.get(dodge.wheels)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"The Dodge has the wheels: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;wheel &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;dodgeWheels:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"-"&lt;/span&gt;+wheel.position&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# The Dodge has the wheels: &lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# -left_front&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# -left_back&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# -right_front&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# -right_back&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;but:&lt;/span&gt; &lt;span style="color: rgb(255, 0, 0);"&gt;be careful, this model does not make sure, a wheel does belong to exactly one car! You could reference a wheel from more than one car, which would be semantically wrong in our example. So rather use this model for n:m relationships!&lt;br /&gt;&lt;/span&gt;  &lt;span style="font-size:180%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:180%;"&gt;Many-to-Many (m:n)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;For creating m:n relationships, use the following model:&lt;span style="font-size:180%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# many-to-many using list+db.Key&lt;/span&gt;&lt;br /&gt;jack    = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Jack"&lt;/span&gt;)&lt;br /&gt;bob     = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Bob"&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;vw      = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"VW"&lt;/span&gt;)&lt;br /&gt;chevy   = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Chevy"&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;carpool = [vw.put(),chevy.put()]&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;jack.owns = carpool&lt;/span&gt;&lt;br /&gt;jack.put()&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;bob.owns = carpool&lt;/span&gt;&lt;br /&gt;bob.put()&lt;br /&gt;&lt;br /&gt;chrysler = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Chrysler"&lt;/span&gt;)&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;jack.owns.append(chrysler.put())&lt;/span&gt;&lt;br /&gt;jack.put()&lt;br /&gt;&lt;br /&gt;jackOwns = Car.get(jack.owns)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Jack owns: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;car &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;jackOwns:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"- "&lt;/span&gt;+car.brand&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# Jack owns: &lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# - VW&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# - Chevy&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# - Chrysler&lt;/span&gt;&lt;code&gt;&lt;br /&gt;&lt;br /&gt;whoOwnsTheChevy = Human.gql(&lt;span style="color: rgb(51, 204, 0);"&gt;"WHERE owns = :car"&lt;/span&gt;,car=chevy)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"These humans own the Chevy: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;who &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;whoOwnsTheChevy:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"- "&lt;/span&gt;+who.name&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# These humans own the Chevy: &lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - Jack&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# - Bob&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;First we create our two guys Jack and Bob. Then they decide on sharing cars. Their carpool consists of a VW and a Chevy. After that, Jack decides on buying an additional car for himself (the Chrysler).&lt;/p&gt;&lt;p&gt;When adding to a list like in this example, there is no check if the value already is present within the list - so it would be possible to reference the same entity multiple times. If you want to prevent this, you need to run a function on the list, which makes sure there are only unique entities:&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;def &lt;/span&gt;unique(lst):&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;d = {}&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;item &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;lst:&lt;br /&gt;&lt;/code&gt;&lt;code&gt;        &lt;/code&gt;&lt;code&gt;d[item] = 1&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;return &lt;/span&gt;d.keys()&lt;br /&gt;&lt;br /&gt;jeep = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Jeep"&lt;/span&gt;)&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;jack.owns = unique(jack.owns + [jeep.put()])&lt;/span&gt;&lt;br /&gt;jack.put()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;You could also check to content of the list before adding an entity, if it is already referenced.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Most flexible:  using mapping entities&lt;/span&gt;&lt;br /&gt;&lt;p&gt;It might be a better idea to implement a different entity to represent the relationship&lt;br /&gt;between a human and the cars they own.&lt;br /&gt;The reason for this is that you could add more fields to this which may be beneficial later in a query. Lets say we add a bought field that contains the date the car was bough. Then one could get cars owned by Jack that he bought after a certain date.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;class &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;CarOwner&lt;/span&gt;(db.Model):&lt;br /&gt; car    = db.Reference(Car, required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt; owner  = db.Reference(Human, required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt; bought = db.DateProperty(auto_now_add=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153);"&gt;  @staticmethod&lt;/span&gt;&lt;br /&gt; &lt;span style="color: rgb(51, 102, 255);"&gt;def &lt;/span&gt;get_owner_cars(human, bought):&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;        """Returns the cars that the given human&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;           bought before a specified date"""&lt;/span&gt;&lt;br /&gt;     &lt;span style="color: rgb(51, 102, 255);"&gt;if not&lt;/span&gt; human: &lt;span style="color: rgb(51, 102, 255);"&gt;return &lt;/span&gt;[]&lt;br /&gt;     query = &lt;/code&gt;&lt;code&gt;db.Query(CarOwner)&lt;br /&gt;     query.filter(&lt;/code&gt;&lt;code&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;'owner ='&lt;/span&gt;,human)&lt;br /&gt;     query&lt;/code&gt;&lt;code&gt;.filter(&lt;span style="color: rgb(51, 204, 0);"&gt;'bought &gt;= '&lt;/span&gt;,bought)&lt;/code&gt;&lt;code&gt;                            &lt;br /&gt;     &lt;span style="color: rgb(51, 102, 255);"&gt;return &lt;/span&gt;[entry.car &lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;entry &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;query]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;also we'd like to add a function to the Car-Model, so that it looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;class &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;Car&lt;/span&gt;(db.Model):&lt;br /&gt;   brand  = db.StringProperty(required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;   wheels = db.ListProperty(db.Key)&lt;br /&gt;&lt;br /&gt;   &lt;span style="color: rgb(51, 102, 255);"&gt;def &lt;/span&gt;human_owns(&lt;span style="font-style: italic;"&gt;self&lt;/span&gt;, human):&lt;br /&gt;       &lt;span style="color: rgb(51, 204, 0);"&gt;"""Returns true if the given human owns this car."""&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(51, 102, 255);"&gt;   if not&lt;/span&gt; human: return &lt;span style="color: rgb(51, 102, 255);"&gt;False&lt;/span&gt;&lt;br /&gt;       query = db.Query(CarOwner)&lt;br /&gt;       query.filter(&lt;span style="color: rgb(51, 204, 0);"&gt;'car ='&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;self&lt;/span&gt;)&lt;br /&gt;       query.filter(&lt;span style="color: rgb(51, 204, 0);"&gt;'owner ='&lt;/span&gt;, human)&lt;br /&gt;    &lt;span style="color: rgb(51, 102, 255);"&gt;   return &lt;/span&gt;query.get()&lt;/code&gt;&lt;/pre&gt;When we have the Models defined like that, we can go on using them:&lt;pre&gt;&lt;code&gt;max = Human(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Max"&lt;/span&gt;)&lt;br /&gt;max.put()&lt;br /&gt;saab = Car(brand=&lt;span style="color: rgb(51, 204, 0);"&gt;"Saab"&lt;/span&gt;)&lt;br /&gt;saab.put()&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;ownership = CarOwner(car=saab,owner=max)&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;ownership.put()&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;import &lt;/span&gt;datetime&lt;br /&gt;delta = datetime.timedelta(days=-1)&lt;br /&gt;yesterday = (datetime.datetime.utcnow() + delta)&lt;br /&gt;&lt;br /&gt;maxs_cars = CarOwner.get_owner_cars(max,yesterday)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Max's cars since yesterday: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;car &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;maxs_cars:&lt;br /&gt;   &lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"- "&lt;/span&gt;+car.brand&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# Max's cars since yesterday:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# - Saab&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;max_owns_saab = &lt;span style="color: rgb(0, 0, 0);"&gt;bool&lt;/span&gt;(saab.human_owns(max))&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"Max owns the Saab: "&lt;/span&gt;+str(max_owns_saab)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(153, 153, 153); font-style: italic;"&gt;# Max owns the Saab: True&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see, the additional property on the mapping entity comes in handy when selecting the cars Max bought since yesterday.&lt;br /&gt;And also the method on the car-Model is pretty neat to bundle functionality related to a model within it.&lt;/p&gt;&lt;p&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;Note: you can use this sort of referencing also use for 1:1 and 1:n relationships. You just need to make sure, that either one referenced entity (1:n) is unique, or both (the combination of those - for 1:1)&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Thanks to Brian, who brought this up!&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:180%;"&gt;Cascading relations&lt;/span&gt;&lt;/p&gt;With the &lt;span style="font-family:courier new;"&gt;parent&lt;/span&gt;-property, cascading relationships are possible. This is very useful, if you have a folder-like structure (e.g. categories and products of an online shop). Let's have a look on how to do this:&lt;pre&gt;&lt;code&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;import &lt;/span&gt;sys&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;class &lt;/span&gt;Category(db.Model):&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;name     = db.StringProperty(required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;class &lt;/span&gt;Product(db.Model):&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;name       = db.StringProperty(required=&lt;span style="color: rgb(51, 102, 255);"&gt;True&lt;/span&gt;)&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;price      = db.FloatProperty()&lt;br /&gt;   categories = db.ListProperty(db.Key)&lt;br /&gt;&lt;br /&gt;root    = Category(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Products"&lt;/span&gt;)&lt;br /&gt;root.put()&lt;br /&gt;tech    = Category(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=root&lt;/span&gt;,name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Tech stuff"&lt;/span&gt;).put()&lt;br /&gt;books   = Category(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=root&lt;/span&gt;,name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Books"&lt;/span&gt;)&lt;br /&gt;books.put()&lt;br /&gt;fantasy = Category(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=books&lt;/span&gt;,name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Fantasy"&lt;/span&gt;)&lt;br /&gt;fantasy.put()&lt;br /&gt;scifi   = Category(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=books&lt;/span&gt;,name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Science Fiction"&lt;/span&gt;)&lt;br /&gt;scifi.put()&lt;br /&gt;scomics = Category(&lt;span style="color: rgb(255, 0, 0);"&gt;parent=scifi&lt;/span&gt;,name=&lt;span style="color: rgb(51, 204, 0);"&gt;"SciFi comics"&lt;/span&gt;)&lt;br /&gt;scomics.put()&lt;br /&gt;&lt;br /&gt;somebook = Product(&lt;span style="color: rgb(255, 0, 0);"&gt;&lt;/span&gt;name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Some book"&lt;/span&gt;)&lt;br /&gt;somebook.categories.append(books.key())&lt;br /&gt;somebook.price = &lt;span style="color: rgb(204, 0, 0);"&gt;9.99&lt;/span&gt;&lt;br /&gt;somebook.put()&lt;br /&gt;&lt;br /&gt;lotr = Product(name=&lt;span style="color: rgb(51, 204, 0);"&gt;"Lord Of The Rings"&lt;/span&gt;)&lt;br /&gt;lotr.categories.append(fantasy.key())&lt;br /&gt;lotr.price = &lt;span style="color: rgb(204, 0, 0);"&gt;29.99&lt;/span&gt;&lt;br /&gt;lotr.put()&lt;br /&gt;&lt;br /&gt;allFantasyBooks = Product.gql(&lt;span style="color: rgb(51, 204, 0);"&gt;"WHERE categories = :cat"&lt;/span&gt;,&lt;br /&gt;                  cat=fantasy)&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"All fantasy books: "&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;for &lt;/span&gt;book &lt;span style="color: rgb(51, 102, 255);"&gt;in &lt;/span&gt;allFantasyBooks:&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;"- "&lt;/span&gt;+book.name&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;path = []&lt;br /&gt;p = scomics&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;while &lt;/span&gt;p.parent():&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;path.append(p.name)&lt;br /&gt;&lt;/code&gt;&lt;code&gt;    &lt;/code&gt;&lt;code&gt;p = p.parent()&lt;br /&gt;&lt;br /&gt;path.reverse()&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;print &lt;/span&gt;&gt;&gt; sys.stdout, &lt;span style="color: rgb(51, 204, 0);"&gt;" &gt; "&lt;/span&gt;.join(path)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;&lt;/span&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;&lt;/span&gt;&lt;span style="font-style: italic; color: rgb(153, 153, 153);"&gt;# Books &gt; Science Fiction &gt; SciFi comics&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now getting all books in a category is pretty easy, huh? And also generating a breadcrumb-like "where am I" is simple by iterating through all parent entities for a given entity (here it is the science fiction comics &lt;span style="font-family:courier new;"&gt;Category&lt;/span&gt;).&lt;/p&gt;&lt;p style="color: rgb(255, 0, 0);"&gt;&lt;span style="font-weight: bold;"&gt;Attention:&lt;/span&gt; while the use of parent relationships if fine for the use case here, it would not be very efficient in the case where there are a lot of children associated with a parent.&lt;br /&gt;This is because this results in a very large entity group.&lt;br /&gt;The more entity groups your application has - that is, the more root entities there are - the more efficiently the datastore can distribute the entity groups across datastore nodes.&lt;br /&gt;Thus for efficiency you should avoid the case where there are a lot of children.&lt;br /&gt;According to the docs:&lt;/p&gt;&lt;p style="color: rgb(255, 0, 0);"&gt;&lt;/p&gt;&lt;blockquote style="font-style: italic;"&gt;"A good rule of thumb for entity groups is that they should be about the size of a&lt;br /&gt;single user's worth of data or smaller."&lt;/blockquote&gt; &lt;p&gt;&lt;/p&gt;&lt;p&gt;Thanks to Ben, Michael and Brian for pointing this out.&lt;br /&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/mby_EEynciE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/5405819694095326879/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2008/04/er-modeling-with-google-app-engine.html#comment-form" title="21 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5405819694095326879?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5405819694095326879?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2008/04/er-modeling-with-google-app-engine.html" title="ER-Modeling with Google App Engine (updated)" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><thr:total>21</thr:total></entry><entry gd:etag="W/&quot;D0cNQH87fip7ImA9WxRVFk0.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-7435733026834974721</id><published>2008-04-10T10:25:00.021+02:00</published><updated>2008-11-13T20:38:11.106+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-13T20:38:11.106+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="App Engine SDK" /><category scheme="http://www.blogger.com/atom/ns#" term="howto" /><category scheme="http://www.blogger.com/atom/ns#" term="eclipse" /><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine" /><category scheme="http://www.blogger.com/atom/ns#" term="PyDev" /><category scheme="http://www.blogger.com/atom/ns#" term="Google" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>Google App Engine &amp; eclipse (PyDev)</title><content type="html">&lt;span style="font-size:78%;"&gt;updated (Link to youtube video) thanks to mano marks.&lt;br /&gt;updated (Python debugging) thanks to mkielar.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:78%;"&gt;updated (CC-by license).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Update: If you like following a video more than reading some text, Mano Marks from Google created a &lt;a href="http://de.youtube.com/watch?v=e1dtyQ6wqzc"&gt;screencast about how to get started with App Engine&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Since Google published their &lt;a href="http://code.google.com/appengine/"&gt;App Engine&lt;/a&gt;, I am highly interested in it. Too bad I didn't get an App Engine account, but at least I am able to test it locally...&lt;br /&gt;As a fan of eclipse and being new to Python development, I searched for an eclipse Python extension and finally found &lt;a href="http://pydev.sourceforge.net/"&gt;PyDev sourceforge project&lt;/a&gt;.&lt;br /&gt;I know this is basic stuff, but it took me some minutes to find out how to get code completion working with Google App Engine.&lt;br /&gt;So here is a small HowTo for getting App Engine with eclipse and PyDev up and running:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Get &lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt; and install it.&lt;/li&gt;&lt;li&gt;Get &lt;a href="http://www.eclipse.org/"&gt;eclipse&lt;/a&gt; and unzip it.&lt;/li&gt;&lt;li&gt;Get the &lt;a href="http://code.google.com/appengine/downloads.html"&gt;Google App Engine SDK&lt;/a&gt; and install it.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Open eclipse and go to Help -&gt; Software Updates -&gt; Find and Install.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Choose "Search for new features to install" and click on "Next"&lt;/li&gt;&lt;li&gt;Click on "New remote site"&lt;/li&gt;&lt;/ul&gt;&lt;ol&gt;&lt;ul&gt;&lt;li&gt;choose a name&lt;/li&gt;&lt;li&gt;enter &lt;a href="http://pydev.sourceforge.net/updates/"&gt;http://pydev.sourceforge.net/updates/&lt;/a&gt; in the URL field&lt;/li&gt;&lt;li&gt;click "OK"&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_3SNqJ9-vI/AAAAAAAAAJw/mlJbnQhhO9s/s1600-h/PyDev_update_site_select.PNG"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_3SNqJ9-vI/AAAAAAAAAJw/mlJbnQhhO9s/s200/PyDev_update_site_select.PNG" alt="" id="BLOGGER_PHOTO_ID_5187533477896911602" border="0" /&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ol&gt;&lt;ul&gt;&lt;li&gt;Select the PyDev update site you just added&lt;/li&gt;&lt;li&gt;Click on "Finish"&lt;/li&gt;&lt;li&gt;After the update site has been searched, Choose to install the PyDev plugin (see image below)&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_3gzqJ9-wI/AAAAAAAAAJ4/NEnOcNWk8_E/s1600-h/pydev_features_install.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_3gzqJ9-wI/AAAAAAAAAJ4/NEnOcNWk8_E/s320/pydev_features_install.png" alt="" id="BLOGGER_PHOTO_ID_5187549523894729474" border="0" /&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Click on "Next"&lt;/li&gt;&lt;li&gt;accept the agreement&lt;/li&gt;&lt;li&gt;Click on "Finish"&lt;/li&gt;&lt;li&gt;Click on "Install All"&lt;/li&gt;&lt;li&gt;Choose "Yes" to restart eclipse&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;After eclipse has been started&lt;/li&gt;&lt;ul&gt;&lt;li&gt;choose Windows -&gt; Preferences to bring up the preferences dialog&lt;/li&gt;&lt;li&gt;Change to the "PyDev" -&gt; "Interpreter - Python" section to configure Python&lt;/li&gt;&lt;li&gt;Click on "New" right to the "Python interpreters" field to add an interpreter&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lBJLUOoqxdY/R_3i_KJ9-xI/AAAAAAAAAKA/KE-2dgKLbd0/s1600-h/new_python_interpreter.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/R_3i_KJ9-xI/AAAAAAAAAKA/KE-2dgKLbd0/s320/new_python_interpreter.png" alt="" id="BLOGGER_PHOTO_ID_5187551920486480658" border="0" /&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Search for the python.exe executable (normally in C:\Python25)&lt;/li&gt;&lt;/ul&gt;&lt;/ol&gt;&lt;ul&gt;&lt;li&gt;After you selected the executable, PyDev will search for libraries, and the following screen comes up:&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lBJLUOoqxdY/R_4BPKJ9-6I/AAAAAAAAALI/abscLhirUZo/s1600-h/pythonpath.PNG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/R_4BPKJ9-6I/AAAAAAAAALI/abscLhirUZo/s320/pythonpath.PNG" alt="" id="BLOGGER_PHOTO_ID_5187585180713221026" border="0" /&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ol&gt;&lt;ul&gt;&lt;li&gt;Normally, the preselection done by PyDev is fine, so click "OK" to accept the system pythonpath entries.&lt;/li&gt;&lt;li&gt;In the Preferences Window also click "OK" to confirm your changes.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Now we are ready to create our first python project.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Do a right-click in the Package Explorer and choose New -&gt; Other.&lt;/li&gt;&lt;li&gt;In the upcoming dialog select "Pydev project" from the Pydev folder.&lt;/li&gt;&lt;li&gt;Create a new helloworld project with (!important) Python 2.5 (see image below)&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lBJLUOoqxdY/R_4Bi6J9-7I/AAAAAAAAALQ/Y8JMGsSEBt0/s1600-h/helloworld.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/R_4Bi6J9-7I/AAAAAAAAALQ/Y8JMGsSEBt0/s320/helloworld.png" alt="" id="BLOGGER_PHOTO_ID_5187585520015637426" border="0" /&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;After clicking "Finish", eclipse should switch to the Pydev view.&lt;/li&gt;&lt;li&gt;Do a right click on your new project and choose "Properties"&lt;/li&gt;&lt;li&gt;In the properties dialog choose the "PyDev - PYTHONPATH" section to add the App Engine libraries (we need this for proper code assist)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Click on "Add source folder" to add the following folders (see image) from your google appengine folder:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_36GqJ9-2I/AAAAAAAAAKo/CUjkOq20MpQ/s1600-h/external_sources.PNG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 303px; height: 74px;" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_36GqJ9-2I/AAAAAAAAAKo/CUjkOq20MpQ/s400/external_sources.PNG" alt="" id="BLOGGER_PHOTO_ID_5187577338102938466" border="0" /&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Click "OK" to confirm your changes.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_3_iqJ9-4I/AAAAAAAAAK4/jmH1QswXT5Y/s1600-h/helloworld_structure.PNG"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_3_iqJ9-4I/AAAAAAAAAK4/jmH1QswXT5Y/s200/helloworld_structure.PNG" alt="" id="BLOGGER_PHOTO_ID_5187583316697414530" border="0" /&gt;&lt;/a&gt;For a quick start, you can work on the &lt;a href="http://code.google.com/appengine/docs/gettingstarted/"&gt;Getting Started tutorial&lt;/a&gt; avalibale.&lt;br /&gt;Use the "src" Folder as base folder for your project (see image to the right)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The last thing we need to do now is to add a user-defined starter for our Google App Engine development server.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Got to "Run" -&gt; "Open Run Dialog"&lt;/li&gt;&lt;li&gt;Choose "Python Run" and add a new launch configuration (document icon with the plus sign in the top left of the dialog)&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lBJLUOoqxdY/SAp5kRCtgWI/AAAAAAAAAMc/w90rlUzbGVg/s1600-h/python_run_helloworld.PNG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_lBJLUOoqxdY/SAp5kRCtgWI/AAAAAAAAAMc/w90rlUzbGVg/s320/python_run_helloworld.PNG" alt="" id="BLOGGER_PHOTO_ID_5191095184454943074" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Name your run configuration&lt;/li&gt;&lt;li&gt;As project choose your python GAE project.&lt;/li&gt;&lt;li&gt;As main module, enter the location of the "dev_appserver.py" script.&lt;/li&gt;&lt;li&gt;Change to the "Arguments" tab and enter "${project_loc}/src" as first argument.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lBJLUOoqxdY/SAp3_xCtgVI/AAAAAAAAAMU/FQiz0E6y71c/s1600-h/pargs.PNG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_lBJLUOoqxdY/SAp3_xCtgVI/AAAAAAAAAMU/FQiz0E6y71c/s320/pargs.PNG" alt="" id="BLOGGER_PHOTO_ID_5191093457878090066" border="0" /&gt;&lt;/a&gt;After this argument, you may add all available additional arguments listed on the &lt;a href="http://code.google.com/appengine/docs/thedevwebserver.html#Command_Line_Arguments"&gt;Dev Webserver documentation page&lt;/a&gt;. (here for example we changed the port where GAE is listening to 9999)&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;Click on "Apply" to save your changes&lt;/li&gt;&lt;li&gt;Click on "Run" to run your project&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;From now on you can run your project by selecting your confuguration from the "Run" dropdown and access your app on &lt;a href="http://localhost:9999/"&gt;http://localhost:9999/&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;If an error occurs, you can see it in the console view of eclipse and click on it to jump to the error location within your scripts.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lBJLUOoqxdY/SAp7GBCtgXI/AAAAAAAAAMk/Cn_D5pClT40/s1600-h/error_example.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/SAp7GBCtgXI/AAAAAAAAAMk/Cn_D5pClT40/s320/error_example.png" alt="" id="BLOGGER_PHOTO_ID_5191096863787155826" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rel="license" href="http://creativecommons.org/licenses/by/3.0/"&gt;&lt;img alt="Creative Commons License" style="border-width: 0pt;" src="http://i.creativecommons.org/l/by/3.0/88x31.png" /&gt;&lt;/a&gt;&lt;br /&gt;This work is licensed under a &lt;a rel="license" href="http://creativecommons.org/licenses/by/3.0/"&gt;Creative Commons Attribution 3.0 Unported License&lt;/a&gt;.&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/qyP5DAQAnuA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/7435733026834974721/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2008/04/google-app-engine-eclipse-pydev.html#comment-form" title="35 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/7435733026834974721?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/7435733026834974721?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2008/04/google-app-engine-eclipse-pydev.html" title="Google App Engine &amp; eclipse (PyDev)" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_lBJLUOoqxdY/R_3SNqJ9-vI/AAAAAAAAAJw/mlJbnQhhO9s/s72-c/PyDev_update_site_select.PNG" height="72" width="72" /><thr:total>35</thr:total></entry><entry gd:etag="W/&quot;D0cNQ3Y9fSp7ImA9WxRVFk0.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-8439245438646430885</id><published>2008-04-03T22:24:00.007+02:00</published><updated>2008-11-13T20:38:12.865+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-13T20:38:12.865+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="web 2.0" /><category scheme="http://www.blogger.com/atom/ns#" term="svg" /><category scheme="http://www.blogger.com/atom/ns#" term="inkscape" /><category scheme="http://www.blogger.com/atom/ns#" term="badge" /><title>web 2.0 SVG badge</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lBJLUOoqxdY/R_VE1niwL_I/AAAAAAAAAJo/PsJogpcbN88/s1600-h/web20_badge.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://2.bp.blogspot.com/_lBJLUOoqxdY/R_VE1niwL_I/AAAAAAAAAJo/PsJogpcbN88/s400/web20_badge.png" alt="" id="BLOGGER_PHOTO_ID_5185126233925496818" border="0" /&gt;&lt;/a&gt;Auf der Suche nach einem web 2.0 badge kam mir folgendes &lt;a href="http://photoshopit.wordpress.com/2006/09/19/a-photoshop-tutorial-on-starbursts-badges-web20-style/"&gt;Photoshop Tutorium&lt;/a&gt; unter die Finger.&lt;br /&gt;Da ich kein aktuelles Photoshop mein Eigen nenne, und eine &lt;a href="http://www.google.de/search?hl=de&amp;amp;q=%2Bweb+%2B2.0+%2Bbadge+filetype%3Asvg&amp;amp;btnG=Google-Suche&amp;amp;meta="&gt;Suche nach einem web 2.0 SVG badge bei Google&lt;/a&gt; nicht wirklich brauchbares Material zu Tage förderte, nahm ich mir geschwind &lt;a href="http://www.inkscape.org/"&gt;inkscape&lt;/a&gt; und baute das badge aus dem Tutorium nach Augenmaß nach.&lt;br /&gt;Das Ergebnis kann man links begutachten und &lt;a href="http://uploads.feth.com/blog/web20_badge.svg"&gt;hier herunterladen&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/2.0/de/"&gt;&lt;img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by-sa/2.0/de/88x31.png"/&gt;&lt;/a&gt;&lt;br/&gt;This work is licensed under a &lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/2.0/de/"&gt;Creative Commons Attribution-Share Alike 2.0 Germany License&lt;/a&gt;.&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/rt6_SVk4VKk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/8439245438646430885/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2008/04/web-20-svg-badge.html#comment-form" title="0 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/8439245438646430885?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/8439245438646430885?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2008/04/web-20-svg-badge.html" title="web 2.0 SVG badge" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_lBJLUOoqxdY/R_VE1niwL_I/AAAAAAAAAJo/PsJogpcbN88/s72-c/web20_badge.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEcASX0zcSp7ImA9WxZWGEs.&quot;"><id>tag:blogger.com,1999:blog-596650637928113520.post-5285249522108300329</id><published>2008-03-18T18:25:00.000+01:00</published><updated>2008-03-18T19:47:28.389+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-18T19:47:28.389+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="plotutils" /><category scheme="http://www.blogger.com/atom/ns#" term="svg" /><category scheme="http://www.blogger.com/atom/ns#" term="eps" /><category scheme="http://www.blogger.com/atom/ns#" term="ghostscript" /><category scheme="http://www.blogger.com/atom/ns#" term="eps2svg" /><category scheme="http://www.blogger.com/atom/ns#" term="pstoedit" /><category scheme="http://www.blogger.com/atom/ns#" term="epstosvg" /><title>EPS zu SVG konvertieren (EPS2SVG)</title><content type="html">Hin und wieder kommt man in die Verlegenheit ein Vektorformat in ein anderes überführen zu müssen. Beispielsweise EPS in SVG. Da &lt;a href="http://www.inkscape.org/" target="_blank"&gt;inkscape&lt;/a&gt; (noch) kein EPS versteht bleibt nur der Umweg über Drittprogramme.&lt;br /&gt;&lt;br /&gt;An dieser Stelle ein kleines Howto.&lt;br /&gt;&lt;br /&gt;Benötigt wird:&lt;br /&gt;&lt;ul&gt;   &lt;li&gt;&lt;a href="http://www.pstoedit.net/"&gt;pstoedit&lt;/a&gt; (z.B. Version 3.45)&lt;/li&gt;    &lt;li&gt;&lt;a href="http://pages.cs.wisc.edu/%7Eghost/"&gt;Ghostscript&lt;/a&gt; (z.B. GPL Ghostscript 8.61)&lt;/li&gt;    &lt;li&gt;GNU plotutils für &lt;a href="http://gnuwin32.sourceforge.net/packages/plotutils.htm"&gt;Win32&lt;/a&gt; (wählen Sie in diesem Fall den Download Binaries) oder &lt;a href="http://www.gnu.org/software/plotutils/plotutils.html"&gt;andere&lt;/a&gt; (z.B. Version 2.4.1)    &lt;br /&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;ol&gt;   &lt;li&gt;Laden Sie die Programme herunter und installieren bzw. entpacken (im Falle der plotutils) Sie sie.&lt;/li&gt;    &lt;li&gt;Damit pstoedit auf Ghostscript und die plotutils zugreifen kann, müssen unter Windows noch die Pfade der /bin-Verzeichnisse von Ghostscript und den plotutils in die %PATH%-Umgebungsvariable aufgenommen werden. Exemplarisch an dieser Stelle der Vorgang für Windows - Nutzer anderer Systeme müssen diesen Schritt auf ihr System angepasst ausführen.   &lt;br /&gt;  &lt;br /&gt;    &lt;ul&gt;       &lt;li&gt;Linux:       &lt;br /&gt;Stichwort: PATH="$PATH:/path/to/bin"&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;        &lt;li&gt;&lt;a href="http://lh4.google.com/joscha.feth/R9__PprWWDI/AAAAAAAAAI0/cNUWnvEEnqE/Systemeigenschaften%5B4%5D?imgmax=800"&gt;&lt;img style="border: 0px none ;" alt="Systemeigenschaften" src="http://lh6.google.com/joscha.feth/R9__QJrWWEI/AAAAAAAAAI8/O10jBcFV5eY/Systemeigenschaften_thumb%5B2%5D?imgmax=800" align="right" border="0" height="137" width="199" /&gt;&lt;/a&gt;Windows Vista:         &lt;br /&gt;&lt;span style="font-style: italic;"&gt;Systemsteuerung &lt;/span&gt;-&amp;gt; &lt;span style="font-style: italic;"&gt;System und Wartung&lt;/span&gt; -&amp;gt; &lt;span style="font-style: italic;"&gt;System&lt;/span&gt;.        &lt;br /&gt;Dort auf &lt;span style="font-style: italic;"&gt;Einstellungen ändern&lt;/span&gt; (siehe rechts).        &lt;br /&gt;In den Systemeinstellungen auf den Reiter &lt;span style="font-style: italic;"&gt;Erweitert&lt;/span&gt; und dort auf den Button &lt;span style="font-style: italic;"&gt;Umgebungsvariablen&lt;/span&gt; rechts unten klicken.        &lt;br /&gt;&lt;br /&gt;&lt;/li&gt;        &lt;li&gt;&lt;a href="http://lh4.google.com/joscha.feth/R9__QprWWFI/AAAAAAAAAJE/dTt_ENMJ9pA/xp_path%5B6%5D?imgmax=800"&gt;&lt;img style="border: 0px none ;" alt="xp_path" src="http://lh6.google.com/joscha.feth/R9__RJrWWGI/AAAAAAAAAJM/JUN1pEGJbQY/xp_path_thumb%5B4%5D?imgmax=800" align="right" border="0" height="166" width="182" /&gt;&lt;/a&gt; Windows XP:          &lt;ol&gt;           &lt;li&gt;&lt;span style="font-style: italic;"&gt;Rechtsklick &lt;/span&gt;auf den &lt;span style="font-style: italic;"&gt;Arbeitsplatz&lt;/span&gt;.&lt;/li&gt;            &lt;li&gt;Aus dem Kontextmenü &lt;span style="font-style: italic;"&gt;Eigenschaften &lt;/span&gt;auswählen.&lt;/li&gt;            &lt;li&gt;Auf der &lt;span style="font-style: italic;"&gt;Eigenschaften&lt;/span&gt;-Seite den Tab &lt;span style="font-style: italic;"&gt;Erweitert &lt;/span&gt;wählen.&lt;/li&gt;            &lt;li&gt;Auf den Knopf &lt;span style="font-style: italic;"&gt;Umgebungsvariablen &lt;/span&gt;klicken.            &lt;br /&gt;&lt;/li&gt;         &lt;/ol&gt;       &lt;/li&gt;     &lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Im neuen Fenster bei den &lt;span style="font-style: italic;"&gt;Benutzervariablen für XYZ&lt;/span&gt; (oben) die Variable &lt;span style="font-weight: bold;"&gt;PATH &lt;/span&gt;wählen und auf &lt;span style="font-style: italic;"&gt;Bearbeiten&lt;/span&gt; klicken (Alternative: Doppelklick auf &lt;span style="font-style: italic;"&gt;PATH&lt;/span&gt;).    &lt;br /&gt;Dort dann die Pfade zu Ghostscript und den plotutils an die bestehenden Einträge anhängen (mit Semikolon [bei Linux mit Doppelpunkt] von den vorhandenen trennen).    &lt;br /&gt;  &lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Beispiel&lt;/span&gt;:    &lt;br /&gt;Eine;Menge;alte;Einträge&lt;span style="font-weight: bold;"&gt;;C:\Program Files\gs\gs8.61\bin;C:\plotutils-2.4.1-4-bin\bin     &lt;br /&gt;    &lt;br /&gt;Hinweis&lt;/span&gt;: Die Pfadangaben mögen sich bei Ihnen - je nachdem welche Pfade Sie beim entpacken/installieren gewählt haben - unterscheiden. Wichtig ist, dass Sie jeweils die Pfade zu den &lt;span style="font-weight: bold;"&gt;bin&lt;/span&gt;-Verzeichnissen eintragen und nicht nur den Stammpfad zum Programm.    &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Danach öffnen Sie eine Eingabeaufforderung und wechseln in das Installationsverzeichnis von pstoedit.   &lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Hinweis&lt;/span&gt;: befinden Sie sich unter Windows Vista ist es sinnvoll die Eingabeaufforderung als Administrator zu öffnen (&lt;span style="font-style: italic;"&gt;Rechtsklick &lt;/span&gt;-&amp;gt; &lt;span style="font-style: italic;"&gt;Als Administrator ausführen&lt;/span&gt;), da es sonst sein kann, dass das in der Kommandozeile ausgeführte Programm die Ausgabedatei nicht schreiben kann. Hierbei gibt es keine Warnung seitens &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;pstoedit&lt;/span&gt;, sondern die Ausgabedatei wird lediglich nicht angelegt.&lt;/li&gt;    &lt;li&gt;Um eine &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;eps&lt;/span&gt;-Datei mittels &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;Ghostscript&lt;/span&gt; und den &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;plotutils&lt;/span&gt; in &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;SVG&lt;/span&gt; zu konvertieren geben Sie auf der Kommandozeile folgendes ein:    &lt;br /&gt;  &lt;br /&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;pstoedit&lt;/span&gt; -f &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;plot&lt;/span&gt;-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;svg&lt;/span&gt; Pfad\zur\Eingabedatei.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_8"&gt;eps&lt;/span&gt; Pfad\zur\Ausgabedatei.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_9"&gt;svg&lt;/span&gt;    &lt;br /&gt;  &lt;br /&gt;    &lt;ol&gt;       &lt;li&gt;der Schalter -f legt das Format zur Konvertierung fest - in unserem Fall (&lt;span style="font-weight: bold;"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_10"&gt;plot&lt;/span&gt;-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_11"&gt;svg&lt;/span&gt;&lt;/span&gt;) sollen die &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_12"&gt;plotutils&lt;/span&gt; zur Erzeugung von &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_13"&gt;SVG&lt;/span&gt; verwendet werden. &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_14"&gt;pstoedit&lt;/span&gt; versteht noch einige andere Formate, eine Übersicht finden Sie auf der Seite des Projekts.        &lt;br /&gt;&lt;/li&gt;        &lt;li&gt;Der zweite Parameter ist der Pfad zur Eingabedatei (in unserem Fall eine &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_15"&gt;EPS&lt;/span&gt;-Datei).&lt;/li&gt;        &lt;li&gt;Der dritte Parameter ist der Pfad zur Ausgabedatei (in unserem Fall eine &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_16"&gt;SVG&lt;/span&gt;-Datei).&lt;/li&gt;     &lt;/ol&gt;    &lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Beispiel&lt;/span&gt;:    &lt;br /&gt;  &lt;br /&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_17"&gt;pstoedit&lt;/span&gt; -f &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_18"&gt;plot&lt;/span&gt;-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_19"&gt;svg&lt;/span&gt; x.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_20"&gt;eps&lt;/span&gt; x.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_21"&gt;svg&lt;/span&gt;    &lt;br /&gt;  &lt;br /&gt;Dieser Befehl würde eine &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_22"&gt;EPS&lt;/span&gt;-Datei namens x.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_23"&gt;eps&lt;/span&gt; welche im Verzeichnis von &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_24"&gt;pstoedit&lt;/span&gt; liegt mittels den &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_25"&gt;plotutils&lt;/span&gt; in eine &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_26"&gt;SVG&lt;/span&gt;-Datei namens x.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_27"&gt;svg&lt;/span&gt; welche im Verzeichnis von &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_28"&gt;pstoedit&lt;/span&gt; angelegt wird, konvertieren.    &lt;br /&gt;Denken Sie daran, dass Sie unter &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_29"&gt;Windows&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_30"&gt;Vista&lt;/span&gt; bei diesem Beispiel die Kommandozeile als Administrator geöffnet haben müssen, da die Ausgabedatei ansonsten nicht geschrieben werden kann.&lt;/li&gt; &lt;/ol&gt;&lt;img src="http://feeds.feedburner.com/~r/TheDailyProfeth/~4/bty_hLVp8Kg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://daily.profeth.de/feeds/5285249522108300329/comments/default" title="Kommentare zum Post" /><link rel="replies" type="text/html" href="http://daily.profeth.de/2008/03/eps-zu-svg-konvertieren-eps2svg.html#comment-form" title="1 Kommentare" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5285249522108300329?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/596650637928113520/posts/default/5285249522108300329?v=2" /><link rel="alternate" type="text/html" href="http://daily.profeth.de/2008/03/eps-zu-svg-konvertieren-eps2svg.html" title="EPS zu SVG konvertieren (EPS2SVG)" /><author><name>Joscha Feth</name><uri>http://www.blogger.com/profile/04180478904744217779</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="21" height="32" src="http://bp3.blogger.com/_lBJLUOoqxdY/R-AQt5rWWII/AAAAAAAAAJY/aRqofwQ5m5s/S220/Joscha_2008_150x225.png" /></author><thr:total>1</thr:total></entry></feed>
