<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' 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'><id>tag:blogger.com,1999:blog-4309086861920339739</id><updated>2024-09-02T10:40:55.673+03:00</updated><category term="python"/><category term="automation"/><category term="google app engine"/><category term="ppa"/><category term="ubuntu"/><category term="android"/><category term="api"/><category term="app inventor"/><category term="bash"/><category term="clojurescript"/><category term="django"/><category term="editors"/><category term="face recognition"/><category term="git"/><category term="lighttable"/><category term="offtopic"/><category term="reactjs"/><category term="rest"/><category term="spycify"/><category term="sublime text 2"/><category term="sublime text 3"/><category term="tastypie"/><category term="testing"/><category term="vim"/><category term="virtualenv"/><title type='text'>Brabadu Technical Blog</title><subtitle type='html'>software development, Python, Django, Ubuntu and everything.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.brabadu.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>12</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-8007653256486554136</id><published>2015-01-12T23:33:00.000+02:00</published><updated>2015-01-13T13:56:27.957+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="clojurescript"/><category scheme="http://www.blogger.com/atom/ns#" term="lighttable"/><category scheme="http://www.blogger.com/atom/ns#" term="reactjs"/><title type='text'>React.js based plugin for LightTable</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;LightTable made quite a big fuzz some time ago. Now original authors are working fulltime on&amp;nbsp;&lt;a href=&quot;http://incidentalcomplexity.com/&quot; rel=&quot;nofollow&quot;&gt;Eve&lt;/a&gt;, while LightTable is now more driven &amp;nbsp;by community members. And they&#39;re doing some pretty interesting work like switching to &lt;a href=&quot;https://github.com/atom/atom-shell&quot; rel=&quot;nofollow&quot;&gt;Atom Shell&lt;/a&gt; instead of node-webkit.&lt;br /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;I like the idea that text editor is a browser. It gives you ability to do literally&lt;i&gt; anything&lt;/i&gt; you want. So I decided to integrate React.js into LightTable plugins ecosystem. One of the most interesting things in latest release is &lt;a href=&quot;http://docs.lighttable.com/#user-plugin&quot; rel=&quot;nofollow&quot;&gt;User plugin&lt;/a&gt;. It is a place to configure your editor not just by tweaking settings and changing keybinding. You can add new features right there without need to create separate plugin. It is much like &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;.vimrc&lt;/span&gt; or &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;.emacs/init.el&lt;/span&gt; files. This turns process of configuring your editor into development task. &lt;br /&gt;
&lt;br /&gt;
First let&#39;s add User plugin to workspace. Start LightTable and run command &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;Settings: Add User plugin to workspace&lt;/span&gt;, open workspace panel (&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;Workspace: toggle workspace tree&lt;/span&gt;&amp;nbsp;command). Now you can see User dir with a some files added already.&lt;br /&gt;
&lt;br /&gt;
Let&#39;s open &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;user.behaviour&lt;/span&gt; file. It is a place to actually change settings and add or remove hooks on different events LightTable and plugins provide. We need to add custom javascript file with React.js and make sure User plugin is loaded too.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;[
  [:app :lt.objs.plugins/load-js &quot;/Users/brabadu/react/react.min.js&quot;]
  [:app :lt.objs.plugins/load-js &quot;user_compiled.js&quot;]
  ...
]&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Let&#39;s open&amp;nbsp;&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;User/src/lt/plugins/user.cljs&lt;/span&gt;. It already has an example that shows how to make objects and open new tabs. We&#39;ll tweak this example to use React.js.&lt;br /&gt;
&lt;br /&gt;
First we need to define a namespace and dependencies&lt;/div&gt;&lt;pre&gt;&lt;code&gt;(ns lt.plugins.user
  (:require [clojure.string :as string]
            [lt.object :as object]
            [lt.objs.tabs :as tabs]
            [lt.objs.command :as cmd])
  (:require-macros [lt.macros :refer [defui behavior]]))
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
All LightTable plugins are in &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;lt.plugins.*&lt;/span&gt; namespace. As you can see, we&#39;ll be working with LightTable object, commands and tabs. Next, we&#39;ll define couple of helper functions&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;(defn format-time [d]
  (first (string/split (.toTimeString d) #&quot; &quot;)))

(def el React/createElement)
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;format-time&lt;/span&gt; is for pretty-printing time from javascript Date object. And &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;el&lt;/span&gt; is simply an alias to &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;React.createElement&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;(def label
  (React/createClass
     #js {:displayName &quot;TimerLabel&quot;
          :render (fn [] 
             (this-as this
                      (el &quot;h1&quot; nil (str &quot;Timer: &quot;
                                        (-&amp;gt; this .-props .-time)))))}))
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Our component, that is going to show whole React.js-LightTable integration. I guess nothing stops us from using om/reagent/quiescent/etc. Add it&#39;s dependency in &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;project.clj&lt;/span&gt; and you&#39;re good to go. Our example is simple enough not to bring anything more than bare React. &lt;br /&gt;
&lt;br /&gt;
Interesting thing is how javascript &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;this&lt;/span&gt; works. Calling macro &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;this-as&lt;/span&gt; with symbol to which javascript &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;this&lt;/span&gt;&amp;nbsp;bounds is idiomatic way to access it. Most of ClojureScript libs are hiding this pattern in their innards, so you&#39;ll need it mostly when doing interop with javascript.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;(defui react-panel [this]
  [:h1 {:id &quot;app-root&quot;} &quot;React + LightTable!&quot;])

(object/object* ::user.react-timer
                :tags [:user.hello]
                :behaviors [::on-close-timer-destroy]
                :init (fn [this]
                        (react-panel this)))

