Ole Morten's Blog Feedhttp://www.omh.cc/Latest blog entriesen-usFri, 13 Jan 2017 10:39:57 +0100The case for why eZ should adopt Ember.js for the new administration interfacehttp://www.omh.cc/blog/2017/jan/13/why-ez-should-adopt-emberjs/ <p>In a recent <a href="http://share.ez.no/blogs/ez/2.x-refactoring-work-round-one-deciding-on-ui-technology">blog post</a> eZ outlined some pain points with the current technology (<span class="caps">YUI</span>) used for their <span class="caps">SPA</span> administration interface and expressed a desire for alternatives for the next major&nbsp;version. </p> <p>I would like to make the case for why eZ should consider <a href="http://emberjs.com/">Ember.js</a> as the JavaScript framework of&nbsp;choice. </p> <p>The rapid pace of development in the JavaScript world is very exciting and we see new cool technology popup almost every day, but this has a very significant drawback. How do you choose a framework or library to base your product upon that won&#8217;t be tossed aside in a few months or a year for something new and&nbsp;shiny? </p> <p>With a history going back to 2007, an eternity in the JavaScript world, Ember.js has managed to stay relevant and modern by today&#8217;s standard, despite its relative old age. Ember.js aims to continually improve and adapt the best ideas while also providing a smooth upgrade path without the throw-everything-away-and-rewrite-everything-from-scratch approach. <em>This</em>, I believe, is the biggest benefit of Ember. Things like a predicable release schedule and <span class="caps">LTS</span> releases matter a lot when you&#8217;re building big serious applications where you can&#8217;t just rewrite things because there&#8217;s something newer and cooler out&nbsp;there. </p> <p>The other benefit of Ember I&#8217;d like to highlight is the <a href="https://ember-cli.com/">ember-cli</a>. Ember-cli is what makes working with Ember fun and productive. It gives you a new project with live reloading, es6 transpiling with babel.js, a build system that concats and minifies, a test framework and a lot more, all out of the box. If you&#8217;ve ever built your own build system with Gulp or similar tools you&#8217;ll no doubt appreciate this. Ember-cli is also how you extend Ember. Want to use Sass? <code>ember install ember-cli-sass</code> and you&#8217;re done. IndexedDB support? <code>ember install ember-indexeddb</code>. Oauth2? <code>ember install ember-oauth2</code>. </p> <p>In my opinion picking a framework or library is more about the tooling, community, philosophy and process behind it than pure technical features. Features can easily be added and changed, but it&#8217;s much harder to build a sustainable community around the project. This is something I think Ember.js has achieved very&nbsp;well. </p> <p>So go ahead, take it for a&nbsp;spin! </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3</pre></td><td class="code"><div class="codehilite"><pre>npm install -g ember-cli ember new mycoolapp cd mycoolapp &amp;&amp; ember serve </pre></div> </td></tr></table>Ole Morten HalvorsenFri, 13 Jan 2017 10:39:57 +0100http://www.omh.cc/blog/2017/jan/13/why-ez-should-adopt-emberjs/Integration tests are a scamhttp://www.omh.cc/blog/2012/dec/14/integration-tests-are-scam/ <p>Best presentation on testing I&#8217;ve seen so&nbsp;far. </p> <blockquote><p>Integration tests are a scam. A self replicating virus that threaten the very health of your code base, your sanity and I&#8217;m not exaggerating when I say your&nbsp;life. </p> </blockquote><p><a href="http://www.infoq.com/presentations/integration-tests-scam"><span class="caps">J. B.</span> Rainsberge &#8212; Integration tests are a&nbsp;scam</a> </p>Ole Morten HalvorsenFri, 14 Dec 2012 09:08:58 +0100http://www.omh.cc/blog/2012/dec/14/integration-tests-are-scam/VIM: Automatically insert words into CtrlPhttp://www.omh.cc/blog/2012/dec/6/vim-automatically-insert-words-ctrlp/ <p>If you use the excellent <a href="/media/images/Screen_Shot_2012-12-07_at_22.13.13__post.png">CtrlP</a> plugin for <span class="caps">VIM</span> here&#8217;s a quick tip. You can automatically insert the word under your cursor into CtrlP. This is super handy especially for <span class="caps">PHP</span> development where class names often map to file names 1:1. To do this we need to create a mapping that will bring up CtrlP, press <code>&lt;C-\&gt;</code> and then press <code>w</code>: </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre>nmap &lt;leader&gt;lw :CtrlP&lt;CR&gt;&lt;C-\&gt;w </pre></div> </td></tr></table><p>If you want to insert a selected line straight into CtrlP you can do that as&nbsp;well: </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre>vmap &lt;leader&gt;lw y:CtrlP&lt;CR&gt;&lt;C-\&gt;c </pre></div> </td></tr></table><p><img src="/media/images/Screen_Shot_2012-12-07_at_22.13.13__post.png" alt="CtrlP"/> </p> <p>As an example, in the above image the cursor is placed on<code>eZINI</code>. Pressing <code>&lt;leader&gt;lw</code> in will open CtrlP, insert and search for <code>eZINI</code>. </p> <p><img src="/media/images/Screen_Shot_2012-12-07_at_22.13.25__post.png" alt="CtrlP 2"/> </p> <p>Pretty&nbsp;neat! </p>Ole Morten HalvorsenThu, 06 Dec 2012 15:07:08 +0100http://www.omh.cc/blog/2012/dec/6/vim-automatically-insert-words-ctrlp/Django: Inserting and Positioning Imageshttp://www.omh.cc/blog/2008/aug/18/django-inserting-and-positioning-images/ <p>A few days ago I stumbled upon <a href="http://groups.google.com/group/django-users/browse_thread/thread/dbed6e49c0a0cd12?hl=en">a thread over at the Django users mailing list</a> discussing how to handle media in <a href="http://www.djangoproject.com">Django</a>. It raised the interesting question of how do you associate images and files with your articles, blog posts, etc. When I started this blog I &#8212; too &#8212; realized I needed a way to insert and position images in my blog posts. Django only gives you a way to store text and images and leaves the rest up to us, the users of&nbsp;Django. </p> <p>My solution uses <a href="http://daringfireball.net/projects/markdown/">Markdown</a> as the markup language. All my post are written and stored in the database as Markdown. Markdown might be too low-level for end users or non technical editors. It is more than user-friendly enough for me, but your milage may vary on this depending who&#8217;s creating the content on your&nbsp;site. </p> <p>One of the nice things about Markdown is that it has a very handy syntax for inserting images without having to type any&nbsp;<span class="caps">HTML</span>: </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2</pre></td><td class="code"><div class="codehilite"><pre>![Alt text][id] [id]: url/to/image &quot;Optional title attribute&quot; </pre></div> </td></tr></table><p>Running the above through Markdown will convert it into a nice <code>&lt;img&gt;</code> tag. However, I would have to manually figure out and insert the path to all my images, not fun. To fix this annoying figure-out-the-url-manually, I added some pre-processing to the Markdown which dynamically creates the Markdown image references. This removes the need of manually figuring out image&nbsp;paths. </p> <p>As an example, something like&nbsp;this: </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3 4 5 6 7 8</pre></td><td class="code"><div class="codehilite"><pre># Hello, World! Lorem ipsum dolor sit amet, consectetur adipisicing elit ![ImageOne][] ![ImageTwo][] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat </pre></div> </td></tr></table><p>Will be turned&nbsp;into: </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11</pre></td><td class="code"><div class="codehilite"><pre># Hello, World! Lorem ipsum dolor sit amet, consectetur adipisicing elit ![ImageOne][] ![ImageTwo][] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat [ImageOne]: /media/imageone.png [ImageTwo]: /media/imagetwo.png </pre></div> </td></tr></table><p>By having a <code>ManyToMany</code> relationship to <code>Image</code> on my <code>Post</code> model I can associate images and position them using by insert <code>![ImageName]</code> into the markdown. You can see how this works out in the admin from the screenshot&nbsp;below. </p> <p><img src="/media/images/Django_Admin_-_Inserting_Images_post.png" alt="Image Insertion in Admin"/> </p> <p>The Markdown processing is done using a helper function, <code>markdown_to_html</code>. It generates Markdown image references from a list of images, inserts the references at the bottom of the Markdown text and turns the Markdown into&nbsp;<span class="caps">HTML</span>. </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33</pre></td><td class="code"><div class="codehilite"><pre><span class="c"># models.py</span> <span class="k">import</span> <span class="nn">datetime</span> <span class="k">import</span> <span class="nn">markdown</span> <span class="k">from</span> <span class="nn">django.db</span> <span class="k">import</span> <span class="n">models</span> <span class="k">from</span> <span class="nn">django.conf</span> <span class="k">import</span> <span class="n">settings</span> <span class="k">def</span> <span class="nf">markdown_to_html</span><span class="p">(</span> <span class="n">markdownText</span><span class="p">,</span> <span class="n">images</span> <span class="p">):</span> <span class="n">image_ref</span> <span class="o">=</span> <span class="s">&quot;&quot;</span> <span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">images</span><span class="p">:</span> <span class="n">image_url</span> <span class="o">=</span> <span class="n">settings</span><span class="o">.</span><span class="n">MEDIA_URL</span> <span class="o">+</span> <span class="n">image</span><span class="o">.</span><span class="n">image</span><span class="o">.</span><span class="n">url</span> <span class="n">image_ref</span> <span class="o">=</span> <span class="s">&quot;</span><span class="si">%s</span><span class="se">\n</span><span class="s">[</span><span class="si">%s</span><span class="s">]: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span> <span class="n">image_ref</span><span class="p">,</span> <span class="n">image</span><span class="p">,</span> <span class="n">image_url</span> <span class="p">)</span> <span class="n">md</span> <span class="o">=</span> <span class="s">&quot;</span><span class="si">%s</span><span class="se">\n</span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span> <span class="n">markdownText</span><span class="p">,</span> <span class="n">image_ref</span> <span class="p">)</span> <span class="n">html</span> <span class="o">=</span> <span class="n">markdown</span><span class="o">.</span><span class="n">markdown</span><span class="p">(</span> <span class="n">md</span> <span class="p">)</span> <span class="k">return</span> <span class="n">html</span> <span class="k">class</span> <span class="nc">Image</span><span class="p">(</span> <span class="n">models</span><span class="o">.</span><span class="n">Model</span> <span class="p">):</span> <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span> <span class="n">max_length</span><span class="o">=</span><span class="mf">100</span> <span class="p">)</span> <span class="n">image</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ImageField</span><span class="p">(</span> <span class="n">upload_to</span><span class="o">=</span><span class="s">&quot;image&quot;</span> <span class="p">)</span> <span class="k">def</span> <span class="nf">__unicode__</span><span class="p">(</span> <span class="bp">self</span> <span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="k">class</span> <span class="nc">Post</span><span class="p">(</span> <span class="n">models</span><span class="o">.</span><span class="n">Model</span> <span class="p">):</span> <span class="n">title</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span> <span class="n">max_length</span><span class="o">=</span><span class="mf">100</span> <span class="p">)</span> <span class="n">body</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span> <span class="n">images</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ManyToManyField</span><span class="p">(</span> <span class="n">Image</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">True</span> <span class="p">)</span> <span class="k">def</span> <span class="nf">body_html</span><span class="p">(</span> <span class="bp">self</span> <span class="p">):</span> <span class="k">return</span> <span class="n">markdown_to_html</span><span class="p">(</span> <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">images</span><span class="o">.</span><span class="n">all</span><span class="p">()</span> <span class="p">)</span> </pre></div> </td></tr></table><p>Please note that the <code>markdown_to_html</code> function uses <code>image.image.url</code> to grab the image <span class="caps">URL</span>. This only works after the <a href="http://code.djangoproject.com/wiki/FileStorageRefactor">file storage re-factoring</a>. </p> <p>As you can see on line 32, the <code>Post</code> class has a <code>body_html</code> method which calls <code>markdown_to_html</code>. We can call this in the templates to get the final <span class="caps">HTML</span> output. If performance is important, you probably want to cache the conversion result when saving your&nbsp;model. </p> <p>I&#8217;m very interested to hear about other solutions. If you have tackled this type of issue, please add a comment describing how you did&nbsp;it. </p>Ole Morten HalvorsenMon, 18 Aug 2008 16:07:20 +0100http://www.omh.cc/blog/2008/aug/18/django-inserting-and-positioning-images/Adding Your Twitter Status to a Django Sitehttp://www.omh.cc/blog/2008/aug/4/adding-your-twitter-status-django-site/ <p>As part of changing the layout of my blog I wanted to add my latest <a href="http://twitter.com/omh">Twitter status</a> to the bottom left corner. It turned out to be very straight forward. Python has an excellent <a href="http://code.google.com/p/python-twitter/">Twitter <span class="caps">API</span></a> &#8212; very litle code was&nbsp;required. </p> <p>There&#8217;s two ways you could go about doing this. The first way is to write a <a href="http://www.djangoproject.com/documentation/templates_python/#writing-custom-template-tags">template tag</a> that would fetch the latest tweet, the other to create a <a href="http://www.djangoproject.com/documentation/templates_python/#writing-your-own-context-processors">context processor</a> that would provide a tweet variable to your templates. I choose the&nbsp;latter. </p> <p>Before we can start, the Twitter <span class="caps">API</span> module needs to be installed. You can find both the module and installation instruction over on google code: <a href="http://code.google.com/p/python-twitter/">http://code.google.com/p/python-twitter/</a>. </p> <p><em>Note</em>: I had to install the <span class="caps">SVN</span> version as there&#8217;s a <a href="http://groups.google.com/group/django-users/browse_thread/thread/2f61248c9dcaf7fb/606646e7ef0f9c0e?hl=en">bug</a> in python-twitter version 0.5 when using Apache &#8212; it works fine using the development&nbsp;server. </p> <p>To set up a context processor we first need to specify where Django can find our processors. This is done using the <code>TEMPLATE_CONTEXT_PROCESSORS</code> setting. Add the below to your project&#8217;s <code>settings.py</code> file. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3 4 5 6 7 8</pre></td><td class="code"><div class="codehilite"><pre><span class="k">from</span> <span class="nn">django.conf.global_settings</span> <span class="k">import</span> <span class="n">TEMPLATE_CONTEXT_PROCESSORS</span> <span class="n">TEMPLATE_CONTEXT_PROCESSORS</span> <span class="o">=</span> <span class="n">TEMPLATE_CONTEXT_PROCESSORS</span> <span class="o">+</span> <span class="p">(</span> <span class="s">&quot;mysite.blog.context_processors.latest_tweet&quot;</span><span class="p">,</span> <span class="p">)</span> <span class="n">TWITTER_USER</span> <span class="o">=</span> <span class="s">&quot;omh&quot;</span> <span class="n">TWITTER_TIMEOUT</span> <span class="o">=</span> <span class="mf">3600</span> </pre></div> </td></tr></table><p>Django has bunch of context processors already defined in its global settings. If we only specify our processor, we would remove the default ones. If you want to keep the default processors &#8212; chances are you probably do &#8212; you could either manually add them &#8212; that doesn&#8217;t feel very <span class="caps">DRY</span> to me, or just append them like we do&nbsp;above. </p> <p>Since we&#8217;re already editing <code>settings.py</code> we&#8217;ll go ahead and add two Twitter settings, <code>TWITTER_USER</code> to specify your Twitter login and <code>TWITTER_TIMEOUT</code> to specify how often we should ask Twitter for the latest&nbsp;tweet. </p> <p>We specified <code>mysite.blog.context_processors.latest_tweet</code> as the context processor location. Next up is then to create a <code>latest_tweet</code> method inside of <code>context_processors.py</code>. Here&#8217;s what <code>context_processors.py</code> looks&nbsp;like: </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16</pre></td><td class="code"><div class="codehilite"><pre><span class="k">from</span> <span class="nn">datetime</span> <span class="k">import</span> <span class="n">datetime</span> <span class="k">from</span> <span class="nn">django.conf</span> <span class="k">import</span> <span class="n">settings</span> <span class="k">from</span> <span class="nn">django.core.cache</span> <span class="k">import</span> <span class="n">cache</span> <span class="k">import</span> <span class="nn">twitter</span> <span class="k">def</span> <span class="nf">latest_tweet</span><span class="p">(</span> <span class="n">request</span> <span class="p">):</span> <span class="n">tweet</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span> <span class="s">&#39;tweet&#39;</span> <span class="p">)</span> <span class="k">if</span> <span class="n">tweet</span><span class="p">:</span> <span class="k">return</span> <span class="p">{</span><span class="s">&quot;tweet&quot;</span><span class="p">:</span> <span class="n">tweet</span><span class="p">}</span> <span class="n">tweet</span> <span class="o">=</span> <span class="n">twitter</span><span class="o">.</span><span class="n">Api</span><span class="p">()</span><span class="o">.</span><span class="n">GetUserTimeline</span><span class="p">(</span> <span class="n">settings</span><span class="o">.</span><span class="n">TWITTER_USER</span> <span class="p">)[</span><span class="mf">0</span><span class="p">]</span> <span class="n">tweet</span><span class="o">.</span><span class="n">date</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">strptime</span><span class="p">(</span> <span class="n">tweet</span><span class="o">.</span><span class="n">created_at</span><span class="p">,</span> <span class="s">&quot;%a %b </span><span class="si">%d</span><span class="s"> %H:%M:%S +0000 %Y&quot;</span> <span class="p">)</span> <span class="n">cache</span><span class="o">.</span><span class="n">set</span><span class="p">(</span> <span class="s">&#39;tweet&#39;</span><span class="p">,</span> <span class="n">tweet</span><span class="p">,</span> <span class="n">settings</span><span class="o">.</span><span class="n">TWITTER_TIMEOUT</span> <span class="p">)</span> <span class="k">return</span> <span class="p">{</span><span class="s">&quot;tweet&quot;</span><span class="p">:</span> <span class="n">tweet</span><span class="p">}</span> </pre></div> </td></tr></table><p>Django passes a <code>HttpRequest</code> object as the first and only parameter to all context processors. This is why we need to take <code>request</code> as a parameter, however in our case we don&#8217;t use&nbsp;it. </p> <p>The above code is checking the cache for an existing <code>tweet</code> object. If the cache is empty it is uses the Twitter <span class="caps">API</span> to fetch the latest tweet, converts the date into a datetime object and stores the tweet in the&nbsp;cache. </p> <p>With the context processor in place you can access the <code>tweet</code> object from any template loaded by any of the <a href="http://www.djangoproject.com/documentation/generic_views/">generic views</a>. In your own views you need to use <code>RequestContext</code> instead of <code>Context</code>, as your context class when rendering&nbsp;templates. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3 4 5</pre></td><td class="code"><div class="codehilite"><pre><span class="x">&lt;div class=&quot;twitter&quot;&gt;</span> <span class="x"> </span><span class="cp">{%</span> <span class="k">if</span> <span class="nv">tweet</span> <span class="cp">%}</span><span class="x"></span> <span class="x"> &lt;p&gt;Latest tweet from </span><span class="cp">{{</span> <span class="nv">tweet.date</span><span class="o">|</span><span class="nf">naturalday</span> <span class="cp">}}</span><span class="x">: </span><span class="cp">{{</span> <span class="nv">tweet.text</span> <span class="cp">}}</span><span class="x">&lt;p&gt;</span> <span class="x"> </span><span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x"></span> <span class="x">&lt;/div&gt;</span> </pre></div> </td></tr></table>Ole Morten HalvorsenMon, 04 Aug 2008 21:13:41 +0100http://www.omh.cc/blog/2008/aug/4/adding-your-twitter-status-django-site/Copy and Paste on the iPhonehttp://www.omh.cc/blog/2008/jul/21/copy-and-paste-iphone/ <p>One of the biggest complaints about the iPhone is the lack of copy-and-paste. After spending a few days with my new iPhone &#8212; yes, I bought one, even though <a href="/2008/jun/28/netcom-you-suck/">NetCom sucks</a> &#8212; it was obvious to me too, that copy-and-paste is desperately needed. I think <a href="http://daringfireball.net/2008/07/copy_and_paste">John Gruber was right on the money in his post</a> about the copy-and-paste&nbsp;issue: </p> <blockquote><p>Writing the code to implement a system-wide clipboard isn’t the hard part — as I wrote in August, the hard part is coming up with the right <span class="caps">UI</span> design for it. Whatever the <span class="caps">UI</span> for copy-and-paste for the iPhone <span class="caps">OS</span> eventually is, it’s very likely to remain as the <span class="caps">UI</span> for copy-and-paste on the iPhone for decades to&nbsp;come. </p> </blockquote><p>This got me thinking. How would you implement copy-and-paste on the iPhone? Say you want to copy a paragraph from a website in Safari and paste in a new email post, how do you see yourself doing&nbsp;that? </p> <p>There&#8217;s only a few hardware buttons on the phone, all of which have dedicated functions, so those are out of the question. Many of the common and easy-to-perform finger gestures &#8212; like tapping, double tapping, etc &#8212; are already&nbsp;taken. </p> <p>I&#8217;ve never owned a smart phone before. None of my previous phones had copy-and-paste functionality. That being said, none of my previous phones needed copy-and-paste. Please post a comment if you can explain me how copy-and-paste works on, Windows Mobile, or any other smart phone&nbsp;platform. </p> <p>Here&#8217;s how I&#8217;d think copy-and-paste could work, from a user point of&nbsp;view. </p> <ul> <li> <em>Copy</em>: double tap and hold to start selecting text, remove finger to&nbsp;copy. </li> <li> <em>Paste</em>: Triple tap on any editable text&nbsp;field. </li> </ul> <p><img src="/media/images/IMG_0004_post.PNG" alt="iPhoneMoveCursor"/> </p> <p>(Since my Photoshop skills are non existent, imagine the text to the right of the cursor is selected with a nice blue&nbsp;background.) </p> <p>The iPhone already uses tap-and-hold in text fields &#8212; which brings up the magnifying glass &#8212; to move the cursor around. The copy functionality should do the same, expect select text instead of moving the cursor. When the user is done selecting and removes his/her finger, the selected text should blink once to indicate that it has been copied. A simple triple tap in any text input field would then perform a paste of the copied&nbsp;text. </p> <p>I think the reason Apple hasn&#8217;t included copy-and-paste is simple: they haven&#8217;t gotten around to it yet. Between the <span class="caps">SDK</span>, 3G, <span class="caps">GPS</span>, the App Store, MobileMe and the new development tools, Apple probably just wanted to focused on what would be critical to iPhone&#8217;s success. The iPhone is pretty much sold out world wide, proving that copy-and-paste wasn&#8217;t <em>that</em> important for most&nbsp;people. </p> <p>Even though every review of a new smart phone will have &#8220;iPhone killer?&#8221; in the headline, Apple is the one playing catch up when it comes to features. Nokia, Sony Ericsson, <span class="caps">RIM</span>, Palm have all had their products on the marked for a long time. The iPhone is only a year old, so I think it&#8217;s natural that some &#8220;obvious&#8221; features like <span class="caps">MMS</span>, copy-and-paste, voice dialing and video recording are&nbsp;missing. </p>Ole Morten HalvorsenMon, 21 Jul 2008 17:16:57 +0100http://www.omh.cc/blog/2008/jul/21/copy-and-paste-iphone/Netcom, you suck!http://www.omh.cc/blog/2008/jun/28/netcom-you-suck/ <p>Updated: Netcom has launched iConnect, a plan with unlimited&nbsp;bandwidth. </p> <p>If you think the Canadians got it bad with crappy <a href="http://www.rogers.com/web/content/wireless-products/iphone_voice_data_packages">Roger plans</a>, think again. On Friday, <a href="http://www.netcom.no">Netcom</a> announced their iPhone plans for Norway. Oh boy&#8230; they suck. Netcom offers three plans for the iPhone: Small, Medium, Large with the price ranging from $ 80 to $ 218. Before going berserk over the prices keep in mind Norway has 25% <span class="caps">VAT</span> and cell phone companies are only allowed to lock you in for 12&nbsp;months. </p> <p>The price level isn&#8217;t the biggest problem, it&#8217;s what you get for your money. <span class="caps">100MB</span> for $80, what the hell are you thinking Netcom? Even <a href="http://www.rogers.com/web/content/wireless-products/iphone_voice_data_packages">Roger&#8217;s</a> plans start at 400 <span class="caps">MB</span>. In the <span class="caps">UK</span>, <a href="http://www.o2.co.uk/iphone/paymonthly">O2</a> is offering all their plans with unlimited&nbsp;data. </p> <p><table width="100%"> <tr> <th>&nbsp;</th> <th>Small</th> <th>Medium</th> <th>Large</th> <th>iConnect</th> </tr> <tr> <td>Minutes</td> <td>100</td> <td>250</td> <td>1000</td> <td>None</td> </tr> <tr> <td><span class="caps">SMS</span></td> <td>100</td> <td>250</td> <td>1000</td> <td>None</td> </tr> <tr> <td>Data</td> <td>100</td> <td>250</td> <td>1000</td> <td>Unlimited</td> </tr> <tr> <td>Price</td> <td>$80 <span class="caps">USD</span></td> <td>$138 <span class="caps">USD</span></td> <td>$218 <span class="caps">USD</span></td> <td>$98 <span class="caps">USD</span></td> </tr> </table> </p> <p>In an interview with <a href="http://digi.no/php/art.php?id=777918">digi.no</a>, Director of Communications, Øyvind Vederhus&nbsp;said: </p> <blockquote><p>People forget what 1000 megabyte and 1000 minutes&nbsp;costs </p> </blockquote><p>We didn&#8217;t forget Øyvind. Netcom was already offering <a href="https://netcom.no/priser/datapriser.html">unlimited data</a> for $120. This plan is &#8212; surprise, surprise &#8212; not available for the iPhone. I guess iPhone megabytes are more expensive than normal megabytes. But wait, there was more bullshit coming out of&nbsp;Øyvind: </p> <blockquote><p>When digi.no asked how they came up with the prices and plans, Vederhus said they&#8217;ve done solid&nbsp;research. </p> <p>The plans were made in collaboration with iPhone [sic]. We sought their advice and got numbers showing how iPhone users is different from other&nbsp;users. </p> </blockquote><p>Really? You managed &#8212; <em>with the help of Apple</em> &#8212; to come up with plans starting at 100 <span class="caps">MB</span> for a phone that comes with E-mail, a YouTube application, <span class="caps">GPS</span> which uses Google Maps, online store for buying music and iPhone applications and a full blown web browser? <em>Nice work</em>,&nbsp;Netcom! </p>Ole Morten HalvorsenSat, 28 Jun 2008 15:22:58 +0100http://www.omh.cc/blog/2008/jun/28/netcom-you-suck/One Virtual Host, Many eZ Publish Installationshttp://www.omh.cc/blog/2008/jun/24/one-virtual-host-many-ez-publish-installations/ <p>As someone who spends most of the day testing and developing eZ Publish, I always have multiple instances of eZ Publish checked out. Different versions, customer installations, etc. Previously I&#8217;ve created a new virtual host in Apache for each installation &#8212; very painful&nbsp;indeed. </p> <p>I went searching for a better solution &#8212; one that wouldn&#8217;t need reconfiguring of Apache. I could have used rewrite rules to map part of the <span class="caps">URL</span> to a directory, however rewrite rules will get messy fast. I want my configuration to be as clean and simple as possible. Some researching later and I stumbled upon the <code>VirtualDocumentRoot</code> directive. From the <a href="http://httpd.apache.org/docs/2.2/mod/mod_vhost_alias.html#virtualdocumentroot">VirtualDocumentRoot</a>&nbsp;documentation: </p> <blockquote><p>The VirtualDocumentRoot directive allows you to determine where Apache will find your documents based on the value of the server name. The result of expanding interpolated-directory is used as the root of the document tree in a similar manner to the DocumentRoot directive&#8217;s&nbsp;argument. </p> </blockquote><p>Sounds perfect. Here&#8217;s my new and improved virtual host configuration I came up with using <code>VirtualDocumentRoot</code>: </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27</pre></td><td class="code"><div class="codehilite"><pre><span class="nt">&lt;VirtualHost</span> <span class="s">*:80</span><span class="nt">&gt;</span> <span class="nt">&lt;Directory</span> <span class="s">/www</span><span class="nt">&gt;</span> <span class="nb">Options</span> FollowSymLinks <span class="nb">AllowOverride</span> <span class="k">None</span> <span class="nb">Order</span> allow,deny <span class="nb">Allow</span> from <span class="k">all</span> <span class="nt">&lt;/Directory&gt;</span> <span class="nb">VirtualDocumentRoot</span> <span class="sx">/www/</span>%1 <span class="nb">RewriteEngine</span> <span class="k">On</span> <span class="nb">RewriteRule</span> content/treemenu/?$ <span class="sx">/index_treemenu.php</span> [L] <span class="nb">Rewriterule</span> ^/var/storage/.* - [L] <span class="nb">Rewriterule</span> ^/var/[^/]+/storage/.* - [L] <span class="nb">RewriteRule</span> ^/var/cache/texttoimage/.* - [L] <span class="nb">RewriteRule</span> ^/var/[^/]+/cache/texttoimage/.* - [L] <span class="nb">Rewriterule</span> ^/design/[^/]+/(stylesheets|images|javascript)/.* - [L] <span class="nb">Rewriterule</span> ^/share/icons/.* - [L] <span class="nb">Rewriterule</span> ^/extension/[^/]+/design/[^/]+/(stylesheets|images|javascripts?)/.* - [L] <span class="nb">Rewriterule</span> ^/packages/styles/.+/(stylesheets|images|javascript)/[^/]+/.* - [L] <span class="nb">RewriteRule</span> ^/packages/styles/.+/thumbnail/.* - [L] <span class="nb">RewriteRule</span> ^/favicon\.ico - [L] <span class="nb">RewriteRule</span> ^/robots\.txt - [L] <span class="nb">RewriteRule</span> .* <span class="sx">/index.php</span> [<span class="caps">PT</span>] <span class="nt">&lt;/VirtualHost&gt;</span> </pre></div> </td></tr></table> <h2>How it&nbsp;works</h2> <div class="codehilite"><pre> </pre></div> <p>This is the same virtual host setup as explained in the <a href="http://ez.no/doc/ez_publish/technical_manual/4_0/installation/virtual_host_setup">eZ Publish installation documentation</a>, except for 2 new&nbsp;directives. </p> <ol> <li><p><code>VirtualDocumentRoot /www/%1</code> </p> <p>Instead of <code>DocumentRoot</code>, we use <code>VirtualDocumentRoot</code> to dynamically set the document root depending on the <em>Host</em> <span class="caps">HTTP</span> header. The <code>%1</code> specifies that we only want the first part of the host name. For more information about <code>VirtualDocumentRoot</code>, check out the <a href="http://httpd.apache.org/docs/2.2/mod/mod_vhost_alias.html#virtualdocumentroot">mod_vhost_alias documentation</a>. </p> </li> <li><p><code>RewriteRule .* /index.php [PT]</code> </p> <p><code>[PT]</code> (pass through to next handler) was added to make sure the document root is set using <code>VirtualDocumentRoot</code>. If omitted the default <code>DocumentRoot</code> setting will be used, resulting in paths looking like <em>/www/index.php</em>, instead of <em>/www/trunk/index.php</em>. </p> </li> </ol> <h3>Examples</h3> <p>To illustrate how the mapping between the host header and directories on your file system, here are some&nbsp;examples: </p> <p><table width="50%"> <tr> <th>Host</th> <th>Document root</th> </tr> <tr> <td>http://trunk</td> <td>/www/trunk</td> </tr> <tr> <td>http://trunk.oh.ez.no</td> <td>/www/trunk</td> </tr> <tr> <td>http://stable.oh.ez.no</td> <td>/www/stable</td> </tr> </p> </table>Ole Morten HalvorsenTue, 24 Jun 2008 12:59:41 +0100http://www.omh.cc/blog/2008/jun/24/one-virtual-host-many-ez-publish-installations/How Do You Fight Spam?http://www.omh.cc/blog/2008/may/24/how-do-you-fight-spam/ <p>In the last few weeks I&#8217;ve been getting an increasingly amount of comment spam. This was expected though &#8212; I&#8217;ve done nothing to actually keep spam out. For a tiny blog like mine I feel the two traditional ways of blocking spam; user registration and CAPTCHAs, are not very user&nbsp;friendly. </p> <p>As a first line of defense I decided to disable commenting for posts older than 2 weeks. As always with Django, it turned out to be much simpler than I&nbsp;thought. </p> <h2>Step 1: Create a template&nbsp;filter</h2> <p>The template filter is very straight forward. It takes a date and returns True/False if the date is within 14 days. I hardcoded the limit to 14 days in my example, however it should probably be an setting in settings.py. If you do not know or remember how to create a template filter, Django has some <a href="http://www.djangoproject.com/documentation/templates_python/#writing-custom-template-filters" title="Django - Writing custom template filters">excellent documentation</a> on the&nbsp;subject. </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11 12 13</pre></td><td class="code"><div class="codehilite"><pre><span class="k">from</span> <span class="nn">django</span> <span class="k">import</span> <span class="n">template</span> <span class="k">import</span> <span class="nn">datetime</span> <span class="n">register</span> <span class="o">=</span> <span class="n">template</span><span class="o">.</span><span class="n">Library</span><span class="p">()</span> <span class="nd">@register</span><span class="o">.</span><span class="n">filter</span> <span class="k">def</span> <span class="nf">showcomments</span><span class="p">(</span> <span class="n">date</span> <span class="p">):</span> <span class="n">date_adjusted</span> <span class="o">=</span> <span class="n">date</span> <span class="o">+</span> <span class="n">datetime</span><span class="o">.</span><span class="n">timedelta</span><span class="p">(</span> <span class="n">days</span><span class="o">=</span><span class="mf">14</span> <span class="p">)</span> <span class="k">if</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">&lt;=</span> <span class="n">date_adjusted</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> <span class="k">return</span> <span class="bp">False</span> </pre></div> </td></tr></table> <h2>Step 2: Modify the blog&nbsp;template</h2> <p>In your blog post template add an if statement to only show the comment form if our <em>showcomments</em> filter returns&nbsp;true. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3 4 5</pre></td><td class="code"><div class="codehilite"><pre><span class="cp">{%</span> <span class="k">if</span> <span class="nv">object.published</span><span class="o">|</span><span class="nf">showcomments</span> <span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="c">{# show comments form #}</span><span class="x"></span> <span class="cp">{%</span> <span class="k">else</span> <span class="cp">%}</span><span class="x"></span> <span class="x"> </span><span class="c">{# show comments has been disabled message #}</span><span class="x"></span> <span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span><span class="x"></span> </pre></div> </td></tr></table> <h2>Step 3: Stopping spam at the view level <em>(added)</em></h2> <p>As some mentioned in the comments &#8212; chances are the spammers will figure out how to post comments even if you hide the form. To block comments at the a lower level &#8212; the view level &#8212; here&#8217;s a quick and crude way of wrapping around the free comments <em>post_free_comment()</em>&nbsp;method. </p> <p>Override <em>/commments/postfree/</em> in your&nbsp;urls.py: </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre><span class="p">(</span> <span class="s">r&#39;^comments/postfree/$&#39;</span><span class="p">,</span> <span class="s">&#39;mysite.blog.views.post_comment&#39;</span> <span class="p">),</span> </pre></div> </td></tr></table><p>Create the <em>post_comment</em> in your&nbsp;views.py: </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18</pre></td><td class="code"><div class="codehilite"><pre><span class="k">import</span> <span class="nn">datetime</span> <span class="k">from</span> <span class="nn">django.shortcuts</span> <span class="k">import</span> <span class="n">get_object_or_404</span> <span class="k">from</span> <span class="nn">django.http</span> <span class="k">import</span> <span class="n">HttpResponseForbidden</span> <span class="k">from</span> <span class="nn">django.contrib.comments.views.comments</span> <span class="k">import</span> <span class="n">post_free_comment</span> <span class="k">from</span> <span class="nn">mysite.blog.models</span> <span class="k">import</span> <span class="n">Post</span> <span class="k">def</span> <span class="nf">post_comment</span><span class="p">(</span> <span class="n">request</span> <span class="p">):</span> <span class="n">comment_id</span><span class="p">,</span> <span class="n">post_id</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n"><span class="caps">POST</span></span><span class="p">[</span><span class="s">&#39;target&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span> <span class="s">&#39;:&#39;</span> <span class="p">)</span> <span class="n">post</span> <span class="o">=</span> <span class="n">get_object_or_404</span><span class="p">(</span> <span class="n">Post</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="n">post_id</span> <span class="p">)</span> <span class="n">date_adjusted</span> <span class="o">=</span> <span class="n">post</span><span class="o">.</span><span class="n">published</span> <span class="o">+</span> <span class="n">datetime</span><span class="o">.</span><span class="n">timedelta</span><span class="p">(</span> <span class="n">days</span><span class="o">=</span><span class="mf">14</span> <span class="p">)</span> <span class="k">if</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">&lt;=</span> <span class="n">date_adjusted</span><span class="p">:</span> <span class="k">return</span> <span class="n">post_free_comment</span><span class="p">(</span> <span class="n">request</span> <span class="p">)</span> <span class="k">return</span> <span class="n">HttpResponseForbidden</span><span class="p">(</span> <span class="s">&quot;Error 403: Spam forbidden&quot;</span> <span class="p">)</span> </pre></div> </td></tr></table><p>If you have small tips on how to fight spam &#8212; that&#8217;s user friendly &#8212; please share it in the&nbsp;comments. </p>Ole Morten HalvorsenSat, 24 May 2008 16:49:03 +0100http://www.omh.cc/blog/2008/may/24/how-do-you-fight-spam/View Source in TextMatehttp://www.omh.cc/blog/2008/mar/16/view-source-textmate/ <p>For reasons unknown to me Safari does not let you choose what application you would like to view the <span class="caps">HTML</span> source in. As a web developer I find this frustrating. There are some plugins out there&#8212;like <a href="http://hetima.com/safari/stand-e.html" title="SafarStand plugin for Safari">SafariStand</a>&#8212;that adds syntax highlighting. However it doesn&#8217;t let me view the source in <a href="http://www.macromates.com" title="TextMate">TextMate</a>, my editor of&nbsp;choice. </p> <p><img src="/media/images/ViewSourceinTextMate_post.png" alt="View Source in TextMateIMG"/> </p> <p>The best solution I&#8217;ve found so far is an applescript that saves the source to a file and opens that file in <a href="http://www.macromates.com" title="TextMate">TextMate</a>. I don&#8217;t remember where I found this script, but I did not write it, nor trying to take any credit for&nbsp;it. </p> <h2>View <span class="caps">HTTP</span> Headers in&nbsp;Safari</h2> <p>Since Safari doesn&#8217;t let you view <span class="caps">HTTP</span> headers either&#8212;I modified the applescript to grab both headers and source and open it in&nbsp;TextMate. </p> <p><img src="/media/images/ViewSourceAndHeadersInTextmate_post.png" alt="View Source in TextMate with HeadersIMG"/> </p> <p>For quick access put the applescript somewhere you can launch it with <a href="http://www.blacktree.com/" title="Quicksilver">Quicksilver</a>, <a href="http://www.obdev.at/products/launchbar/index.html" title="Launchbar">Launchbar</a>, or&nbsp;similar&#8230; </p> <h2>Download</h2> <p><a href="/media/files/View_Source_in_TextMate.scpt">View Source In TextMate</a> (<span class="caps">16KB</span>) &#8212; 16 Mar&nbsp;2008 </p> <p><a href="/media/files/View_Source_With_Headers_in_TextMate.scpt">View Source in TextMate with Headers</a> (<span class="caps">12KB</span>) &#8212; 16 Mar&nbsp;2008 </p> <p><strong>Update:</strong> With the release of Safari 3.1 you can now use the Web Inspector to see <span class="caps">HTTP</span> headers.&nbsp;Cool. </p>Ole Morten HalvorsenSun, 16 Mar 2008 11:31:13 +0100http://www.omh.cc/blog/2008/mar/16/view-source-textmate/Clipper, The Tiny Mac OS X Clipboard History Applicationhttp://www.omh.cc/blog/2008/mar/15/clipper-mac-os-x-clipboard-history-manager/ <h3><a href="/clyppan/">Clipper has been replaced by&nbsp;Clyppan</a></h3> <p>Back in 2003 when I bought my first Mac&#8212;a shiny new iBook&#8212;I got an itch. Before switching I had become accustomed to a brilliant little <a href="http://www.kde.org"><span class="caps">KDE</span></a> app called Klipper. All Klipper did was to keep a history of stuff you&#8217;d copied. Nothing more, nothing&nbsp;less. </p> <p>I missed Klipper&#8230;bad. I looked around for similar application for the mac, but didn&#8217;t find any that felt as simple as Klipper. I just wanted a simple that would stay in the background and didn&#8217;t consume any&nbsp;resources. </p> <p>One day the the itch got too strong. The result of my scratching is Clipper&#8212;the simplest clipboard history manager I could&nbsp;write. </p> <p><img src="/media/images/Clipper_post.png" alt="ClipperIMG"/> </p> <p>As you can see&#8212;all it does is sit on your menu bar and saves stuff you copy to the&nbsp;clipboard. </p> <p><strong>Update:</strong> Unsurprisingly, I&#8217;m not the only one with a bad imagination when it comes to naming things. David pointed out in the comments that a utility called Clipper already exists. If you have a suggestion for a more imaginative name feel free to post a&nbsp;comment. </p> <h2>Download</h2> <p><a href="/clyppan/">Clipper has been replaced by Clyppan, essentially Clipper version&nbsp;2.0</a> </p>Ole Morten HalvorsenSat, 15 Mar 2008 19:00:52 +0100http://www.omh.cc/blog/2008/mar/15/clipper-mac-os-x-clipboard-history-manager/Separating Development and Production Settings in Djangohttp://www.omh.cc/blog/2008/mar/10/separating-development-production-settings-django/ <p><a href="http://www.djangoproject.com" title="Django project homepage">Django</a> does not have a built-in way of having development only settings. Luckily for us, <a href="http://www.djangoproject.com" title="Django project homepage">Django</a> settings is nothing but a simple python module. This allows us to implement our own way of dealing with settings right in the <em>settings.py</em>&nbsp;file. </p> <p>On <a href="http://blog.michaeltrier.com/2007/12/31/this-week-in-django-4-2007-12-30" title="This week in Django episode 4 show notes">This Week in Django episode 4</a> (great podcast!), <a href="http://blog.michaeltrier.com" title="Michael Trier Blog">Michael Trier</a> featured a <em>Tip of the Week</em> on how to implement development only settings. Since I like to have everything checked in to my <a href="http://www.selenic.com/mercurial/wiki/" title="Mercurial">Mercurial</a> repository, I extended the code to allow local settings to exists on my production server without being&nbsp;loaded. </p> <p>The original&nbsp;version. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3 4 5</pre></td><td class="code"><div class="codehilite"><pre><span class="p">[</span><span class="o">..</span><span class="p">]</span> <span class="k">try</span><span class="p">:</span> <span class="k">from</span> <span class="nn">local_settings</span> <span class="k">import</span> <span class="o">*</span> <span class="k">except</span> <span class="ne">ImportError</span><span class="p">,</span> <span class="n">exp</span><span class="p">:</span> <span class="k">pass</span> </pre></div> </td></tr></table><p>My&nbsp;version. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3 4 5 6 7</pre></td><td class="code"><div class="codehilite"><pre><span class="p">[</span><span class="o">..</span><span class="p">]</span> <span class="k">import</span> <span class="nn">socket</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">socket</span><span class="o">.</span><span class="n">gethostname</span><span class="p">()</span> <span class="o">==</span> <span class="s">&quot;omh.cc&quot;</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="k">from</span> <span class="nn">local_settings</span> <span class="k">import</span> <span class="o">*</span> <span class="k">except</span> <span class="ne">ImportError</span><span class="p">,</span> <span class="n">exp</span><span class="p">:</span> <span class="k">pass</span> </pre></div> </td></tr></table><p>It&#8217;s very simple. All it does is to check the host name and only import <em>local_settings.py</em> if host name is not &#8220;omh.cc&#8221;, which is the name of my&nbsp;server. </p>Ole Morten HalvorsenMon, 10 Mar 2008 16:34:24 +0100http://www.omh.cc/blog/2008/mar/10/separating-development-production-settings-django/Simplify Django&#39;s Free Comments Redirectionhttp://www.omh.cc/blog/2008/mar/9/free-comments-redirection/ <p><a href="http://www.djangoproject.com" title="Django project homepage">Django</a>, with its <em>battery included</em> philosophy has several very nice features included. One of these cool features is the comments contrib application. In a few minutes, I was able to add commenting with <a href="http://daringfireball.net/projects/markdown/" title="Markdown Syntax Documentation">Markdown</a> highlighting to my blog. Not bad for a <a href="http://www.djangoproject.com" title="Django project homepage">Django</a>&nbsp;beginner. </p> <p>I opted to remove the annoying &#8220;Thanks for commenting&#8221; page and redirect directly back to the blog post. The <a href="http://code.djangoproject.com/wiki/UsingFreeComment#Wrappingpost_free_commenttochangetheredirecturl" title="Django Free Comments Wiki Page">Free Comments Wiki page</a> explained how to do just that. However, it felt like way too much hassle, too much tinkering for a simple&nbsp;redirect. </p> <p>I live, uhm&#8230;, code by the motto: <em>Less code is better code</em>. Here&#8217;s my simplified approach. Add a line to your <em>urls.py</em> file overriding the default /comments/posted/&nbsp;view. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3 4</pre></td><td class="code"><div class="codehilite"><pre><span class="p">[</span><span class="o">...</span><span class="p">]</span> <span class="p">(</span> <span class="s">r&#39;^comments/posted/$&#39;</span><span class="p">,</span> <span class="s">&#39;blog.views.comment_posted&#39;</span> <span class="p">),</span> <span class="p">(</span> <span class="s">r&#39;^comments/&#39;</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span> <span class="s">&#39;django.contrib.comments.urls.comments&#39;</span> <span class="p">)</span> <span class="p">),</span> <span class="p">[</span><span class="o">...</span><span class="p">]</span> </pre></div> </td></tr></table><p>Make sure to add the <em>urls.py</em> line before the inclusion of comments. Now, create the<br /> <code>comment_posted()</code> method in your <em>views.py</em>&nbsp;file. </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11 12 13</pre></td><td class="code"><div class="codehilite"><pre><span class="k">from</span> <span class="nn">django.http</span> <span class="k">import</span> <span class="n">HttpResponseRedirect</span> <span class="k">from</span> <span class="nn">mysite.blog.models</span> <span class="k">import</span> <span class="n">Post</span> <span class="k">def</span> <span class="nf">comment_posted</span><span class="p">(</span> <span class="n">request</span> <span class="p">):</span> <span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n"><span class="caps">GET</span></span><span class="p">[</span><span class="s">&#39;c&#39;</span><span class="p">]:</span> <span class="n">comment_id</span><span class="p">,</span> <span class="n">post_id</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n"><span class="caps">GET</span></span><span class="p">[</span><span class="s">&#39;c&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span> <span class="s">&#39;:&#39;</span> <span class="p">)</span> <span class="n">post</span> <span class="o">=</span> <span class="n">Post</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span> <span class="n">pk</span><span class="o">=</span><span class="n">post_id</span> <span class="p">)</span> <span class="k">if</span> <span class="n">post</span><span class="p">:</span> <span class="k">return</span> <span class="n">HttpResponseRedirect</span><span class="p">(</span> <span class="n">post</span><span class="o">.</span><span class="n">get_absolute_url</span><span class="p">()</span> <span class="p">)</span> <span class="k">return</span> <span class="n">HttpResponseRedirect</span><span class="p">(</span> <span class="s">&quot;/&quot;</span> <span class="p">)</span> </pre></div> </td></tr></table><p>That&#8217;s it. 10 lines of code and only 2 files to modify instead of 5 files and 15 lines of code. Nothing like the smell of freshly deleted&nbsp;code. </p>Ole Morten HalvorsenSun, 09 Mar 2008 21:15:50 +0100http://www.omh.cc/blog/2008/mar/9/free-comments-redirection/Fixing Apache Segmentation Faults Caused by PHPhttp://www.omh.cc/blog/2008/mar/6/fixing-apache-segmentation-faults-caused-php/ <p>Ever seen this in your Apache error&nbsp;log? </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre><span class="o">[</span>notice<span class="o">]</span> child pid 10024 <span class="nb">exit </span>signal Segmentation fault <span class="o">(</span>11<span class="o">)</span> </pre></div> </td></tr></table><p>If you have, read on. At my <a href="http://ez.no" title="eZ Systems AS - http://ez.no">day job</a> I deal with both Apache and <span class="caps">PHP</span> a lot. If you have ever tried to figure out why your <span class="caps">PHP</span> code seems to cause Apache segmentation faults, you probably experienced the same as I did. Lots of pain, frustration and headaches. However all is not lost. There is a way to figure out what <span class="caps">PHP</span> code is making Apache act crazy. I&#8217;ll try to explain how to debug Apache using <a href="http://sourceware.org/gdb/" title="GNU Debugger">gdb</a> to locate that nasty bug that is causing you to lose your precious beauty&nbsp;sleep. </p> <h2>Before we&nbsp;begin</h2> <p>Now, Apache does not dump core by default. We need to do some work before that happens. If you don&#8217;t like to get your hands dirty compiling stuff manually, you should leave now, this is not for&nbsp;you. </p> <p>Still here? Good! Before we start, make sure you&nbsp;have: </p> <ol> <li> Root access to your web&nbsp;server </li> <li> Apache source code and everything needed to (re-)compile&nbsp;Apache </li> <li> <span class="caps">PHP</span> source code (or just download the <a href="https://github.com/php/php-src/blob/master/.gdbinit">.gbdinit file</a>) </li> </ol> <h2>Make Apache dump&nbsp;core</h2> <p>The first step is to make Apache not change user when it starts up and forks, so we will make it run as root the whole time. To accomplish this we need to compile Apache with -DBIG_SECURITY_HOLE. For obvious reasons, this is not recommended for&nbsp;production. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3</pre></td><td class="code"><div class="codehilite"><pre>make clean <span class="nb">export </span><span class="nv">EXTRA_CFLAGS</span><span class="o">=</span><span class="s2">&quot;-DBIG_SECURITY_HOLE&quot;</span> ./configure &amp;&amp; make &amp;&amp; make install </pre></div> </td></tr></table><p>Now specify the &#8220;root&#8221; as the &#8220;User&#8221; in httpd.conf. While we&#8217;re editing httpd.conf we&#8217;ll add a setting to specify where Apache should put our core&nbsp;dump. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2 3</pre></td><td class="code"><div class="codehilite"><pre><span class="nb">User</span> nobody <span class="err">[...]</span> <span class="nb">CoreDumpDirectory</span> <span class="sx">/tmp/apache</span> </pre></div> </td></tr></table><p>Make sure /tmp/apache&nbsp;exists </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre>mkdir -p /tmp/apache </pre></div> </td></tr></table><p>On most systems the core file size is set to zero by default. We&#8217;ll go ahead and change it to&nbsp;unlimited. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre><span class="nb">ulimit</span> -c 0 </pre></div> </td></tr></table><p>You can check your current core dump file size limit by&nbsp;running </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre><span class="nb">ulimit</span> -a </pre></div> </td></tr></table><p>Restart&nbsp;Apache </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre>apachectl restart </pre></div> </td></tr></table><p>Next time Apache crashes with a Segmentation fault it should make us a core dump. If you did everything correctly you should see something like this in the apache&nbsp;error_log. </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2</pre></td><td class="code"><div class="codehilite"><pre><span class="o">[</span>notice<span class="o">]</span> child pid 16430 <span class="nb">exit </span>signal Segmentation fault <span class="o">(</span>11<span class="o">)</span>, possible coredump in /tmp/apache </pre></div> </td></tr></table><p><strong>Note</strong>: For reasons unclear to me my dump file ended up on the root (/) and not in the directory we specified with CoreDumpDirectory. If anyone knows why please drop me a&nbsp;comment. </p> <h2>Making sense of the core&nbsp;dump</h2> <p>At this point we could run gdb and get a backtrace, however that will only show us the function called inside php itself and not pinpoint where in our php code the problem is. To get a backtrace of our <span class="caps">PHP</span> code we need to use the &#8220;dump_bt&#8221; function inside the <a href="https://github.com/php/php-src/blob/master/.gdbinit">.gbdinit file</a>. Copy the <a href="https://github.com/php/php-src/blob/master/.gdbinit">.gbdinit file</a> to your home&nbsp;directory </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre>cp &lt;php_source&gt;/.gdbinit ~/ </pre></div> </td></tr></table><p>Start gdb with the path to httpd from the source as first parameter and the path to the core dump file as&nbsp;second </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1 2</pre></td><td class="code"><div class="codehilite"><pre><span class="nb">cd</span> ~ gdb &lt;apache_source&gt;/src/httpd /tmp/apache/&lt;core_file&gt; </pre></div> </td></tr></table><p>Run bt_dump from&nbsp;.gdbinit: </p> <table class="codehilitetable"><tr><td class="linenos"><pre>1</pre></td><td class="code"><div class="codehilite"><pre><span class="o">(</span>gdb<span class="o">)</span> dump_bt executor_globals.current_execute_data </pre></div> </td></tr></table><p>Instead of a internal <span class="caps">PHP</span> backtrace we should get a nice backtrace of our <span class="caps">PHP</span>&nbsp;code. </p> <table class="codehilitetable"><tr><td class="linenos"><pre> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15</pre></td><td class="code"><div class="codehilite"><pre><span class="o">[</span>...<span class="o">]</span> <span class="o">[</span>0xbf75f6fc<span class="o">]</span> query<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:767 <span class="o">[</span>0xbf76249c<span class="o">]</span> unlock<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:484 <span class="o">[</span>0xbf7627bc<span class="o">]</span> query<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:767 <span class="o">[</span>0xbf76555c<span class="o">]</span> unlock<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:484 <span class="o">[</span>0xbf76587c<span class="o">]</span> query<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:767 <span class="o">[</span>0xbf76861c<span class="o">]</span> unlock<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:484 <span class="o">[</span>0xbf76893c<span class="o">]</span> query<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:767 <span class="o">[</span>0xbf76b6dc<span class="o">]</span> unlock<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:484 <span class="o">[</span>0xbf76b9fc<span class="o">]</span> query<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:767 <span class="o">[</span>0xbf76e79c<span class="o">]</span> unlock<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:484 <span class="o">[</span>0xbf76eabc<span class="o">]</span> query<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:767 <span class="o">[</span>0xbf77185c<span class="o">]</span> unlock<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:484 <span class="o">[</span>0xbf771b7c<span class="o">]</span> query<span class="o">()</span> /home/site/www/lib/ezdb/classes/ezmysqldb.php:767 <span class="o">[</span>...<span class="o">]</span> </pre></div> </td></tr></table><p>In this example we can clearly see there is a recursive problem with query() and unlock() on line 767 and 484 in ezmysqldb.php. With the help of this backtrace in only took a quick look in ezmysqldb.php before I had a bug report written and posted: <a href="http://issues.ez.no/11038">http://issues.ez.no/11038</a>. </p> <p>Thanks to my good friend and co-worker <a href="http://derickrethans.nl/" title="Derick Rethans">Derick Rethans</a> for telling me about the &#8220;dump_bt&#8221;&nbsp;function. </p>Ole Morten HalvorsenThu, 06 Mar 2008 11:39:50 +0100http://www.omh.cc/blog/2008/mar/6/fixing-apache-segmentation-faults-caused-php/