<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Talk Unafraid &#187; rails</title>
	<atom:link href="http://www.talkunafraid.co.uk/tag/rails/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.talkunafraid.co.uk</link>
	<description>The (occasionally coherent) ramblings of a geek</description>
	<lastBuildDate>Sat, 07 Jan 2012 22:24:46 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Perceptual image and audio deduplication</title>
		<link>http://www.talkunafraid.co.uk/2012/01/perceptual-image-and-audio-deduplication/</link>
		<comments>http://www.talkunafraid.co.uk/2012/01/perceptual-image-and-audio-deduplication/#comments</comments>
		<pubDate>Sat, 07 Jan 2012 18:03:11 +0000</pubDate>
		<dc:creator>James Harrison</dc:creator>
				<category><![CDATA[Code Snippets and Examples]]></category>
		<category><![CDATA[Odds and Ends]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[dragonfly]]></category>
		<category><![CDATA[image processing]]></category>
		<category><![CDATA[opencv]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[software]]></category>

		<guid isPermaLink="false">http://www.talkunafraid.co.uk/?p=1375</guid>
		<description><![CDATA[Okay, two months without a post, won&#8217;t happen again&#8230; So, lately I&#8217;ve been moving out from the broadcast area and getting back into webapp development, but some of the things I&#8217;ve been working on touch quite heavily on deduplication, of images and music. This is quite an interesting topic, so let&#8217;s have a look at [...]]]></description>
			<content:encoded><![CDATA[<p>Okay, two months without a post, won&#8217;t happen again&#8230;</p>
<p>So, lately I&#8217;ve been moving out from the broadcast area and getting back into webapp development, but some of the things I&#8217;ve been working on touch quite heavily on deduplication, of images and music. This is quite an interesting topic, so let&#8217;s have a look at what we can do now.</p>
<p>Doing exact deduplication &#8211; stopping someone uploading the same file twice to a website &#8211; is pretty easy. You just hash the uploaded file (or de-encapsulate the data and hash that if you want to be a little more resilient) with something like SHA256 or SHA512. It&#8217;s fast, effective and easy. Lookups are as fast as your RDBMS is. This works with images, audio, videos, you name it.</p>
<p>What&#8217;s much harder is doing perceptual deduplication, or content deduplication. If I upload two files which are the same except one&#8217;s a PNG and one&#8217;s a JPEG, I want to be able to say &#8220;Hang on, you&#8217;ve already uploaded that!&#8221; when you upload the second file. Similarly, what if we resize an image? We want something resistant to that sort of attack.<span id="more-1375"></span></p>
<p>There are already perceptual hashing libraries like pHash out there which work by generating hashes which look very similar to your average md5sum, but are actually generated perceptually &#8211; the Hamming distance between two hashes is a measure of the similarity of the images represented by those two hashes. This is a great thing, but it&#8217;s pretty useless on large datasets without some specialist software to manage databases of hashes and querying based on Hamming distance. The pHash guys will of course <em>sell </em>you this solution, but there&#8217;s the problem &#8211; there&#8217;s quite a bit of money to be made with this sort of product, and useful open source implementations seem to be quite rare if not non-existent.</p>
<p>pHash is also a bit of an odd thing in that it works on multiple media types &#8211; audio, video and images. More specifically for audio are techniques for audio fingerprinting, like <a href="http://acoustid.org/">AcoustID</a> which aim to generate a specific fingerprint for each recording of a song. Distance between songs isn&#8217;t something we&#8217;re very interested in, because audio releases are typically few in number, and rarely are we looking for a song which sort of sounds like X &#8211; if it sounds different, it&#8217;s a different recording of the song, or has been mastered.</p>
<p>Images are very different because people often make small changes, or change the formatting or file type of an image, and these circulate and get thrown around all over the place. We want to be able to accept things that are legitimately changed, but flag up things that are almost perfectly identical to something we already have in a database.</p>
<p>So, what can we do? Well, we can use simpler techniques to reduce the number of images to compare down to a small number &#8211; say, 50 &#8211; and then compare the Hamming distance on full pHashes using that technique. But do we actually need this? Certainly for some databases, merely using those simpler techniques may well be sufficient.</p>
<p>I&#8217;ve been building an image board, lately, which I&#8217;m using Dragonfly for, an awesome on-the-fly image serving system. We&#8217;re storing everything in MongoDB, with GridFS for file storage. Here&#8217;s what we&#8217;re doing.</p>
<p>First, we need some analysis of the image contents. I&#8217;m using a roughly perceptually weighted average intensity metric. Here&#8217;s a tiny little Python script using Python-OpenCV&#8217;s SWIG bindings to perform that analysis rapidly.</p>
<p><script type="text/javascript" src="https://gist.github.com/1575463.js?file=simple_analyser.py"></script>Now we need to get Dragonfly in on this, and register a function we&#8217;ll use to handle GIFs. This goes in our Dragonfly initializer.<script type="text/javascript" src="https://gist.github.com/1575463.js?file=dragonfly.rb"></script></p>
<p>And finally we need to store all this and actually do the find-duplicates step. Note the aspect ratio stuff &#8211; with the ImageMagick analyser, Dragonfly will handle storing this for you, which makes life easier.</p>
<p><script src="https://gist.github.com/1575463.js?file=image.rb"></script></p>
<p>In our find_duplicates function we just look for images that have both a similar intensity and a similar aspect ratio. If both of these things are very close we&#8217;ve got ourselves a potential duplicate. We&#8217;re not doing <em>exact</em> matching on intensity/aspect ratio, more fuzzy matching, because compression changes and resizes can often affect both slightly.</p>
<p>Of course, the proof is in the pudding- this site only has a hundred images in it now, and we may need to adjust the distance variable, but so far it&#8217;s working very well and isn&#8217;t bringing up any false positives while correctly identifying duplicates. The way we&#8217;re doing this from a workflow perspective is important, too &#8211; when we do find duplicates, we ask the uploader of the image to check and make sure they&#8217;re not uploading anything we already have by presenting the duplicate images to them. They can then mark the duplicate (which deletes the image they just uploaded and leaves a redirect to the image their upload duplicates) or confirm it&#8217;s not a duplicate. The statistics from those actions will be interesting to see over time as a metric of success for this approach!</p>
<p>So, that&#8217;s how I&#8217;ve done image dedupe in a Rails app &#8211; what approach are you using?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.talkunafraid.co.uk/2012/01/perceptual-image-and-audio-deduplication/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IRIS &#8211; The Interchangeable Radio Ingest System</title>
		<link>http://www.talkunafraid.co.uk/2011/10/iris-the-interchangeable-radio-ingest-system/</link>
		<comments>http://www.talkunafraid.co.uk/2011/10/iris-the-interchangeable-radio-ingest-system/#comments</comments>
		<pubDate>Thu, 13 Oct 2011 10:48:14 +0000</pubDate>
		<dc:creator>James Harrison</dc:creator>
				<category><![CDATA[Audio]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Radio]]></category>
		<category><![CDATA[Rivendell]]></category>
		<category><![CDATA[Servers and Software]]></category>
		<category><![CDATA[EBU R128]]></category>
		<category><![CDATA[iris]]></category>
		<category><![CDATA[myriad]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[rivendell]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[software]]></category>

		<guid isPermaLink="false">http://www.talkunafraid.co.uk/?p=1367</guid>
		<description><![CDATA[Well, wow. After nearly forgetting to actually submit it and only writing the entry a few hours before the deadline, it turns out that the system I made while at Insanity Radio 1287AM has been nominated for the Best Technical Achievement award at the Student Radio Awards. So, I figured it would be worth actually [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://assets.talkunafraid.co.uk/2011/10/IRIS-ConceptualDrawing.png" rel="lightbox[1367]"><img class="alignright size-medium wp-image-1369" title="Conceptual Overview" src="http://assets.talkunafraid.co.uk/2011/10/IRIS-ConceptualDrawing-300x225.png" alt="" width="300" height="225" /></a>Well, wow. After nearly forgetting to actually submit it and only writing the entry a few hours before the deadline, it turns out that the system I made while at Insanity Radio 1287AM has been nominated for the Best Technical Achievement award at the Student Radio Awards. So, I figured it would be worth actually writing up a little bit about what it is and what it does. And why you can use it, too, if you&#8217;re involved with a student radio station.</p>
<p>IRIS was written to replace MACIS, a system I bodged up out of necessity. At Insanity, we had a computer failure weeks before we went on air at the start of the first term, and lost all the data- including the entire playout system. Lessons have been learned (I made sure we replaced that machine with a box that had RAID, for starters) since, but we had the unenviable challenge of repopulating a student radio playout system from scratch with little to no staff. Enter MACIS!</p>
<p><span id="more-1367"></span></p>
<p>MACIS was <em>dumb</em>. It talked to our playout system (PSquared&#8217;s Myriad 3.5) via the not-very-documented TCP/IP interface, had a web interface and drop box, and some background processing magic. It was implemented as a Ruby on Rails web application, since we already used Rails and Ruby for various tasks around the station (the website is all Rails, and Ruby was chosen for most scripting tasks because of its user friendliness to people not too familiar with programming. You passed it files, it converted them (the main purpose- Myriad doesn&#8217;t support AAC, MP4 or many other formats we were using for ingest), did a basic stab at normalization, and then imported the files. Myriad is slow at importing files- 1-2 minutes per file on average, so we let MACIS distribute workload across all four of our Myriad machines, speeding things up massively.</p>
<p>This was good and got us running, but then term started. We&#8217;re a student station and have specialist music shows, who upload their own content to the playout system every week, and new content for new music and chart shows came in regularly. MACIS was used for this, as it did the conversion automatically, saving our presenters loads of time. It also did batch imports quickly and efficiently, which sped things up. However, after a while, we stopped using it, and just provided a simple Ruby dropbox on a shared file server for conversion. MACIS was useful, but too buggy and inflexible. In addition, while it did a better job of getting metadata into Myriad than Myriad managed on its own, it had issues with some formats and material.</p>
<p>What was needed was a system that would let presenters upload content in any format, would sort out the metadata, handle conversion, and fire it off to the playout system for usage.</p>
<div id="attachment_1370" class="wp-caption alignright" style="width: 211px"><a href="http://assets.talkunafraid.co.uk/2011/10/IRIS-Screenshot-UploadInformation.png" rel="lightbox[1367]"><img class="size-medium wp-image-1370" title="Upload Information" src="http://assets.talkunafraid.co.uk/2011/10/IRIS-Screenshot-UploadInformation-201x300.png" alt="" width="201" height="300" /></a><p class="wp-caption-text">An example upload showing the log and graphs (huge image)</p></div>
<p>So, while presenters got back to using Myriad directly, I went back to the drawing board and scrapped MACIS. At this stage we were considering transitioning to the Rivendell open source playout system to replace Myriad, so I decided whatever I was going to make had to support both Myriad and Rivendell, and any other system you could imagine.</p>
<p>I also wanted to solve the loudness problem. Even doing normalization to every track imported, we had huge loudness level differences between some tracks, making life quite tricky for presenters and impacting our on-air sound. Especially given our lack of transmitter processing (we only have a limiter and preemphasis box on the AM system- no AGC or multiband comps), I wanted to do all I could to get everything as perceptually loud as everything else. Enter EBU Recommendation 128 for loudness measurement- with the help of some great libraries (libebur128 in particular), I implemented a simple version of the recommended processing system for loudness normalization, including LRA correction using a compressor. Thus, everything you run through IRIS comes out sounding about the same as everything else in terms of perceptual levels &#8211; as much as is possible without impacting the sound. <em>Wish You Were Here</em> is still going to have a quiet bit at the beginning- but IRIS will gently compress the track to make the difference less severe, and will then use R128&#8242;s standards to normalize to -23 LUFS.</p>
<p>Next up, user authentication. This was almost an afterthought, but added after talking to people about security. You register an account, and that account is either able to upload content only, upload and review (more on that in a sec), or administrate the system (ie modify users etc). This is done by user groups, which are pretty flexible, and easily adapted for your own usage via a simple permissions file. Uploads are linked to users- users can only see their uploads (unless they have permission to see more than that) and admins can see who owns a specific upload. You can also have emails sent on error conditions being met- so presenters know if a file they uploaded failed to make it to the playout system <em>before</em> they turn up to do their show and wonder where it is.</p>
<p>Metadata was one of the big issues I wanted to solve. Let&#8217;s say I have a track from a CD- I&#8217;ve ripped it and the ripper has embedded title/artist, maybe album metadata from Gracenote. For a playout system for radio this isn&#8217;t perfect- really we want to know the record label, copyright info, and so on. Enter MusicBrainz- a huge collaborative open database of music metadata. With some clever tools, IRIS matches up the track&#8217;s metadata to a MusicBrainz identifier and fills in the blanks. For most tracks, it can get everything- including ISRCs, year of release, and so on. This is great for music librarians and makes copyright reconciliation for PRS/PPL much simpler, since you&#8217;ve now got all this in a database.</p>
<p>Of course, if we know what the track is, we can do another useful step- especially so for student stations. Using the MusixMatch API service (commercial but free for nonprofits at present), we can get the lyrics to a track. This means we can do a quick once-over for words we don&#8217;t want on air (swearing). Of course, this assumes the track isn&#8217;t a radio edit. We do a quick check and skip the lyric pass for tracks that look like a radio edit, but if not, the track will be flagged for review.</p>
<p>Additionally, and this is intended specifically for situations where you have no control over the ingest quality that presenters are using to rip CDs or vinyl, tracks are flagged for review if they fail to meet quality restrictions. This can be specified as a function of sample rate and bit rate. This stops people trying to upload 96kbps MP3 at 32k sample rate. We don&#8217;t want that in the system. Not now, not ever. All of these parameters can be changed easily and simply by changing a single configuration file and restarting the app.</p>
<div id="attachment_1371" class="wp-caption aligncenter" style="width: 310px"><a href="http://assets.talkunafraid.co.uk/2011/10/IRIS-SystemArchitecture.png" rel="lightbox[1367]"><img class="size-medium wp-image-1371" title="System Architecture" src="http://assets.talkunafraid.co.uk/2011/10/IRIS-SystemArchitecture-300x225.png" alt="" width="300" height="225" /></a><p class="wp-caption-text">Overview of the system architecture</p></div>
<p>Once an upload is flagged for review an administrator or music librarian can review the track and either permanently reject it or approve it (in case of false positive lyric matches or odd uploads where quality restrictions aren&#8217;t able to be met).</p>
<p>Everything in IRIS is done through a web interface- uploads, management and monitoring. Every track has its own log of events, allowing administrators to debug and diagnose problems with ease, and giving clear and simple feedback to users. There are convenience functions such as automatic display of R128 loudness graphs pre/post normalization and compression, and display of all metadata available for tracks, plus lyrics if they were found.</p>
<p>The backend to IRIS is all Ruby and Rails, using a simple database server (PostgreSQL recommended) to store everything. Background processing is distributable over multiple computers with shared storage, allowing for CPU-intensive tasks to be spread across multiple machines. Given the R128 metering process includes a fourfold upsampling, this is particularly useful. You can run workers without running the whole web application, allowing you to install copies of the app onto lots of low-cost general purpose machines and have your own distributed ingest processing cluster on even a tight bugdet.</p>
<p>Of course, now you&#8217;ve got a track with lots of metadata and some normalized audio in WAV format (archived to FLAC pre-normalization, just in case you need the original audio). Now you need to get it into your playout system. Rivendell is supported, Myriad 3.6 now has dropbox support so you can just tell IRIS to export files to that dropbox in a Myriad-supported format, and you can also just do an export in any format you choose to an arbitrary folder. Export formats supported include FLAC, MP3, WAV, BWF (Broadcast WAVE Format) and AAC. Most of these flavours come with embedded metadata.</p>
<p>IRIS isn&#8217;t a perfect system, and it&#8217;s not an instant drop-in system; I don&#8217;t have the time to maintain it as such. What it is, though, is a flexible and powerful system that any average Linux user can install and have running in hours, and which can be used by any station looking to improve their import process.  The entire project is open source, and can be obtained <a href="https://github.com/JamesHarrison/iris">here</a>- there&#8217;s also a bugtracker and wiki with some documentation (unfinished) on it. If you&#8217;d like to contribute, feel free- as I&#8217;ve stepped out from student radio to work on student television, I&#8217;ve not got a huge amount of time to work on it at the moment.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.talkunafraid.co.uk/2011/10/iris-the-interchangeable-radio-ingest-system/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django &#8211; a flying visit</title>
		<link>http://www.talkunafraid.co.uk/2011/08/django-a-flying-visit/</link>
		<comments>http://www.talkunafraid.co.uk/2011/08/django-a-flying-visit/#comments</comments>
		<pubDate>Fri, 19 Aug 2011 23:35:28 +0000</pubDate>
		<dc:creator>James Harrison</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[comparison]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[review]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://www.talkunafraid.co.uk/?p=1345</guid>
		<description><![CDATA[So rather jokingly the other day I asked where the London Hackspace jobs board was, and 20 minutes later I&#8217;d decided to make it. I needed a little project as a break from the main commercial app I&#8217;m working on, which for the last week has involved staring at some rather involved postfix/mailer code. As [...]]]></description>
			<content:encoded><![CDATA[<p>So rather jokingly the other day I asked where the London Hackspace jobs board was, and 20 minutes later I&#8217;d decided to make it. I needed a little project as a break from the main commercial app I&#8217;m working on, which for the last week has involved staring at some rather involved postfix/mailer code. As I usually do, I grabbed Rails, but paused to update to Rails 3.1; I figured if I was going to start a new app, given Rails 3.1 is at rc6, I should probably go with that, knowing that a lot of the asset code would need rewriting shortly thereafter otherwise.</p>
<p>However, I quickly hit problems, probably just with interactions between my authentication library of choice (devise) and 3.1. Notably I got a complete appserver lockup on user registration, which sort of limited what I could do. Getting frustrated at how much had been fiddled with in 3.1 for seemingly no reason, I decided to take a look at how Pythonistas get their web fix.</p>
<p><span id="more-1345"></span>Long story short- I think I&#8217;m sticking with Rails. I can write Python, I even prefer it to Ruby for some tasks and will gladly pick it up and do a project with it. But Django doesn&#8217;t offer the key thing that makes Rails such a good <em>framework</em> - it doesn&#8217;t have batteries included (in the sense that you can pick things up and immediately use them). It has a battery compartment and the makings of some AA cells, but you need to fabricate your own batteries and slot them in. Man, that&#8217;s a stretched metaphor. Anyway.</p>
<p>Now, yes, on the surface it does have &#8216;batteries included&#8217;. There&#8217;s a huge pile of &#8216;apps&#8217; bundled with Django (apps pretty much map directly to Rails engines). But there&#8217;s some major problems. Here&#8217;s one. In Rails, you&#8217;ve got all your views under app/views/&lt;controller&gt;/&lt;action&gt;. In Django, there is literally no in-project templates directory. You can put templates under apps- so in my Hackspace Jobs app, I have a jobs app and a companies app (job listings/detail/posting, and company listings/detail/editing respectively). Makes sense, right? So let&#8217;s say I&#8217;ve mapped (via Django&#8217;s urls.py, equivalent to routes.rb with less syntactic sugar) a /jobs/ URL to the view method (action) jobs.views.index, and /companies/ to companies.views.index. So far, you would be hard pressed to find the problem.</p>
<p>So in the views we&#8217;re going to render out the page, clearly. So we set up something like this in our jobs/views.py file:</p>
<pre>def index(request):
  latest_jobs = Job.objects.all().order_by('-id')[:5]
  return render_to_response('index.html', {'latest_jobs': latest_jobs})</pre>
<p>Okay, sweet. Now we put our template (these Python guys still write using real HTML! It amazed me how much I missed Sass and Haml in a mere day of code) into a templates folder underneath the jobs folder, so we have PROJECT_ROOT/jobs/templates/index.html. So far, so good!</p>
<p>Our companies page is doing the exact same thing, of course:</p>
<pre>def index(request):
  latest_companies = Company.objects.all().order_by('-id')[:5]
  return render_to_response('index.html', {'latest_companies': latest_companies})</pre>
<p>Exact. Same. Thing. Complete with index.html as a view name- but render_to_response isn&#8217;t app-aware, and the companies page ends up rendering the jobs index template. Wonderful!</p>
<p>Of course, the Django guys have spotted this might be a problem. So, what&#8217;s the fix? Why, you simply have a folder with all your templates in arranged in a hierarchy like &lt;app&gt;/&lt;view&gt;.html &#8211; sound familiar? Except it&#8217;s not. Because you can&#8217;t have this in your project folder- or rather you could, but you&#8217;d have to specify the absolute path to it in TEMPLATE_FOLDER and suddenly I&#8217;ve lost all interest- now I have to set that TEMPLATE_FOLDER path depending on where I put the app? Okay, but I have three different development environments on my laptop and two dev boxes, and their paths differ, and then I want to be able to easily deploy&#8230; but Django doesn&#8217;t have any distinction of environments- development and production are the same.</p>
<p>Clearly it&#8217;s nice to be able to specify TEMPLATE_FOLDER and have custom templates to override an app&#8217;s bundled templates so you can reuse code. That&#8217;s nice and all. But not having the ability to sanely bundle defaults? That kinda makes life needlessly complex. In Rails, such a thing exists in app/views- stick everything there, a logical but flexible hierarchy is the default, and if you want to make an app an engine with default templates you can override, you can!</p>
<p>This is just one of many little things where Django does so well, gets to the finishing straight and slaps itself really, really hard in the face. Complete batteries-included authentication system? No default views/templates, and the documented methods don&#8217;t work when try to roll your own using the existing authentication system&#8217;s stuff. Then there&#8217;s the whole app vs project thing- in theory, this is really nice to have. Splitting things into logically reusable chunks is <em>always</em> good, <em>if it does not impact negatively on your development</em>. In Rails, you can&#8217;t immediately drop one folder into another project and suddenly add huge chunks of func- okay, we have gems and plugins and engines, but they&#8217;re not quite the same. But I can pull entire models and classes out of one project and drop them into another without any issue at all. Frankly, that&#8217;s all I need. Everything else is specific enough to a project that trying to customise code I wrote for another project is going to take longer than doing it again from scratch in most cases.</p>
<p>Let&#8217;s take devise, which I mentioned way back at the start of this post. It magicks all the authentication stuff that touches the actual HTTP session or DB objects away, but doesn&#8217;t hide it all away- it makes a skeleton in your app which basically lets you set up things in your app to use devise very easily, without losing track of what devise does in the background. But we&#8217;re never going to want to alter the basic things like logging in, resetting a password or registering an account (and if we do, we can, I hasten to add)- so by default, devise doesn&#8217;t show the controller logic there. That all happens outside of your app. The bits you&#8217;re clearly going to want to change- settings defining things like application salts, email addresses, validation rules, and the templates used to render everything- are all there by default, ready to be edited but in a default state of working, ready to go. That&#8217;s how things should be.</p>
<p>In this day and age, fast and productive app development is about taking components, combining them and customising them. The parts of your app that require complex code, those should be easily pulled in as libraries, and easily adjusted to suit. The specific bits that make your app your app will be your code, clearly, but it should be easy to call on external components where it makes sense (geocoding, for instance, in a model- or crypto, emailing, etc). Writing your app should be the tricky bit, not making it all play nice together. Reusable code happens by itself &#8211; if I&#8217;m writing something I know I&#8217;ll use again, into a library it goes. My entire app, though, or even portions of it (Django&#8217;s apps)? Not a chance- if in the time it takes me to import and reconfigure and adjust my Django app for a portion of my code, I can rewrite it again from scratch in Ruby (probably improving on the last time I did it while I go), then what&#8217;s the point?</p>
<p>Django basically makes life too difficult for it to be a particularly useful tool. Tools should help, not hinder, but I found myself spending more time debugging and working around what seemed like trivial small matters and basic functionality than I did making an app. Which isn&#8217;t to say that Rails can&#8217;t learn from it. Django has <em>excellent</em> documentation (though there are some bugs in their code, probably due to version changes), and is a slower moving beast. Conversely, Rails is <em>stampeding</em> along, moving at a terrifying pace. I&#8217;d love to see Rails 3.2 be a stability and slowing down release. We&#8217;ve got all the tools we need- how about we spend a few months making them really, really good, ironing out the kinks, and refining things.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.talkunafraid.co.uk/2011/08/django-a-flying-visit/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