(def react-timer (object/create ::user.react-timer))
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Here we define LightTable (not React.js) ui component &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;react-panel&lt;/span&gt; and object prototype &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;::user.react-timer&lt;/span&gt;. LightTable has it&#39;s own notion of object, that it uses to implement &lt;a href=&quot;https://github.com/LightTable/LightTable/blob/master/doc/BOT.md&quot; rel=&quot;nofollow&quot;&gt;BOT principle&lt;/a&gt;. When the object &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;react-timer&lt;/span&gt; is created function that is passed with &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;:init&lt;/span&gt; is executed.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;(cmd/command {
  :command :user.show-time
  :desc &quot;User: Show time&quot;
  :exec (fn []
    (tabs/add-or-focus! react-timer)
    (let [app-root (.getElementById js/document &quot;app-root&quot;)
          label-inst (React/render 
                        (el label #js {:time (format-time (js/Date.))} [])
                        app-root)
          refresh-timer (fn []
                   (prn (format-time (js/Date.)))
                   (.setProps label-inst #js {:time (format-time (js/Date.))}))
          interval (js/window.setInterval refresh-timer 1000)]
       (object/merge! react-timer {:interval interval})))})
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
This code adds new command to LightTable. It creates new tab, then instantiates React.js component and mounts it on rendered by react-panel node.&lt;br /&gt;
&lt;br /&gt;
After that defines &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;refresh-timer&lt;/span&gt; function, that updates props in React component. It is called every second as a callback of&amp;nbsp;&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;js/window.setInterval&lt;/span&gt;. We could save timer ID in React component&#39;s state, but I decided to put it in object, so our component could be simple and it&#39;s only purpose would be view layer, while all the business-logic lies separately. &lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;(behavior ::on-close-timer-destroy
          :triggers #{:close}
          :reaction (fn [this]
                      (js/window.clearTimeout (:interval @this))
                      (object/raise this :destroy)))
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Now we&#39;re taking care cleaning on our tab close event. This is why I added &lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;prn&lt;/span&gt;&amp;nbsp;to&amp;nbsp;&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;refresh-timer&lt;/span&gt;&amp;nbsp;function - to see if it stopped being called by setInterval.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXqGimC4161OeOjenM-s0YU_6kUgA_LLee1ApQA-pbLAkWz1ulw7bNBM3QiQr2u5gxe2CvunfMuIs9uqE4uGGgzeC7VzaF5n-pr45_Nbisx-f9RJfgtoY9Xemwa_UKsX7PdOeRyo_hLhbs/s1600/Fullscreen_12_01_15_23_50.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXqGimC4161OeOjenM-s0YU_6kUgA_LLee1ApQA-pbLAkWz1ulw7bNBM3QiQr2u5gxe2CvunfMuIs9uqE4uGGgzeC7VzaF5n-pr45_Nbisx-f9RJfgtoY9Xemwa_UKsX7PdOeRyo_hLhbs/s640/Fullscreen_12_01_15_23_50.png&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
That&#39;s it. Code is a bit messy, but hope everything is clear. All code in one place is here&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/brabadu/5f7bd59f491807ee51c8.js&quot;&gt;&lt;/script&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/8007653256486554136/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2015/01/reactjs-based-plugin-for-lighttable.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/8007653256486554136'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/8007653256486554136'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2015/01/reactjs-based-plugin-for-lighttable.html' title='React.js based plugin for LightTable'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXqGimC4161OeOjenM-s0YU_6kUgA_LLee1ApQA-pbLAkWz1ulw7bNBM3QiQr2u5gxe2CvunfMuIs9uqE4uGGgzeC7VzaF5n-pr45_Nbisx-f9RJfgtoY9Xemwa_UKsX7PdOeRyo_hLhbs/s72-c/Fullscreen_12_01_15_23_50.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-348210889101879352</id><published>2015-01-09T12:00:00.000+02:00</published><updated>2015-01-10T21:44:15.398+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ppa"/><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu"/><title type='text'>Another useful Ubuntu PPA list</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
It&#39;s been a long time since I posted my &lt;a href=&quot;http://blog.brabadu.com/2011/01/my-ubuntu-ppa-list.html&quot;&gt;useful ppa list.&lt;/a&gt; Looking at that list and my current PPAs shows that almost everything changed and I could share my new favourites.&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:ubuntu-mozilla-daily/ppa&lt;/span&gt;&amp;nbsp;Unofficial Firefox nightly builds. This is my main browser for about a year now. Even though sometimes it may be unstable it is great browser. It loosed it&#39;s speed in development and innovation for some time, but now it becomes good product again&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:conscioususer/polly-daily&lt;/span&gt;&amp;nbsp;is for Polly Twitter Client&lt;b&gt; &lt;/b&gt;which is the only usable desktop twitter client under linux for me.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:tuxpoldo/btsync&lt;/span&gt;&amp;nbsp;BTSync &lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:webupd8team/sublime-text-3&lt;/span&gt;&amp;nbsp;Latest builds of the &lt;a href=&quot;http://blog.brabadu.com/2011/10/sublime-text-2-for-django-developer.html&quot;&gt;best text editor&lt;/a&gt; in existence - Sublime Text 3&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:ubuntu-desktop/ppa&lt;/span&gt;&amp;nbsp;Pre-release builds of Unity and other Ubuntu goodness&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:webupd8team/java&lt;/span&gt;&amp;nbsp;Java release by webupd8 team. I need it to run PyCharm, my main IDE for now&lt;/li&gt;
&lt;li&gt;PyCharm has nonofficial &lt;a href=&quot;http://www.ubuntuupdates.org/ppa/getdeb_apps?dist=utopic&quot;&gt;PPA on UbuntuUpdates&lt;/a&gt;. But I prefer to go to it&#39;s &lt;a href=&quot;https://confluence.jetbrains.com/display/PYH/JetBrains+PyCharm+Preview+(EAP)&quot; rel=&quot;nofollow&quot;&gt;EAP download page&lt;/a&gt;&amp;nbsp; to get fresh build.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
To install any of those PPAs type in terminal:&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;sudo apt-add-repository ppa:ubuntu-wine/ppa&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;sudo apt-get update&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
After that you can install software from PPA with&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;sudo apt-get install ...&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: inherit;&quot;&gt;Those things are PPAs, but you have to either add them manually to&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;&lt;span style=&quot;font-family: Courier New, Courier, monospace;&quot;&gt;/etc/apt/sources.list.d/&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; or with help of &lt;a href=&quot;http://askubuntu.com/questions/4983/what-are-ppas-and-how-do-i-use-them&quot; rel=&quot;nofollow&quot;&gt;this tutorial&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;http://dl.google.com/linux/chrome/deb/&lt;/span&gt;&amp;nbsp;you can&#39;t run away from&amp;nbsp;Google Chrome&amp;nbsp;if you&#39;re doing web development these days. Here I also use untested version. And also it is a browser I use when some builds of Firefox Nightly has critical issues.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;https://get.docker.io/ubuntu&lt;/span&gt; Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/348210889101879352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2015/01/another-userfull-ubuntu-ppa-list.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/348210889101879352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/348210889101879352'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2015/01/another-userfull-ubuntu-ppa-list.html' title='Another useful Ubuntu PPA list'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-8069616311349608752</id><published>2013-04-28T14:46:00.005+03:00</published><updated>2013-04-28T14:49:12.127+03:00</updated><title type='text'>Yandex Subbotnik</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyGXrpxgTgVfJ09phFslVVKu-AsZWWajpozvp22tX2bZ7yjqavSTSf5IFWIV_ZCC3qw9VhKLEmQi7ytr4rM5iyZicFn21pmxba0sTLX8ggkICmQ9tmT05vXT1tBGAkVYbweb6qo4hWzCg-/s1600/E5F1C15B-7F57-4483-B6EA-4BBEF254E524.JPG&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyGXrpxgTgVfJ09phFslVVKu-AsZWWajpozvp22tX2bZ7yjqavSTSf5IFWIV_ZCC3qw9VhKLEmQi7ytr4rM5iyZicFn21pmxba0sTLX8ggkICmQ9tmT05vXT1tBGAkVYbweb6qo4hWzCg-/s320/E5F1C15B-7F57-4483-B6EA-4BBEF254E524.JPG&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Today I&#39;ve been to the 5th Yandex Saturday event - &lt;a href=&quot;http://events.yandex.ru/events/yasubbotnik/kiev-apr-2013/&quot;&gt;Subbotnik&lt;/a&gt;. This is series of technical miniconferences in different cities. Place where yandexoids show other developers how they work, show toolset and other yandexish things. This was second Subbotnik for me. Last year most of the talks were about &lt;a href=&quot;http://bem.info/&quot;&gt;BEM&lt;/a&gt; (their front-end architecture) and toolset around it. So I&#39;ve lost what they&#39;re talking about right from the second talk that day.&lt;br /&gt;
&lt;br /&gt;
Today&#39;s Subbotnik was prepared much more thoughtfully. Yes, BEM was mentioned couple of times during each talk anyway, but all the themes were aimed to more broad audience, that doesn&#39;t know all of the ins and outs of bem-tools.&lt;br /&gt;
&lt;br /&gt;
This Subbotnik had 2 tracks. One for Yandex&#39;s API users, the other was more for general developers with talks about front-end development and cloud services. I watched the second track, as I don&#39;t use Yandex API at all, so have no interest in it.&lt;br /&gt;
&lt;br /&gt;
All the talks were in Russian most of the slides (if not all) too. But anyway I think it worth mentioning them in English speaking internet. You never know what may become useful.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://download.cdn.yandex.net.cache-default04f.cdn.yandex.net/company/experience/subbotnik/kiev_2013_rusinov.pdf&quot;&gt;Yandex.Disk. The flight is normal by Vladimir Rusinov.&lt;/a&gt;&lt;br /&gt;
Short introduction talk about &lt;a href=&quot;http://disk.yandex.ru/&quot;&gt;Yandex.Disk&lt;/a&gt; from it&#39;s product manager. Yandex.Disk, for those who don&#39;t know, is online backup, storage and syncing service. Something very close to Dropbox. The product is doing fine, usage is growing, other services actively integrate with it using API. There were some numbers, history and funny picture. As it turned out it was warmup before second talk.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://download.cdn.yandex.net.cache-default04f.cdn.yandex.net/company/experience/subbotnik/kiev_2013_belov_leksunin.pdf&quot;&gt;Operation and development of fast-growing clouds by Michail Belov and Oleg Leksunin.&lt;/a&gt;&lt;br /&gt;
First half of the talk was done by Oleg Leksunin who is system administrator of Yandex.Disk. He talked about requirements that were crucial for such service. architecture of the service, how they are organized and deployed. Also mentioned some key parts of the system and talked what each of them does.&lt;br /&gt;
&lt;br /&gt;
There&#39;s so called MPFS service, that is a virtual file system for users&#39;s files. When file is added to YDisk request to MPFS is sent, which synchronously stores file as blobs on two different servers (with internal project Mulca). When it is saved file&#39;s metadata are saved to MongoDB.&lt;br /&gt;
&lt;br /&gt;
Protocol for YDisk is extended WebDAV, that was chosen because it implements most of needed features, had existing working tools like wget or curl. And almost any modern OS has builtin WebDAV support. WebDAV server is written in Erlang. It was described in the &lt;a href=&quot;http://habrahabr.ru/company/yandex/blog/176251/&quot;&gt;post on habrahabr&lt;/a&gt; (in russian). MPFS is written in Python with Flask, Jinja2 and is served via nginx. Client applications use XMPP for push notifications.&lt;br /&gt;
&lt;br /&gt;
Overall it was great talk with lots of interesting details and insights. Usually there&#39;s not so much you can take from talks like this, but it is interesting to know how big companies make big services. As it turns out everything may be quite simple.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://download.cdn.yandex.net.cache-default03g.cdn.yandex.net/company/experience/subbotnik/kiev_2013_androsov.pdf&quot;&gt;Cryochamber for statics by Alexey Androsov&lt;/a&gt; I think was the best talk of the day. Alexey works as a fronted engineer at Yandex.Mail team. He started discussing how usually static files are served. Common case is to set cache period for static files for a long period, e.g. a year or a month, and make url to static resource uniqe by adding version of the app or revision (&lt;i&gt;/static/1.1/app.js&lt;/i&gt; or &lt;i&gt;/static/app.js?rev=a7cb9&lt;/i&gt;). Problem appears when there&#39;s only small change in the fronend app, then version or revision changes and all the static files have to be downloaded again. For big frontend apps this may become big problem, Y.Mail&#39;s static resource is more than 1Mb so with often changes to code there&#39;s a lot of things to download every time.&lt;br /&gt;
&lt;br /&gt;
Yandex.Mail team took different approach. Now they put all the files for all of the versions into the same folder, but name of the resource is generated by file&#39;s hash. And postprocessor substitutes all links to the file in content that is served to user to hash-based new filenames.&lt;br /&gt;
&lt;br /&gt;
So the CSS-code:

&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;.class {
   background: url(&quot;1.png&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
becomes:
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;
.class {
    background: url(&quot;freeze/f80abidmv6dlfo9.png&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
There&#39;s some tricks for making all of this possible in JavaScript. That is being done with &lt;a href=&quot;http://bit.ly/borschik&quot;&gt;borschick&lt;/a&gt;, the app that does all the magick - renaming, processing links etc. I&#39;ll definately take a look at this project it looks very interesting.  &lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://download.cdn.yandex.net.cache-default03g.cdn.yandex.net/company/experience/subbotnik/kiev_2013_korepanov.pdf&quot;&gt; Michail Korepanov with a talk on &lt;/a&gt;&lt;a href=&quot;http://download.cdn.yandex.net.cache-default03g.cdn.yandex.net/company/experience/subbotnik/kiev_2013_korepanov.pdf&quot;&gt;Incremental updates on the client&lt;/a&gt; continued discussion on how to make updates of front-apps less problematic. They decided to make browser download not the whole file with new version of app fiiles, but implement diff update.&lt;br /&gt;
&lt;br /&gt;
So now the app doesn&#39;t load css and main javascript app as links, but they are downloaded via some javascript bootstrapper and saved to localstorage. After saving resources CSS is inserted inline to page and Javascript is evaluated with &quot;new Function()&quot;.&lt;br /&gt;
&lt;br /&gt;
Next time you open Y.Mail bootstrapper sends request with app version saved in localstorage, server responds with diff, bootstrapper patches code in localstorage and injects into page.&lt;br /&gt;
&lt;br /&gt;
Diffs are prepared beforehand for 3 previous versions, when new version is deployed to production. If version stored in localstorage is too old, then server responds with whole latest version of the app. As I said, Yandex.Mail deploy new frontend app version twice a week, so such method saves a lot of bandwith and makes loading new version of the app much faster.&lt;br /&gt;
&lt;br /&gt;
Also Michail mentioned &lt;a href=&quot;http://www.ietf.org/rfc/rfc3229.txt&quot;&gt;RFC 3229 Delta encoding in HTTP,&lt;/a&gt; now finding diff and patching must be done by hand, with JavaScript, but this RFC implementetion could automate most this work. Unfortunately it is not implemented in any browser yet. There&#39;s a Chromium-based Yandex.Browser, so maybe this would be a place where this RFC would be first implemented ;)&lt;br /&gt;
&lt;br /&gt;
Now I&#39;m not sure how this is correlated to the previous talk by Alexey, since they solve the same problem from different angles. This talk looked a bit more theoretical, though it is used in production, as I understood.&lt;br /&gt;
&lt;br /&gt;
Next talk after a short break - &lt;a href=&quot;http://download.cdn.yandex.net.cache-default01d.cdn.yandex.net/company/experience/subbotnik/kiev_2013_filatov.pdf&quot;&gt;single-page application using node.js and BEM started by Eugene Filatov&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The main point of the talks was to tell about how yandex makes web apps that may be rendered both by backend and in the browser. Server rendering is needed for search engines to get your content properly.&lt;br /&gt;
&lt;br /&gt;
BEM methodology operates with reusable blocks: news block, search bar block, email list block etc. Every block contains all in one place: html, css and javascript for it to work properly. But since block may be rendered both on server and on client there&#39;s some things that may need different handling. In the talk example of setting a cookie was mentioned. On server we have to add Set-Cookie request header, in browser we may write to document.cookies in the simplest case. But there&#39;s also a lot of shared code, so they prepare 3 files:&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;i&gt;cookie.js&lt;/i&gt; - code specific to the browser&lt;/li&gt;
&lt;li&gt;&lt;i&gt;cookie.priv.js&lt;/i&gt; - code specific to node.js (&quot;priv&quot; is for &quot;private&quot;)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;cookie.common.js&lt;/i&gt; - shared code for both versions&lt;/li&gt;
&lt;/ul&gt;
bem-tools that postprocess page would gather bundles for node and browser environments.&lt;br /&gt;
&lt;br /&gt;
Eugene shared a piechart that shows that common code usually takes up to 54% of block code, so it definately worth having similar code organization if you want to do both server and browser rendering.&lt;br /&gt;
&lt;br /&gt;
Next talk was &lt;a href=&quot;http://download.cdn.yandex.net.cache-default01e.cdn.yandex.net/company/experience/subbotnik/kiev_2013_puzankov.pdf&quot;&gt;Interface development in distributed teams by Sergey Puzankov&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
That was just an overview how the whole process is organized, how communication is made and all the things. Not so much to tell about, everything is the same as others do.&amp;nbsp; Well, there&#39;s two things that everybody should, but mostly don&#39;t have: regular inner conferences for sharing knowledge and up-to-date docs about systems. &lt;br /&gt;
&lt;br /&gt;
Last talk was &lt;a href=&quot;http://download.cdn.yandex.net.cache-default02g.cdn.yandex.net/company/experience/subbotnik/kiev_2013_sergeev.pdf&quot;&gt;Code management or Why SCM by Sergey Sergeev&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Sergey is well-known git advocate, so he told about how his team uses git, best practices and anti-patterns. Now I know that Yandex SERP frontend team uses git-flow :) There was nothing really new here too, but it was nice opportunity to share user stories and ask questions for some people.&lt;br /&gt;
&lt;br /&gt;
This was interesting event with great speakers. Despite there were no groundbreaking revalations, it was great opportunity to gather developers and discuss problems that all of us, both small dev teams and large software giants share.&lt;br /&gt;
&lt;br /&gt;
Thanks Yandex Team for interesting event and great speakers! &lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/8069616311349608752/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2013/04/yandex-subbotnik.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/8069616311349608752'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/8069616311349608752'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2013/04/yandex-subbotnik.html' title='Yandex Subbotnik'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyGXrpxgTgVfJ09phFslVVKu-AsZWWajpozvp22tX2bZ7yjqavSTSf5IFWIV_ZCC3qw9VhKLEmQi7ytr4rM5iyZicFn21pmxba0sTLX8ggkICmQ9tmT05vXT1tBGAkVYbweb6qo4hWzCg-/s72-c/E5F1C15B-7F57-4483-B6EA-4BBEF254E524.JPG" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-3459902216961524158</id><published>2011-10-03T21:17:00.000+03:00</published><updated>2015-01-09T22:24:40.584+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="editors"/><category scheme="http://www.blogger.com/atom/ns#" term="sublime text 2"/><category scheme="http://www.blogger.com/atom/ns#" term="sublime text 3"/><category scheme="http://www.blogger.com/atom/ns#" term="vim"/><title type='text'>Sublime Text 2/3 for Django developer</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
Many developers know about this battle between&amp;nbsp;&lt;em&gt;emacs&lt;/em&gt;&amp;nbsp;and&amp;nbsp;&lt;em&gt;vim&lt;/em&gt;. Well, not exactly between editors themselves, but rather between their fanboys.&amp;nbsp;As for me, I started with&amp;nbsp;&lt;em&gt;gedit&lt;/em&gt;, default Gnome, therefore Ubuntu, text editor. After adding a &lt;a href=&quot;http://blog.brabadu.com/2011/01/my-ubuntu-ppa-list.html&quot;&gt;couple of plugins&lt;/a&gt;, most importantly, dummy code completion it worked well for couple of months.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;br clear=&quot;none&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
A bit later I decided&amp;nbsp;&lt;em&gt;gedit&lt;/em&gt;&amp;nbsp;isn&#39;t cool enough :-) And made a switch to&amp;nbsp;&lt;em&gt;vim&lt;/em&gt;. Can&#39;t say this was love from first sight, but as longer I used it, more I liked it.&amp;nbsp;I ended up being completely overtaken by command mode. I don&#39;t care if I need to type one more key to yield into system clipboard, I don&#39;t need it too often. But copying to internal registers, having them as many as I want,&amp;nbsp;&lt;strong&gt;searching&lt;/strong&gt;&amp;nbsp;right under your pinky is awesome. I started using searching dozens of time more often, then I did in other editors and IDEs. Combining this with remembering of your positions (so you can jump to your previous cursor positions), changes, and thousands of other different things.&amp;nbsp;You know, look at any &lt;a href=&quot;http://www.tuxfiles.org/linuxhelp/vimcheat.html&quot;&gt;vim cheat sheet&lt;/a&gt;, and you&#39;ll be scared and maybe interested :-)&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
Yes, I know, it &lt;a href=&quot;http://tottinge.blogsome.com/use-vim-like-a-pro&quot;&gt;takes time to learn it&lt;/a&gt;, even more time to get used to it, but when it is in your muscle memory it is extremely comfortable.&amp;nbsp;But I still have different issues with it. I almost can&#39;t use Rope, code completion is almost as dumb as&amp;nbsp;&lt;em&gt;gedit&#39;&lt;/em&gt;s, and configuring it is cargo-cult for me :).&lt;/div&gt;
&lt;div&gt;
&lt;br clear=&quot;none&quot; /&gt;&lt;/div&gt;
&lt;div&gt;
I tried&amp;nbsp;&lt;em&gt;emacs&lt;/em&gt;, but it uses many many more times control and alt keys. I don&#39;t really like this.&amp;nbsp;Now let&#39;s move to the subject :-)&lt;/div&gt;
&lt;div&gt;
&lt;br clear=&quot;none&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
Text editor&amp;nbsp;&lt;em&gt;Sublime Text 2&lt;/em&gt;&amp;nbsp;is starting to gain more and more attention. It is multiplatform, quick, nicelooking editor with a couple of very interesting features.&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
&lt;br clear=&quot;none&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
One of the mostly used&amp;nbsp;&lt;em&gt;ST2&amp;nbsp;&lt;/em&gt;features is it&#39;s function for opening files from project. You can press Ctrl+P and start typing filepath and it will give a list of matching files. You have to create project first, to use this function. It works great and became very handy way to move between files.&amp;nbsp;Other options is Ctrl+Shift+P - same kind of text input opens and you can start typing a command and it will filter it for&amp;nbsp;you. For example you can type Rope and it will show you all the commands of rope on first place, and then other commands that have letters r,o,p,e in it. It is hard to explain, you should try it for yourself and you&#39;ll understand it quickly.&lt;br /&gt;
&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmwwE7r2VloxqIwYWqmqqxjnVdgMJkNQ30AHlm2PYq5J_THyOeuzZINZiTtZaJNdM2iWJGntOWUU97W-MXmmufmkHsfb8nMh2PRdm_wnXPRdEFkOfXBxVIu6xJCrpZsYjpKCfHN5aL1o2x/s1600/open-file.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmwwE7r2VloxqIwYWqmqqxjnVdgMJkNQ30AHlm2PYq5J_THyOeuzZINZiTtZaJNdM2iWJGntOWUU97W-MXmmufmkHsfb8nMh2PRdm_wnXPRdEFkOfXBxVIu6xJCrpZsYjpKCfHN5aL1o2x/s320/open-file.png&quot; height=&quot;200&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Opening file from project&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Moving between methods is not harder - Ctrl+R and you see a list of methods in current file, can filter and quickly jump to their declaration.&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;
Most surprising feature for me is&amp;nbsp;&lt;strong&gt;vintage&lt;/strong&gt;&amp;nbsp;mode. It is a simulation of VIM&#39;s most commonly used commands, such as ghjk-navigation, commands for copying, deleting, pasting, inserting etc. But miracle didn&#39;t happen, it is quite restricted. Using motion commands isn&#39;t as consistent as in vim. ESC doesn&#39;t clears command, so if you typed d, you will delete something, no matter how many times you push ESC button. Vintage mode is made completely on keybindings, so maybe it is possible to fix some things, but I think emulate whole vim&#39;s behavior would be overkill.&lt;/div&gt;
&lt;div&gt;
&lt;br clear=&quot;none&quot; /&gt;&lt;/div&gt;
Other great advantage of&amp;nbsp;&lt;em&gt;ST2&lt;/em&gt;&amp;nbsp;is it&#39;s configuration file. You can build your own from scratch, referring to default one, which is pretty well documented. Configuration file is json, so you don&#39;t have to learn another configuration language. My (tiny) configuration file is on &lt;a href=&quot;https://gist.github.com/1259801&quot;&gt;github&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
&lt;br /&gt;
I have installed couple of plugins for django development:&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;strong style=&quot;text-align: -webkit-auto;&quot;&gt;&lt;a href=&quot;https://github.com/squ1b3r/Djaneiro&quot;&gt;Djaneiro&lt;/a&gt;&amp;nbsp;&lt;/strong&gt;&lt;span style=&quot;text-align: -webkit-auto;&quot;&gt;is nice set of autocompletions, colorscheme for django. I printed their README to see all list of available substitutions, e.g. &lt;code&gt;var&lt;/code&gt; becomes &lt;code&gt;{{ }}&lt;/code&gt; in template, &lt;code&gt;block&lt;/code&gt; becomes &lt;code&gt;{% block %} {% endblock %}&lt;/code&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: -webkit-auto;&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/JulianEberius/SublimeRope&quot;&gt;SublimeRope&lt;/a&gt;&lt;/strong&gt;&amp;nbsp;is an attempt to bring powerful library Rope into&amp;nbsp;&lt;em&gt;Sublime Text 2&lt;/em&gt;. Rope can do a lot of IDEish things with python code like extracting methods, searching for docstrings, going to definitions, renaming etc.&amp;nbsp;At the moment it can do only renaming and moving to definitions but github repo is being updated time to time, so I hope we will see more features in future.&lt;/li&gt;
&lt;li style=&quot;text-align: -webkit-auto;&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/Kronuz/SublimeLinter&quot;&gt;SublimeLint&lt;/a&gt;&amp;nbsp;&lt;/strong&gt;&amp;nbsp;became a big discovery. I knew lint existed, but never actually used it. It can show you different warnings and errors like those big compilers have: variable declared but never used, unused imports, syntax errors and PEP8. This is not the best explanation what Lint is though :) It can make editor a bit slower, but you can turn off background linting and turn on when you need it again.&lt;/li&gt;
&lt;/ul&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzdDLJEc6XfQ8TpcEV8eNPIyNs3gz2nAnKFB2m9ViAn5FO63nNsEoYssEtGTvnfFXhnEWlAfpr5SUSiE8XvWqt8oQagaETWt_-dQVT3mWRhuGDVFvFgyZztanLGsA1xBkZXvdzigKOd8c6/s1600/sublime-lint.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzdDLJEc6XfQ8TpcEV8eNPIyNs3gz2nAnKFB2m9ViAn5FO63nNsEoYssEtGTvnfFXhnEWlAfpr5SUSiE8XvWqt8oQagaETWt_-dQVT3mWRhuGDVFvFgyZztanLGsA1xBkZXvdzigKOd8c6/s320/sublime-lint.png&quot; height=&quot;90&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;SublimeLint shows PEP8 warning&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;
Autocompletions in&amp;nbsp;&lt;em&gt;ST2&lt;/em&gt;&amp;nbsp;is what I still don&#39;t understand. Quite often I can&#39;t predict what suggestions will it give, especially what it will autocomplete with Tab key.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
&lt;br /&gt;
Everybody is talking about minimap. I can&#39;t say anything about it. It is interesting way to find your place in file, no more, no less.&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
&lt;br clear=&quot;none&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
As a summary I think sublime can become my default editor. I use it for a week already, and though I miss some vim specific features, I like it very and very much. I think I&#39;ll try to do a reversed path. I want to try recreate all things i liked so much from&amp;nbsp;&lt;em&gt;ST2&lt;/em&gt;&amp;nbsp;in&amp;nbsp;&lt;em&gt;vim&lt;/em&gt;. Depending on results I&#39;ll choose one of them to use day-to-day.&lt;/div&gt;
&lt;div style=&quot;text-align: -webkit-auto;&quot;&gt;
&lt;br clear=&quot;none&quot; /&gt;&lt;/div&gt;
&lt;span style=&quot;text-align: -webkit-auto;&quot;&gt;Even if i will switch back to&amp;nbsp;&lt;/span&gt;&lt;em style=&quot;text-align: -webkit-auto;&quot;&gt;vim,&lt;/em&gt;&lt;span style=&quot;text-align: -webkit-auto;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;em style=&quot;text-align: -webkit-auto;&quot;&gt;Sublime Text 2&lt;/em&gt;&lt;span style=&quot;text-align: -webkit-auto;&quot;&gt;&amp;nbsp;showed me some things in editor, that I liked and that can make my life easier and nicer. Users really don&#39;t know what they want, until they get it :)&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/3459902216961524158/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/10/sublime-text-2-for-django-developer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/3459902216961524158'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/3459902216961524158'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/10/sublime-text-2-for-django-developer.html' title='Sublime Text 2/3 for Django developer'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmwwE7r2VloxqIwYWqmqqxjnVdgMJkNQ30AHlm2PYq5J_THyOeuzZINZiTtZaJNdM2iWJGntOWUU97W-MXmmufmkHsfb8nMh2PRdm_wnXPRdEFkOfXBxVIu6xJCrpZsYjpKCfHN5aL1o2x/s72-c/open-file.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-9196783403800512560</id><published>2011-06-23T10:57:00.001+03:00</published><updated>2011-06-23T10:58:21.462+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="api"/><category scheme="http://www.blogger.com/atom/ns#" term="django"/><category scheme="http://www.blogger.com/atom/ns#" term="rest"/><category scheme="http://www.blogger.com/atom/ns#" term="tastypie"/><title type='text'>Easy REST API with django-tastypie</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;One day at work I had to implement JSON response on simple GET request. I was just going to code another RESTish bicycle as my project manager told me to use &lt;code&gt;django-tastypie&lt;/code&gt;. I&#39;m sure he knows what he is talking about so I opened google to find what tastypie is.&lt;br /&gt;
&lt;br /&gt;
When spec for my task was being written, author didn&#39;t think about what tool will be used for implementation. On the other side tastypie authors didn&#39;t know what people will do with their lib. So my task was to make this two worlds meet in one place. On github &lt;a href=&quot;https://github.com/toastdriven/django-tastypie&quot;&gt;project page&lt;/a&gt; there&#39;s a simple example with very basic things showed. The real world is usually more complicated. I&#39;ll try to show you how I worked with tastypie.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;div class=&quot;useful-links&quot;&gt;Useful link &lt;br /&gt;
&lt;a href=&quot;http://www.infoq.com/articles/rest-introduction&quot;&gt;A Brief Introduction to REST&lt;/a&gt;&lt;/div&gt;Let&#39;s take an example. For this post I created tine django project which you can find at &lt;a href=&quot;https://github.com/brabadu/tastypie_example&quot;&gt;github&lt;/a&gt;. &lt;br /&gt;
We have some working web application and we need to add REST API to it. Our main models are: Cabinet, Folder and File. Cabinets stand in some office. Every Cabinet has Folders inside, which contains Files of different types.&lt;br /&gt;
&lt;br /&gt;
The task is to implement such response for request &lt;code&gt;GET api/cabinet/list&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;{
   &quot;cabinets&quot;:[
      {
         &quot;added&quot;:&quot;2011-06-17&quot;,
         &quot;color&quot;:&quot;black&quot;,
         &quot;floor&quot;:2,
         &quot;folders&quot;:[
            {
               &quot;files&quot;:[
                  {
                     &quot;archive_num&quot;:&quot;221122&quot;,
                     &quot;content&quot;:&quot;Lorem ipsum dolor sit amet
consectetur adipisicing elit&quot;,
                     &quot;type&quot;:&quot;Evidence&quot;
                  }
               ],
               &quot;name&quot;:&quot;XFiles&quot;,
               &quot;secrecy&quot;:10
            },
            {
               &quot;files&quot;:[
                  {
                     &quot;archive_num&quot;:&quot;1111&quot;,
                     &quot;content&quot;:&quot;sunt in culpa qui officia deserunt mollit 
anim id est laborum&quot;,
                     &quot;type&quot;:&quot;Document&quot;
                  },
                  {
                     &quot;archive_num&quot;:&quot;333&quot;,
                     &quot;content&quot;:&quot;$1000&quot;,
                     &quot;type&quot;:&quot;Bill&quot;
                  }
               ],
               &quot;name&quot;:&quot;Foldy folder&quot;,
               &quot;secrecy&quot;:6
            }
         ],
         &quot;height&quot;:2,
         &quot;width&quot;:10
      },
      {
         &quot;added&quot;:&quot;2011-06-17&quot;,
         &quot;color&quot;:&quot;white&quot;,
         &quot;floor&quot;:10,
         &quot;folders&quot;:[
            {
               &quot;files&quot;:[
                  {
                     &quot;archive_num&quot;:&quot;87878&quot;,
                     &quot;content&quot;:&quot;Funny picture&quot;,
                     &quot;type&quot;:&quot;Picture&quot;
                  }
               ],
               &quot;name&quot;:&quot;Fun pics&quot;,
               &quot;secrecy&quot;:0
            },
            {
               &quot;files&quot;:[
                  {
                     &quot;archive_num&quot;:&quot;23423424&quot;,
                     &quot;content&quot;:&quot;Photo of some big room&quot;,
                     &quot;type&quot;:&quot;Photo&quot;
                  }
               ],
               &quot;name&quot;:&quot;Old folder&quot;,
               &quot;secrecy&quot;:3
            }
         ],
         &quot;height&quot;:1,
         &quot;width&quot;:4
      }
   ]
}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
This means that we need whole tree of entries, gathered in one root called &#39;Cabinets&#39;.&lt;br /&gt;
&lt;br /&gt;
Installing is easy and usual - &lt;code&gt;pip install django-tastypie&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
After reading docs we create first resource that looks like:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;from tastypie.resources import ModelResource

from app.models import Cabinet


class CabinetResource(ModelResource):
    class Meta:
        queryset = Cabinet.objects.all()&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Docs say that we should put this in api.py file in app folder. &lt;br /&gt;
&lt;br /&gt;
Then we should create url handler:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;from app.api import CabinetResource

cabinet_resource = CabinetResource()

urlpatterns = patterns(&#39;&#39;,
    ...
    url(r&#39;^api/&#39;, include(cabinet_resource.urls))
    ...
)&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
This creates a resource at &lt;code&gt;/api/cabinet&lt;/code&gt; which returns:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;{
   &quot;meta&quot;:{
      &quot;limit&quot;:20,
      &quot;next&quot;:null,
      &quot;offset&quot;:0,
      &quot;previous&quot;:null,
      &quot;total_count&quot;:2
   },
   &quot;objects&quot;:[
      {
         &quot;added&quot;:&quot;2011-06-17&quot;,
         &quot;color&quot;:&quot;black&quot;,
         &quot;floor&quot;:2,
         &quot;height&quot;:2,
         &quot;id&quot;:&quot;1&quot;,
         &quot;resource_uri&quot;:&quot;/api/cabinet/1/&quot;,
         &quot;width&quot;:10
      },
      {
         &quot;added&quot;:&quot;2011-06-17&quot;,
         &quot;color&quot;:&quot;white&quot;,
         &quot;floor&quot;:10,
         &quot;height&quot;:1,
         &quot;id&quot;:&quot;2&quot;,
         &quot;resource_uri&quot;:&quot;/api/cabinet/2/&quot;,
         &quot;width&quot;:4
      }
   ]
}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Good start but we need more!&lt;br /&gt;
&lt;br /&gt;
We got &lt;code&gt;Meta&lt;/code&gt; object, that we don&#39;t want to get :), we have &lt;code&gt;resource_uri&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt;, which we didn&#39;t ask either, and most important - we want elements, connected to our Cabinets.&lt;br /&gt;
&lt;br /&gt;
But first let&#39;s deal with URI of our resource. Our task is clear - URI must be &lt;code&gt;api/cabinet/list&lt;/code&gt;. Resource Meta class has field &lt;code&gt;resource_name&lt;/code&gt;, which is clearly described in docs. By default resource name is generated from resource class name by removing word Resource and lowercasing it. But you can specify your own resource name and nothing keeps you from making it as custom as you want, for example - &lt;code&gt;list/cabinet&lt;/code&gt; (&lt;code&gt;api&lt;/code&gt; part is defined at urls.py), right what we need!&lt;br /&gt;
&lt;br /&gt;
While adding subelements to response I tried some weird hacks. But it turned out to be quite simple. &lt;code&gt;tastypie&lt;/code&gt; doesn&#39;t make connections automatically. Authors follow the principle &quot;Explicit is better then implicit&quot;. So every connection of your resource must be done manually.&lt;br /&gt;
Connections are established between resources, not models. So here&#39;s a resource for Cabinet&#39;s child class - Folder:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class FolderResource(ModelResource):
    class Meta:
        queryset = Folder.objects.all()&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Now we need to add field to CabinetResource, what result in something like:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class CabinetResource(ModelResource):
    folders = fields.ToManyField(&#39;app.api.FolderResource&#39;,
                                 &#39;folders&#39;, full=True)
    class Meta:
        .....   
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Optional parameter &lt;code&gt;full&lt;/code&gt; is False by default. In this state &lt;code&gt;folders&lt;/code&gt; subelement will only contain links to it&#39;s respective Folder resources. To work this way you need to extend your urlhandlers with FolderResource urls. This is &lt;a href=&quot;http://readthedocs.org/docs/django-tastypie/en/latest/api.html&quot;&gt;described quite well&lt;/a&gt; in official docs. But in our case we need to show not just a link, but whole object&#39;s content. To achieve it we set &lt;code&gt;full=True&lt;/code&gt;. Here&#39;s little trick I&#39;ve noticed while was writing this post. If you don&#39;t specify urlhandlers for subelements (Folder in our case) resource_uri parameter for subelements will be empty. So nobody would have a way to get your objects (via this API, I mean)&lt;br /&gt;
&lt;br /&gt;
Having always empty parameter isn&#39;t cool. So I wanted to turn them off. First I tried hackish way, deleting this parameter manually on response generation step. But then I noticed a parameter &lt;code&gt;include_resource_uri&lt;/code&gt; which prevents adding &lt;code&gt;resource_uri&lt;/code&gt; parameter to object.&lt;br /&gt;
&lt;br /&gt;
And at last we would like to remove field &lt;code&gt;id&lt;/code&gt;. Showing to the world inner data like ID of object in database is potentially dangerous. This could be done with &lt;code&gt;fields&lt;/code&gt; or &lt;code&gt;excludes&lt;/code&gt; parameters, that might be familiar to you from Django Forms, that use the same principle. To hide &lt;code&gt;id&lt;/code&gt; field we add &lt;code&gt;excludes = [&#39;id&#39;]&lt;/code&gt; to our Meta classes.&lt;br /&gt;
&lt;br /&gt;
So now our &lt;code&gt;api.py&lt;/code&gt; file is:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;from tastypie.resources import ModelResource
from tastypie import fields

from app.models import Cabinet, Folder


class FolderResource(ModelResource):
    class Meta:
        queryset = Folder.objects.all()
        excludes = [&#39;id&#39;]
        include_resource_uri = False


class CabinetResource(ModelResource):
    folders = fields.ToManyField(&#39;app.api.FolderResource&#39;,
                                 &#39;folders&#39;, full=True)

    class Meta:
        queryset = Cabinet.objects.all()
        resource_name = &#39;cabinet/list&#39;
        excludes = [&#39;id&#39;]
        include_resource_uri = False&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
And response at &lt;code&gt;/api/cabinet/list&lt;/code&gt; is&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;{
   &quot;meta&quot;:{
      &quot;limit&quot;:20,
      &quot;next&quot;:null,
      &quot;offset&quot;:0,
      &quot;previous&quot;:null,
      &quot;total_count&quot;:2
   },
   &quot;objects&quot;:[
      {
         &quot;added&quot;:&quot;2011-06-17&quot;,
         &quot;color&quot;:&quot;black&quot;,
         &quot;floor&quot;:2,
         &quot;folders&quot;:[
            {
               &quot;name&quot;:&quot;XFiles&quot;,
               &quot;secrecy&quot;:10
            },
            {
               &quot;name&quot;:&quot;Foldy folder&quot;,
               &quot;secrecy&quot;:6
            }
         ],
         &quot;height&quot;:2,
         &quot;width&quot;:10
      },
      {
         &quot;added&quot;:&quot;2011-06-17&quot;,
         &quot;color&quot;:&quot;white&quot;,
         &quot;floor&quot;:10,
         &quot;folders&quot;:[
            {
               &quot;name&quot;:&quot;Fun pics&quot;,
               &quot;secrecy&quot;:0
            },
            {
               &quot;name&quot;:&quot;Old folder&quot;,
               &quot;secrecy&quot;:3
            }
         ],
         &quot;height&quot;:1,
         &quot;width&quot;:4
      }
   ]
}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Next step is to add subelements of Folders - Files. We already know how to do it, so let&#39;s make it quick.&lt;br /&gt;
&lt;br /&gt;
Adding a resource:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class FileResource(ModelResource):
    class Meta:
        queryset = File.objects.all()
        excludes = [&#39;id&#39;]
        include_resource_uri = False&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
And a field to Folder resource:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class FolderResource(ModelResource):
    files = fields.ToManyField(&#39;app.api.FileResource&#39;,
                               &#39;files&#39;, full=True)

    class Meta:
       ...&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
And now we have a response like this (excerpt):&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;....
&quot;objects&quot;:[
      {
         &quot;added&quot;:&quot;2011-06-17&quot;,
         &quot;color&quot;:&quot;black&quot;,
         &quot;floor&quot;:2,
         &quot;folder&quot;:[
            {
               &quot;file&quot;:[
                  {
                     &quot;archive_num&quot;:&quot;221122&quot;,
                     &quot;content&quot;:&quot;Lorem ipsum dolor sit amet, consectetur&quot;
                  }
               ],
               &quot;name&quot;:&quot;XFiles&quot;,
               &quot;secrecy&quot;:10
            },
            {
               &quot;file&quot;:[
                  {
                     &quot;archive_num&quot;:&quot;1111&quot;,
                     &quot;content&quot;:&quot;Duis aute irure dolor in reprehenderit
in voluptate velit esse&quot;
                  },
                  {
                     &quot;archive_num&quot;:&quot;333&quot;,
                     &quot;content&quot;:&quot;$1000&quot;
                  }
               ],
               &quot;name&quot;:&quot;Foldy folder&quot;,
               &quot;secrecy&quot;:6
            }
         ],
         &quot;height&quot;:2,
         &quot;width&quot;:10
      },
....&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Almost there. One issue left about Files model. It has field FileType that tells us quite useful information. It is implemented as ForeighKey, but our specification tells us to integrate it flat, without creating another sublevel. What we can do with it? Let&#39;s first create regular subresource.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class FileTypeResource(ModelResource):
    class Meta:
        queryset = FileType.objects.all()


class FileResource(ModelResource):
    type = fields.ToOneField(&#39;app.api.FileTypeResource&#39;,
                             &#39;type&#39;, full=True)

    class Meta:
        ...
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
That responses with&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;    ...
&quot;files&quot;:[
    {
        &quot;archive_num&quot;:&quot;221122&quot;,
        &quot;content&quot;:&quot;Lorem ipsum dolor sit amet, consectetur adipisicing elit&quot;,
        &quot;type&quot;:{
            &quot;id&quot;:&quot;4&quot;,
            &quot;resource_uri&quot;:&quot;&quot;,
            &quot;type&quot;:&quot;Evidence&quot;
        }
    }
 ],
...&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
We have&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;&quot;type&quot;:{
    &quot;id&quot;:&quot;4&quot;,
    &quot;resource_uri&quot;:&quot;&quot;,
    &quot;type&quot;:&quot;Evidence&quot;
 }&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
but we want only &lt;code&gt;&quot;type&quot;:&quot;Evidence&quot;&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Hopefully &lt;code&gt;tastypie&lt;/code&gt; has quite a lot of handy hooks that you can use to modify default behaviour. What we need now is &lt;code&gt;dehydrate&lt;/code&gt; method. It is called every time when object is prepared for serialization. &lt;code&gt;dehydrate&lt;/code&gt; receives two parameters:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;self&lt;/code&gt; - obviously, current resource object link&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bundle&lt;/code&gt; - object of Bundle class. It has two main fields:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;obj&lt;/code&gt; - a link to object that is being serialized&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data&lt;/code&gt; - dictionary with content of &lt;code&gt;obj&lt;/code&gt; prepared for serialization&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;By default &lt;code&gt;dehydrate&lt;/code&gt; simply returns &lt;code&gt;bundle&lt;/code&gt;, which is afterwards serialized into JSON dictionary (or other structure with subelements if response is not JSON). So, basically, everything we need is &lt;code&gt;dehydrate&lt;/code&gt; to return one bit of data - FileType.type &lt;br /&gt;
&lt;pre&gt;&lt;code&gt;class FileTypeResource(ModelResource):
    class Meta:
        queryset = FileType.objects.all()

    def dehydrate(self, bundle):
        return bundle.data[&#39;type&#39;]&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;code&gt;dehydrate&lt;/code&gt; is the right place to manipulate with data you&#39;re going to return to your client. Let&#39;s deal with what is left! Default response consist object &lt;code&gt;meta&lt;/code&gt;. It is generally useful to have some meta data attached to response. Without it things can become confusing when using offsets or other request modifiers. Nonetheless, sometimes we just have to get rid of it. There&#39;s a method for that! Method is called &lt;code&gt;alter_list_data_to_serialize&lt;/code&gt; and it starts after all dehydrations, right before serialization. We have to add it to our root &lt;code&gt;CabinetResource&lt;/code&gt;. : &lt;br /&gt;
&lt;pre&gt;&lt;code&gt;    def alter_list_data_to_serialize(self, request, data_dict):
        if isinstance(data_dict, dict):
            if &#39;meta&#39; in data_dict:
                # Get rid of the &quot;meta&quot;.
                del(data_dict[&#39;meta&#39;])

        return data_dict
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
To be clear, &lt;code&gt;dehydrate&lt;/code&gt; works on per-resource level, when &lt;code&gt;alter_list_data_to_serialize&lt;/code&gt; works with results of &lt;code&gt;dehydrate&lt;/code&gt; — generated python lists and dicts. And the last thing. Now root element is called &#39;objects&#39; and I don&#39;t like it. To rename  we add following to &lt;code&gt;alter_list_data_to_serialize&lt;/code&gt; &lt;br /&gt;
&lt;pre&gt;&lt;code&gt;    ...
    def alter_list_data_to_serialize(self, request, data_dict):
        if isinstance(data_dict, dict):
            if &#39;meta&#39; in data_dict:
                # Get rid of the &quot;meta&quot;.
                del(data_dict[&#39;meta&#39;])
                # Rename the objects.
                data_dict[&#39;cabinets&#39;] = copy.copy(data_dict[&#39;objects&#39;])
                del(data_dict[&#39;objects&#39;])
        return data_dict
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;useful-links&quot;&gt;Useful link&lt;br /&gt;
&lt;a href=&quot;http://www.stereoplex.com/blog/mobile-api-design-thinking-beyond-rest&quot;&gt;Mobile API Design - Thinking Beyond REST&lt;/a&gt;&lt;br /&gt;
not just for mobile, but for overall good REST API design&lt;/div&gt;Renaming is simple, though I still think there must be some option to set it without doing not-so-good things like this. That&#39;s it. There&#39;s a ton of features I didn&#39;t cover. This post is already much larger then I expected. I have to stop here or I will never publish it :)&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/9196783403800512560/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/06/easy-rest-api-with-django-tastypie.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/9196783403800512560'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/9196783403800512560'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/06/easy-rest-api-with-django-tastypie.html' title='Easy REST API with django-tastypie'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-3746414258440348329</id><published>2011-04-23T01:18:00.012+03:00</published><updated>2011-06-23T00:38:20.736+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="automation"/><category scheme="http://www.blogger.com/atom/ns#" term="git"/><title type='text'>Enhancing git commit message template</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;&lt;i&gt;My faveourite DVCS git has a method to generate (or at least prepopulate) your commit message. I&#39;m going to tell you how it works.&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
We&#39;re using a principle of feature branches at work. Every ticket, that is created on bugtracker receives it&#39;s own git branch. Sometimes you have to see all changes related to one ticket. But branches would be merged to master one day sooner or later so commits from different branches would be mixed. To solve this we have a convention, that every commit message has to start with &#39;ticket:###&#39;.&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
It is quite easy rule, but sometimes you forget to follow it (you&#39;re in a rush, or too sleepy etc). There&#39;s a simple answer for such things - automatization :) You can hook many different actions that happen in git. One of them is preparing commit message. So go to your project&#39;s dir. Then&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;cd .git/hooks/&lt;/code&gt;&lt;/pre&gt;create file &lt;code&gt;prepare-commit-message&lt;/code&gt;&lt;br /&gt;
put the following in it:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
ORIG_MSG_FILE=&quot;$1&quot;
TEMP=`mktemp /tmp/git-XXXXX`

TICKETNO=`git branch | grep &#39;^\*&#39; | sed &#39;s/[tT_]/ /g&#39; | cut -f 3 -d &#39; &#39;`

(echo &quot;ticket:$TICKETNO &quot;; cat &quot;$ORIG_MSG_FILE&quot;) &amp;gt; &quot;$TEMP&quot;
cat &quot;$TEMP&quot; &amp;gt; &quot;$ORIG_MSG_FILE&quot;&lt;/code&gt;&lt;/pre&gt;&lt;div style=&quot;text-align: right;&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: x-small;&quot;&gt;&lt;i&gt;Code was taken and tweaked a bit from StackOverflow &lt;a href=&quot;http://stackoverflow.com/questions/3525341/can-i-use-a-scripted-commit-template-for-git&quot;&gt;question&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;
This will work if a branch is named so it matches regexp: &lt;code&gt;t[0-9]+_.*&lt;/code&gt; (e.g. &lt;code&gt;t777_implement_feature&lt;/code&gt;)&lt;br /&gt;
&lt;br /&gt;
Everything is simple:&lt;br /&gt;
&lt;ul&gt;&lt;ol&gt;1. Creating a temporary file&lt;/ol&gt;&lt;ol&gt;2. Getting a ticket&#39;s number from branch&lt;/ol&gt;&lt;ol&gt;3. Writing &quot;ticket:&quot; and ticket number to temporary file AND content of message template file (it maybe there with this script together)&lt;/ol&gt;&lt;ol&gt;4. Making temporary file a file, that will be taken as a message template&lt;/ol&gt;&lt;/ul&gt;&lt;br /&gt;
And don&#39;t forget to make it runnable with:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;chmod +x prepare-commit_message&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
There&#39;s a lot more interesting things, that you can do with git hooks. Just imagine: start tests on your testserver, automatic deployment, send status emails to your boss ;). Isn&#39;t it great?&lt;br /&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/3746414258440348329/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/04/enhancing-git-commit-message-template.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/3746414258440348329'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/3746414258440348329'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/04/enhancing-git-commit-message-template.html' title='Enhancing git commit message template'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-6441262709152645405</id><published>2011-03-29T12:13:00.002+03:00</published><updated>2011-05-29T17:49:45.491+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><category scheme="http://www.blogger.com/atom/ns#" term="app inventor"/><title type='text'>Android App Inventor</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
I had an idea to develop for Android since bought this shiny, speedy, cooly Acer Liquid E. Anyone would like to! It&#39;s so smart, powerful and always stays with you literary everywhere! It is a great temptation not try to handle this power yourself.&lt;br /&gt;
&lt;br /&gt;
I installed all requirements for development, but since then didn&#39;t have much urge to try do something with it because Android SDK would take time to get into. I am more focuse at Python and Django stuff at the moment. Meanwhile, I remember there&#39;s a thing called &lt;a href=&quot;http://appinventor.googlelabs.com/&quot;&gt;Android App Inventor&lt;/a&gt; - a tool for creating android apps visually, without actual coding. That&#39;s an easy way to try something on Android.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
App Inventor consists of two parts: interface builder and block editor, that is used for adding logic to elements you&#39;ve added with interface builder.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs1Za5azazUYKKJOibz8UST_2HLbxpFT8Wel_fp1xxdIrJ1tn1OKpTkD2mkclifwXYsnGjZ9mA3DSzPSZdaAytOBgyVZw5z_igj8r78108R1fB9sM4QjGZl_sNpBgTa5OB3q6KjiYRAndD/s1600/interface-builder.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;App Inventor Interface Builder&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs1Za5azazUYKKJOibz8UST_2HLbxpFT8Wel_fp1xxdIrJ1tn1OKpTkD2mkclifwXYsnGjZ9mA3DSzPSZdaAytOBgyVZw5z_igj8r78108R1fB9sM4QjGZl_sNpBgTa5OB3q6KjiYRAndD/s320/interface-builder.png&quot; width=&quot;420&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
There&#39;s nothing special about Interface builder, actually. You have a pallete with standard visual elements like buttons, textfields, lists etc and some of non-visual components, like camera, image picker and, mind blowing component as for me - &lt;i&gt;speach recognition&lt;/i&gt;. Just imagine, you have an instrument to turn words you speak into text without writing single line of code!!! So I thought that I &lt;b&gt;have&lt;/b&gt; to do something with it, no matter will I use it or not. (Last time, when I saw another cool API - &lt;i&gt;face recognition&lt;/i&gt;, I made &lt;a href=&quot;http://draft.blogger.com/spycify.appspot.com&quot;&gt;Spycify&lt;/a&gt; :) )&lt;br /&gt;
&lt;br /&gt;
I decided to make some kind of a dictaphone app with speech recognition. It has to:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;add recognized text to existing text in textfield (standard voice recognition removes previous content from text field, and fills with last recognized)&lt;/li&gt;
&lt;li&gt;send whole text to Evernote&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Interface should be simple: text field and 3 buttons (start recognizing, send to evernote, clear). Elements positioning options are quite poor, so I couldn&#39;t place buttons exactly as I wanted. Text field didn&#39;t want to take whole space above buttons, so I hardcoded it&#39;s size. Now it looks OK in portrait orientation, but quite crappy in landscape :( Minus to App Inventor.&lt;br /&gt;
&lt;br /&gt;
Let&#39;s move to making the real magic happen. Here&#39;s the &quot;source code&quot; of my app. Everything is quite simple. These blocks are quite similar to puzzles. You have event&#39;s (e.g. button click), you can place actions inside them: set a variable, call something, run activity etc.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwo5u8PX_TLKKhdFfWv6-dE79ydTqHBaEYTXwIY8EjhGFQnqQNhznVnVSvFpRebPXMkV6Pt7mJWiNo5trGDx64V3feh7miM48g3SrJ9ebz-AQ5khRd5btfpOf1_dEOq5y068tdk-qYqwRk/s1600/block-editor.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwo5u8PX_TLKKhdFfWv6-dE79ydTqHBaEYTXwIY8EjhGFQnqQNhznVnVSvFpRebPXMkV6Pt7mJWiNo5trGDx64V3feh7miM48g3SrJ9ebz-AQ5khRd5btfpOf1_dEOq5y068tdk-qYqwRk/s320/block-editor.png&quot; width=&quot;420&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
As you can see, all you have to do to get your greatest speech recognised - call &lt;code&gt;SpeechRecognition.GetText&lt;/code&gt; somewhere and place &lt;code&gt;SpeechRecognition.AfterGettingText&lt;/code&gt; handler (square 1). That&#39;s it! You have a text that was recognized by those powerfool Google servers in a variable. You can assign it to a textfield, parse it, look for command words, everything you can imagine!&lt;br /&gt;
&lt;br /&gt;
In our case we&#39;re checking if textfield (called Note) is empty then just add recognized text. If there&#39;s already something in it, add a newline before adding recognized text.&lt;br /&gt;
&lt;br /&gt;
Another question is how to send your text to Evernote. There&#39;s one thing in Android called Activity. All the apps are built of them, and you can send special request (called Intent) to any app&#39;s Activity from your app, passing some variables to them. Evernote &lt;a href=&quot;http://www.evernote.com/about/developer/android.php&quot;&gt;published&lt;/a&gt; parameters, that can be passed to their app&#39;s (very kind move, thanks Evernote!).&lt;br /&gt;
&lt;br /&gt;
To start Activity (and send your Intent) you have special component ActivityStarter (who could have guessed...). Parameters to Evernote are passed in a special way with dictionary - you have to specify ExtraKey, and it&#39;s ExtraValue. For example, to preset a title of a new note, you should set: &lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;ExtraKey=EXTRA_TITLE;
ExtraValue=&#39;title you want&#39;&lt;/code&gt;&lt;/pre&gt;
Body of a note is set with &lt;code&gt;ExtraKey=EXTRA_TEXT&lt;/code&gt;. If you&#39;re makeing standard app with Android SDK you can set any number of those parameters. But ActivityStarter can send only one pair of these Extra&lt;i&gt;Things&lt;/i&gt;. If I&#39;m wrong, please-please, correct me in comments! So my app send only note&#39;s body, and it&#39;s name I type manually.&lt;br /&gt;
&lt;br /&gt;
That&#39;s it. The last handler (square 3) is clearing text field. As simple as it could be.&lt;br /&gt;
&lt;br /&gt;
You can try your app in emulator, or right on your device. Everything you need to know about installing, using and deploying can be found in &lt;a href=&quot;http://appinventor.googlelabs.com/learn/&quot;&gt;docs&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Yes, sure, you can&#39;t do Angry Birds with App Inventor. Nobody would try to, I think :) Moreover, App Inventor apps can&#39;t be published at Android Market (Google says they will allow it some day). There are geeks, who are &lt;a href=&quot;http://androidandme.com/2010/08/applications/building-a-twitter-client-with-google-app-inventor/&quot;&gt;building a Twitter app&lt;/a&gt; with it and even that&#39;s kind of crazy, I&#39;d say. But if you are creative enough you can do something like &lt;a href=&quot;http://www.jonq.com/jq/proposal/&quot;&gt;marriage proposal app&lt;/a&gt; for the love of your life ;)&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/6441262709152645405/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/03/android-app-inventor.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/6441262709152645405'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/6441262709152645405'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/03/android-app-inventor.html' title='Android App Inventor'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs1Za5azazUYKKJOibz8UST_2HLbxpFT8Wel_fp1xxdIrJ1tn1OKpTkD2mkclifwXYsnGjZ9mA3DSzPSZdaAytOBgyVZw5z_igj8r78108R1fB9sM4QjGZl_sNpBgTa5OB3q6KjiYRAndD/s72-c/interface-builder.png" height="72" width="72"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-4576255322901690763</id><published>2011-03-10T22:53:00.001+02:00</published><updated>2011-04-23T09:12:16.415+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="automation"/><category scheme="http://www.blogger.com/atom/ns#" term="bash"/><category scheme="http://www.blogger.com/atom/ns#" term="python"/><category scheme="http://www.blogger.com/atom/ns#" term="virtualenv"/><title type='text'>Simple automation of virtualenv creation</title><content type='html'>Working with python/django related things is a great fun if we&#39;re talking about actual development. But there&#39;s a couple of things around you have to keep in mind and manage manually. One of the most annoying thing you have to do is activating different virtual environments.&lt;br /&gt;
&lt;br /&gt;
I have a few projects on my local machine, most of them have separate virtualenvs, so when you have to move from one project to another you have to deactivate one, activate other and only then start working. When I was starting developing I thought it&#39;s not a big deal - only two commands actually. But after a tens of switches you&#39;re starting to get angry. Solution for that was found on &lt;a href=&quot;http://djangoandother.blogspot.com/2010/12/activationdeactivation-of-python.html&quot;&gt;Max Klymyshyn blog&lt;/a&gt;. There&#39;s only one thing to mention - on Ubuntu I placed Max&#39;s code at &lt;code&gt;$HOME/.bashrc&lt;/code&gt;, because Ubuntu doesn&#39;t respect &lt;code&gt;.bash_profile&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
But during last weekend I decided to try a couple of projects from github. And for everyone of them I had to create separate virtual environment (and as I&#39;m so lazy I made it automatically started). While creating a new virtualenv, I thought that this whole procedure deserves a bit of automatization too. So I made a little bash script, that creates new clean virtual environment in a new directory. Here&#39;s a script:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
if [ $1 ]
then
     echo &#39;Making new dir&#39; $1
    mkdir $1
    cd $1

    echo &#39;Creating new dev environment&#39;
    virtualenv .env --no-site-packages
    echo VIRTUALENV_PATH=`pwd`/.env &gt; .venv
else
    echo &#39;Parameter is required for dir name&#39;
fi&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
It only checks that you&#39;ve specified a name for a new directory, where virtualenv is going to be created. I put it to &lt;code&gt;$HOME/bin/newenv&lt;/code&gt; file, and  &lt;code&gt;chmod +x newenv&lt;/code&gt; to make it runnable. So now every time I want to try something new from github I have to type only one command, that creates a great place for experiments, which virtualenv is activated right when I &lt;code&gt;cd&lt;/code&gt; into this directory.&lt;br /&gt;
&lt;br /&gt;
At last, I &lt;a href=&quot;http://debianworld.ru/articles/nastrojka-bash-prompt-dlya-subversion-git-i-mercurial/&quot;&gt;tweaked (in russian)&lt;/a&gt; my bash prompt. Now it shows if current dir is svn/hg/git repo, and if so, show the name of current branch and if it has not committed changes. It looks geeky as hell, but not so informative and usefull as I expected :) It slows down using of terminal (cause it runs all svn, hg and git twice after &lt;i&gt;every&lt;/i&gt; command on terminal: first to get a branch name, and second - for status). And it takes a half of command line space. So, there&#39;s more cons then pros, but I&#39;ll see, maybe I&#39;ll get used to it so wouldn&#39;t be able to remove it.</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/4576255322901690763/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/03/simple-automation-of-virtualenv.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/4576255322901690763'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/4576255322901690763'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/03/simple-automation-of-virtualenv.html' title='Simple automation of virtualenv creation'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-7880866523399240424</id><published>2011-02-27T22:18:00.010+02:00</published><updated>2011-06-05T19:39:03.666+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="google app engine"/><category scheme="http://www.blogger.com/atom/ns#" term="python"/><category scheme="http://www.blogger.com/atom/ns#" term="spycify"/><category scheme="http://www.blogger.com/atom/ns#" term="testing"/><title type='text'>Testing Google App Engine application</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;There was a time when I did no testing at all. (As The Kooks sing &quot;You&#39;re so naive&quot;). And Spycify was started and achieved it&#39;s first releases with about 0 tests.&amp;nbsp;Those first versions were working, but had a whole bunch of annoying bugs. The only way I had is to do massive refactoring of whole app.&lt;br /&gt;
&lt;br /&gt;
Previos versions were checked manually. I started a devserver, clicked through changed parts to see does it works. Sometimes this would not help and I had to upload app to production to test it there, manually, of course.&amp;nbsp;Massive refactoring was a nice chance to start using automate testing. No more manual clicking and repeating myself.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
At &lt;a href=&quot;http://42coffeecups.com/&quot;&gt;42cc&lt;/a&gt; we practise TDD. I can&#39;t say I&#39;m a big fan of it, though it has it&#39;s benefits. I decided that I would not write test for every single bit of code. Unittests for main parts of model and functional test or two for main workflow.&lt;br /&gt;
&lt;h3&gt;Installing&lt;/h3&gt;&lt;br /&gt;
Testing of GAE apps needs a bit of tools. First we need nice way to run our tests. Plugin &lt;a href=&quot;http://farmdev.com/projects/nosegae/&quot;&gt;nose-gae&lt;/a&gt; for nosetest works great. To install it run in console:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;pip install NoseGAE&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
To run tests:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;nosetests -v --with-gae&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;h3&gt;Unittesting&lt;/h3&gt;&lt;br /&gt;
It is done with plain unittest and mock modules. As I was not going to test a connection to face.com, which I am using for actual detecting faces, I saved json output for one of photos. In test I put this json into a model and run a method, that parses json, rejects recognitions with low level of convinience (if face.com is only 50% sure it is face, it is better to leave it alone without putting anything on it. Such results often get shirts, other wierd things) finds coordinates for glasses and moustaches. No actual working with datastore involved.&lt;br /&gt;
&lt;br /&gt;
So, basically, my test only loads input and tests everything is present, checked and understood right in a parsed json.&lt;br /&gt;
&lt;br /&gt;
This tests were not a big deal, actually. But they helped me to find lots of syntax errors, wrong imports and other bugs. As I was working on Spycify on weekends and never started a dev_server with it for about a month. So unittests helped me to find out that my model is generally speaking is working.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Functional tests&lt;/h3&gt;&lt;br /&gt;
First functional test was meant to test that photo can be uploaded, then it processed and made right output. As I&#39;m not going to analyze resulting image - find that we don&#39;t end up on one of error pages. This took a little bit more to establish. GAE SDK doesn&#39;t have testing infrastructure, as a django. So you have to create your own WSGI application and operate with it. I googled to see how people are doing it.&lt;br /&gt;
This is a part of my test_http.py, that checks workflow of Spycify:&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;import os
import re

from webtest import TestApp
from spycify.main import application

from wsgiproxy.exactproxy import proxy_exact_request

SERVER_NAME = os.environ[&#39;SERVER_NAME&#39;]
SERVER_PORT = os.environ[&#39;SERVER_PORT&#39;]

app = TestApp(proxy_exact_request, extra_environ={
                                    &#39;wsgi.scheme&#39;: &#39;http&#39;,
                                    &#39;SERVER_NAME&#39;: SERVER_NAME,
                                    &#39;SERVER_PORT&#39;: SERVER_PORT,
                                    &#39;HTTP_HOST&#39;: &#39;%s:%s&#39; % (SERVER_NAME,SERVER_PORT)
                                  })

def test_index():
    response = app.get(&#39;/&#39;)
    response.mustcontain(&#39;Spycify&#39;,
                         &#39;&amp;lt;form&#39;,
                        )&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
At first I was trying to run separate testing server on seperate port. But it I couldn&#39;t make it run on SERVER_NAME and SERVER_PORT I wanted. Maybe this is not the best decision, but I decided to run a real dev server, and proxy all requests to dev_server. &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Testing file upload&lt;/b&gt;&lt;br /&gt;
I thought I will get in trouble with testing file uploading. But that works the best way I could expect. Webtest, that is used for functional testing, doesn&#39;t run javascript, so in test we have to implement all AJAX-requests manually. For example, as I told in previous post, you have to get new upload url for every file upload. Now it is implemented by making separate request, that returns an url for upload. That script inserts url into upload form&#39;s action method.&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;response = app.get(&#39;/upload_url&#39;)
url_pattern = re.compile(r&#39;http\:\/\/\w+\:\d+(\/.*)&#39;)
upload_url = url_pattern.match(response.body).groups()[0]

filename = &#39;tests/ivan.jpg&#39;
response = app.post(upload_url,
        {&#39;img_type&#39;: &#39;photo&#39;},
         upload_files=((&#39;file&#39;, filename),
)) &lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Here we at first making a request to get an url. Then we take last part of url (without server name and port). And then app.post, passing a filename will upload file to app. Easy as pie!&lt;br /&gt;
&lt;br /&gt;
Next page needs some care too. To fit the rule &quot;Show something to user as soon as possible&quot; I&#39;m doing all the picture stuff in background. When user hits &quot;Upload&quot; his photo uploads to the server where it gets a number. Right after user is redirected a page with that number where he sees &#39;Loading...&#39; message. On this page request is made to a server, that tells him to start photo processing. When it is done a response from server contains image link and the rest of the page.&lt;br /&gt;
&lt;br /&gt;
So for all of this to work we have to strip photo id to make all request, that javascript does, manually. And when all of them are done and we reach last response from server the only thing is left - to check response consist img tag in it.&lt;br /&gt;
&lt;br /&gt;
That&#39;s basically all the testing I have at the moment. Next test will be added when new bugs will be found or new features implemented.&lt;br /&gt;
&lt;br /&gt;
There&#39;s one problem in such setup. There&#39;s no database clearing after each test. My app is not really about data, so I can manage without such feature. But on other apps I&#39;d like to find out the way to do it. There must be something already done for it.&lt;br /&gt;
&lt;br /&gt;
And for the ending a video about testing techniques at Google App Engine. I haven&#39;t seen it yet, but going to :)&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&#39;allowfullscreen&#39; webkitallowfullscreen=&#39;webkitallowfullscreen&#39; mozallowfullscreen=&#39;mozallowfullscreen&#39; width=&#39;320&#39; height=&#39;266&#39; src=&#39;https://www.youtube.com/embed/bcyz3sGa1Qo?feature=player_embedded&#39; frameborder=&#39;0&#39;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/7880866523399240424/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/02/testing-google-app-engine-application.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/7880866523399240424'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/7880866523399240424'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/02/testing-google-app-engine-application.html' title='Testing Google App Engine application'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-7038580699240598166</id><published>2011-02-17T17:01:00.011+02:00</published><updated>2011-05-29T17:49:54.460+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="face recognition"/><category scheme="http://www.blogger.com/atom/ns#" term="google app engine"/><category scheme="http://www.blogger.com/atom/ns#" term="python"/><title type='text'>Spycify them all!</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Recently I&#39;ve launched my first site, that was revealed to some friends whose critisism I&#39;m not afraid of :). It is called &lt;a href=&quot;http://spycify.appspot.com/&quot;&gt;Spycify&lt;/a&gt;, and was made just for fun using Python, Google App Engine and some outside APIs.&lt;br /&gt;
&lt;br /&gt;
Actually I saw facedetection API on &lt;a href=&quot;http://www.programmableweb.com/&quot;&gt;Programmable Web&lt;/a&gt;, and decided that I should do something with it. There was no doubt on selecting programming language. It is Python for sure. As a hosting/framework I&#39;ve chosen &lt;a href=&quot;http://code.google.com/appengine/&quot;&gt;Google App Engine&lt;/a&gt;. I see it as a free place for little experiments. I know my service wouldn&#39;t need any highscalability, performancability or other cool-abilities. This is just a good place to host a little project, fast to code, fast to deploy. I know that if I place my service on GAE it will be there &lt;s&gt;forever&lt;/s&gt;&amp;nbsp;some forseeable future, so I can come back to it, if I want to and show it. There&#39;s no need to think about hosting, domain name or something like this - it &quot;just works&quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Of course using GAE puts some restrictions and makes you do something in quite wierd way. I&#39;ll try to tell about places that made me scratch my nape to think why did Google did it and how to deal with it.&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;
&lt;/b&gt;&lt;br /&gt;
&lt;b&gt;File upload handler&lt;/b&gt;. That was one of my first surprises - you need to get special url for POST request&amp;nbsp;from GAE to upload file (see &lt;a href=&quot;http://code.google.com/appengine/docs/python/tools/webapp/blobstorehandlers.html#BlobstoreUploadHandler&quot;&gt;&lt;code&gt;blobstore.create_upload_url()&lt;/code&gt;&lt;/a&gt;). I have no experience anywhere else, but I can&#39;t understand why do I need to do it. At first I placed this call to my view function, that responses with a&amp;nbsp;photo upload&amp;nbsp;page. But soon I realized this is a bad place for it if I want my users to upload more then one photo. The fastest and most obvious way to return on previous page is Back browser button. But because cached page was returned with old, &quot;used&quot; upload url, which you can&#39;t use (server raises exception). So I need to update it every time user visits a page. I ended up creating special handler, that only does return of new upload url and requested it in jQuery&#39;s &lt;code&gt;ready()&lt;/code&gt;. Something like:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;$(document).ready(function(){ 
   $.get(&#39;/upload_url&#39;, function(data) {
      $(&#39;div.upload-form &amp;gt; form&#39;)[0].action = data });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
See &lt;a href=&quot;https://groups.google.com/d/topic/tipfy/AYb-8bAm990/discussion&quot;&gt;topic&lt;/a&gt; on this issue at tipfy (GAE framework) mailing list for more.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;GAE image and blob handling inconsistancy. &lt;/b&gt;There are two kinds of model fields for blob-values. One of them is &lt;code&gt;&lt;a href=&quot;http://code.google.com/appengine/docs/python/blobstore/blobinfoclass.html&quot;&gt;BlobInfo&lt;/a&gt;&lt;/code&gt;, which is given to you when you process uploaded files. More for that, it is a datastore object itself, so you can get it from the store by key and (most interesting for me) use Picasa infrastructure to serve such images to user. BUT you can&#39;t create it manually - it is made only by GAE with uploaded files.&lt;br /&gt;
&lt;br /&gt;
Other field is plain &lt;code&gt;BlobProperty&lt;/code&gt;, which only holds a byte-string of something.&amp;nbsp;The main problem is that if you mant somehow modify content of uploaded file and then save it, you can do it only in a plain &lt;code&gt;BlobProperty&lt;/code&gt;, no &lt;code&gt;BlobInfo&lt;/code&gt; object, no cool, fast, efficient Picasa resources to show your pics. Only reading them from datastore and serving them with an ugly view.&lt;br /&gt;
&lt;br /&gt;
That&#39;s all, basically. I&#39;m using web.py + django templates, a which are in a box with GAE. I didn&#39;t try to use django ports to GAE, because I don&#39;t need them for such small project.&lt;br /&gt;
&lt;br /&gt;
I still have many things to polish and to clean. I&#39;d like to add sharing not only to vkontakte, but to facebook and buzz. But it is already usable. So please, &lt;a href=&quot;http://spycify.appspot.com/&quot;&gt;go try&lt;/a&gt; it and please tell me what you think about it!&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/7038580699240598166/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/02/spycify-them-all.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/7038580699240598166'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/7038580699240598166'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/02/spycify-them-all.html' title='Spycify them all!'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-4283155280885195367</id><published>2011-01-23T00:12:00.005+02:00</published><updated>2015-01-10T21:43:54.574+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ppa"/><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu"/><title type='text'>Useful PPA list</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
I&#39;m using Ubuntu as my primary OS. It serves me well and I like it. I know some of it&#39;s bugs and problems and nice ways to avoid them to live in peace and harmony.&lt;br /&gt;
&lt;br /&gt;
After removing previous version (because it was death slow after my horrible experiments) and installing Ubuntu 10.10 Maverick Meerkat I realised that some of my favourite software is missing. That was because PPA (&lt;a href=&quot;https://help.launchpad.net/Packaging/PPA&quot;&gt;Personal Package Archive&lt;/a&gt;) died together with previous install of Ubuntu. So I had to recollect a list of my daily used software and search for a correct PPA. &lt;br /&gt;
&lt;br /&gt;
So to prevent such failures in future I decided to make a list of useful PPAs:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:docky-core/ppa&lt;/span&gt; - is a development PPA for a great nice looking dock, written with Mono, if it means something to you&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:ubuntu-on-rails/ppa&lt;/span&gt; - a set of plugins, themes and code highlighting schemes for gEdit for RoR development, like TextMate. In fact it suits for Django&#39;ers too, and I was using it for quite a long time. But now a Gemini plugin is broken for me (&lt;a href=&quot;https://github.com/gmate/gmate/issues/issue/51&quot;&gt;issue&lt;/a&gt; on github) so I&#39;m trying to switch to vim. Process goes slowly and painfully and when it will come to end I&#39;ll report it here. This PPA gives you good editor that is both powerful and fast (not to mention monsters like PyDev and PyCharm) to do almost everything you need. With vim I just want some more power and opportunities to shoot myself in a leg :)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:am-monkeyd/nautilus-elementary-ppa&lt;/span&gt; patches your Nautilus and makes it sleek and sexy. Many unnecessary elements of it removed, it looks cleaner and more usable. It also contains Gloobus Preview, wich is another nice &quot;MacOS X&quot; kind of app.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:team-xbmc/unstable&lt;/span&gt; - fresh version of XBMC - big multimedia combine. (Until now PPA  on this position called &lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:team-xbmc/ppa&lt;/span&gt;. But they didn&#39;t make any updates since Maverick, so I decided to move to &lt;i&gt;unstable&lt;/i&gt; XBMC release) &lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:chromium-daily/ppa&lt;/span&gt; - this is what I can&#39;t live without. Daily builds of opensource version of Google Chrome - Chromium. It&#39;s great to see improvements here and there of the software that is updated &lt;i&gt;every&lt;/i&gt; day. The downside - sometimes, when they do really great changes, you can suffer much. It&#39;s bleeding edge for sure :)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:ubuntu-wine/ppa&lt;/span&gt; - newer version of Wine, that works faster and better.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;ppa:ubuntu-desktop/ppa&lt;/span&gt; - fresher version of Unity, Ubuntu&#39;s default desktop environment. New features and fixes right from developers!&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:webupd8team/sublime-text-2&lt;/span&gt; - text editor that &lt;a href=&quot;http://2011/10/sublime-text-2-for-django-developer.html&quot;&gt;I use daily&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;ppa:mitya57/ppa&lt;/span&gt; - PPA which has nice Markdown editor Retext with preview and tabs &lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
There&#39;s a few more of PPA in my OS, but I&#39;m not sure what they are doing and do I actually need them :) I guess they were part of my experiments.&lt;br /&gt;
&lt;br /&gt;
To install any of those PPAs type in terminal:&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;sudo apt-add-repository ppa:ubuntu-wine/ppa&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;sudo apt-get update&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
After that you can install software from PPA with&lt;br /&gt;
&lt;span style=&quot;font-family: &#39;Courier New&#39;,Courier,monospace;&quot;&gt;sudo apt-get install ...&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
But you have to know exact package name of the app in PPA, which is not the name of PPA itself. Chromium package in chromium-daily PPA is chromium-browser. It is much easier to go to &lt;i&gt;Software Center&lt;/i&gt;, where all of your PPA are listed and see what you actually need to install.&lt;br /&gt;
&lt;br /&gt;
In the end I want to say that PPA is a handy way to keep your software up to date, see all the new shiny features right away. But quite a big problem with PPA is that they do not support dependencies more then in main Ubuntu repositories. It means it can break your system if you install something from PPA that was made without needed care and love. So watch out! &lt;br /&gt;
&lt;br /&gt;
Tell me other interesting PPAs if you know any!&lt;br /&gt;
&lt;br /&gt;
P.S. As Blogger Stat tells me, some people googles here in searching &lt;code&gt;Opera PPA&lt;/code&gt;. I wouldn&#39;t comment my attitude to Opera, but I&#39;ll try to ease your pain and tell you the answer (taken from &lt;a href=&quot;http://www.ubuntuupdates.org/ppas/15&quot;&gt;UbuntuUpdates&lt;/a&gt;) in 3 steps:&lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;&lt;b&gt;1.&lt;/b&gt; wget -O - http://deb.opera.com/archive.key | sudo apt-key add -
&lt;b&gt;2.&lt;/b&gt; sudo sh -c &#39;echo &quot;deb http://deb.opera.com/opera/ stable non-free&quot; &amp;amp;gt;&amp;amp;gt; 
/etc/apt/sources.list.d/opera.list&#39;
&lt;b&gt;3.&lt;/b&gt; sudo aptitude update &amp;amp;amp;&amp;amp;amp; sudo aptitude install opera  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/4283155280885195367/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/01/my-ubuntu-ppa-list.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/4283155280885195367'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/4283155280885195367'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/01/my-ubuntu-ppa-list.html' title='Useful PPA list'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4309086861920339739.post-6359892705174057500</id><published>2011-01-22T21:09:00.008+02:00</published><updated>2011-06-23T01:06:54.898+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="offtopic"/><title type='text'>Starting up!</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;I was going to create some kind of technical blog for quite a long time. I have livejournal, but that is definitely not the place to write anything IT-specific. But plans are still only plans until you sit and do what you&#39;ve planned, so now I&#39;m here.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I want to blog in English, though my native languages are Russian and Ukrainian. It will be a challenge for my, but I hope it wouldn&#39;t be a problem. Don&#39;t be shy to leave comments in any language, I know how to use Google Translate ;)&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.brabadu.com/feeds/6359892705174057500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://blog.brabadu.com/2011/01/starting-up.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/6359892705174057500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4309086861920339739/posts/default/6359892705174057500'/><link rel='alternate' type='text/html' href='http://blog.brabadu.com/2011/01/starting-up.html' title='Starting up!'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/10227648347754614374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>