<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Disembrangling Programming</title>
 <link href="http://embrangler.com/tag/sumo/atom.xml" rel="self"/>
 <link href="http://embrangler.com/tag/sumo"/>
 <updated>2012-02-03T09:24:19-08:00</updated>
 <id>http://embrangler.com/</id>
 <author>
   <name>Paul Craciunoiu</name>
   <email>paul@craciunoiu.net</email>
 </author>

 
 <entry>
   <title>Django and AJAX image uploads</title>
   <link href="http://embrangler.com/2010/08/ajax-uploads-images-in-django"/>
   <updated>2010-08-20T00:00:00-07:00</updated>
   <id>http://embrangler.com/2010/08/ajax-uploads-images-in-django</id>
   <content type="html">&lt;p&gt;Table of contents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href='#demo'&gt;Demo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;a href='#summary'&gt;Summary&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;a href='#server_side_django'&gt;Server side (Django)&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='#model'&gt;Model&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='#form'&gt;Form&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='#view_uploading_image_saving_to_disk'&gt;View&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='#generating_the_thumbnail_with_pil'&gt;Generating the thumbnail with PIL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;a href='#client_side'&gt;Client side&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;a href='#a_note_about_graceful_degradation'&gt;Graceful degradation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='demo'&gt;Demo&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href='http://screencast.com/t/ZGI0NTA3'&gt;Screencast&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Screenshots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The upload form, empty and ready for action: &lt;div class='img-wrap'&gt;&lt;div class='img'&gt;
  &lt;img title='Empty upload form' src='/images/upload/upload_1.png' alt='Empty upload form' /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Browsing for an image: &lt;div class='img-wrap'&gt;&lt;div class='img'&gt;
  &lt;img title='Browsing for an image' src='/images/upload/upload_2.png' alt='Browsing for an image' /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Uploading the image (in progress): &lt;div class='img-wrap'&gt;&lt;div class='img'&gt;
  &lt;img title='Uploading the image' src='/images/upload/upload_3.png' alt='Uploading the image' /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;The image is uploaded: &lt;div class='img-wrap'&gt;&lt;div class='img'&gt;
  &lt;img title='Uploaded image' src='/images/upload/upload_4.png' alt='Uploaded image' /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Deleting the image would show a similar progress block as uploading: &lt;div class='img-wrap'&gt;&lt;div class='img'&gt;
  &lt;img title='Delete the image' src='/images/upload/upload_5.png' alt='Delete the image' /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='summary'&gt;Summary&lt;/h2&gt;

&lt;p&gt;In this post, we&amp;#8217;ll go through how to get AJAX uploads to work with Django, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://en.wikipedia.org/wiki/Cross-site_request_forgery'&gt;csrf protection&lt;/a&gt; with &lt;a href='http://docs.djangoproject.com/en/dev/topics/forms/'&gt;Django&amp;#8217;s forms&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://en.wikipedia.org/wiki/Graceful_degradation'&gt;graceful degradation&lt;/a&gt; (see also &lt;a href='http://en.wikipedia.org/wiki/Unobtrusive_JavaScript'&gt;unobtrusive JavaScript&lt;/a&gt;)&lt;/li&gt;

&lt;li&gt;&lt;a href='http://docs.djangoproject.com/en/dev/topics/http/file-uploads/'&gt;uploading files in Django&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;thumbnail generation with &lt;a href='http://www.pythonware.com/products/pil/'&gt;PIL&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;cross-browser uploading of files through AJAX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I&amp;#8217;m planning to add upload progress. If you can&amp;#8217;t wait for that post (understandably), there are several ways to go about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your site isn&amp;#8217;t using multiple webheads, you can just ask the webhead to get you the size of what&amp;#8217;s been uploaded so far. Since Django can read in chunks, it can tell you how much has been processed. See &lt;a href='http://fairviewcomputing.com/blog/2008/10/21/ajax-upload-progress-bars-jquery-django-nginx/'&gt;this post&lt;/a&gt; for implementation ideas.&lt;/li&gt;

&lt;li&gt;Or, regardless of the server setup, you can use the File API (in Firefox and Chrome) - easier, cleaner, no server-side interaction required.&lt;/li&gt;

&lt;li&gt;Other multi-webhead approaches: writing progress to a file shared among them, or saving directly to a shared folder and e.g. returning the size uploaded so far.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='server_side_django'&gt;Server side (Django)&lt;/h2&gt;

&lt;p&gt;First, we&amp;#8217;ll look at how the server handles files sent to it.&lt;/p&gt;

&lt;h3 id='model'&gt;Model&lt;/h3&gt;

&lt;p&gt;I created an app called &lt;code&gt;upload&lt;/code&gt; with an ImageAttachment model, like so:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://github.com/pcraciunoiu/kitsune/blob/466b65ad885118f0fb8d14f706ea9efa21f49edd/apps/upload/models.py'&gt;apps/upload/models.py&lt;/a&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.conf&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;User&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.contrib.contenttypes.models&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;ContentType&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.contrib.contenttypes&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;generic&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.db&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;models&lt;/span&gt;


&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ImageAttachment&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;models&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;Model&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;A tag on an item.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='nb'&gt;file&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;models&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ImageField&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;upload_to&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;IMAGE_UPLOAD_PATH&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;thumbnail&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;models&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ImageField&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;upload_to&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;THUMBNAIL_UPLOAD_PATH&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;creator&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;models&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ForeignKey&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;User&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;related_name&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;image_attachments&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;content_type&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;models&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ForeignKey&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ContentType&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;object_id&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;models&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;PositiveIntegerField&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;

    &lt;span class='n'&gt;content_object&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;generic&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;GenericForeignKey&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;__unicode__&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;file&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This represents an image attached to a piece of content (using a generic foreign key). Pretty basic stuff. The form is ridiculously simple:&lt;/p&gt;

&lt;h3 id='form'&gt;Form&lt;/h3&gt;

&lt;p&gt;&lt;a href='http://github.com/pcraciunoiu/kitsune/blob/466b65ad885118f0fb8d14f706ea9efa21f49edd/apps/upload/forms.py'&gt;apps/upload/forms.py&lt;/a&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;forms&lt;/span&gt;


&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ImageUploadForm&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;forms&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;Form&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Image upload form.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='n'&gt;image&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;forms&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ImageField&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='view_uploading_image_saving_to_disk'&gt;View (uploading image, saving to disk)&lt;/h3&gt;

&lt;p&gt;The view is a bit more complicated, so I won&amp;#8217;t go into the details. But you can &lt;a href='http://github.com/pcraciunoiu/kitsune/blob/466b65ad885118f0fb8d14f706ea9efa21f49edd/apps/upload'&gt;have a look at the entire app&lt;/a&gt; and &lt;a href='#footer'&gt;contact me&lt;/a&gt; if you have questions. Basically, the view does the file upload as you see in &lt;a href='http://docs.djangoproject.com/en/dev/topics/http/file-uploads/'&gt;Django&amp;#8217;s documentation&lt;/a&gt;. The function &lt;a href='http://github.com/pcraciunoiu/kitsune/blob/466b65ad885118f0fb8d14f706ea9efa21f49edd/apps/upload/utils.py#L9'&gt;&lt;code&gt;create_image_attachment&lt;/code&gt;&lt;/a&gt; deals with the part about saving a file to disk.&lt;/p&gt;

&lt;h3 id='generating_the_thumbnail_with_pil'&gt;Generating the thumbnail with PIL&lt;/h3&gt;

&lt;p&gt;There is also a task for generating thumbnails, which is offloaded from the web server thread to improve performance. If you don&amp;#8217;t need that, you can just call generate_thumbnail directly, it&amp;#8217;s defined &lt;a href='http://github.com/pcraciunoiu/kitsune/blob/466b65ad885118f0fb8d14f706ea9efa21f49edd/apps/upload/tasks.py'&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id='client_side'&gt;Client side&lt;/h2&gt;

&lt;p&gt;Now, the magical JavaScript!&lt;/p&gt;

&lt;p&gt;We&amp;#8217;re using jQuery on &lt;a href='http://support.mozilla.com'&gt;SUMO&lt;/a&gt;, so I wrote two jQuery extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;jQuery.fn.ajaxSubmitInput(options) &amp;#8211; wraps an &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; in a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; and creates an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; to which that form posts. To get around Django&amp;#8217;s csrf protection, it also copies the &lt;code&gt;csrfmiddlewaretoken&lt;/code&gt; hidden input into the form. You can&amp;#8217;t clone a file input for security reasons (nor can you change or access its value), so you need to wrap it in a form.&lt;/li&gt;

&lt;li&gt;jQuery.fn.wrapDeleteInput(options) &amp;#8211; wraps an &lt;code&gt;input&amp;lt;type=&amp;quot;submit&amp;quot;&amp;gt;&lt;/code&gt; in a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; and creates an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; to which that form posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two pretty much summarize the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;when the user changes the value of the file input, post the form&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;show some progress while the file is uploading&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;once the file is done uploading, show a thumbnail of the image&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;also create the delete input and wrap it in the form using &lt;code&gt;wrapDeleteInput()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;when the user clicks on the delete button, post the action&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;show some progress while the file is being deleted&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id='a_note_about_graceful_degradation'&gt;A note about graceful degradation&lt;/h2&gt;

&lt;p&gt;To degrade gracefully, you want to post the file input to whatever view you&amp;#8217;re including it to. And you can just do something like:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;some_view&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='c'&gt;# ...&lt;/span&gt;
    &lt;span class='c'&gt;# NOJS: upload image&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;upload_image&amp;#39;&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;POST&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
        &lt;span class='n'&gt;upload_images&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;obj&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='c'&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Thanks for reading, hope it helps!&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>Access control in Django, and django-authority</title>
   <link href="http://embrangler.com/2010/05/access-control-django"/>
   <updated>2010-05-22T00:00:00-07:00</updated>
   <id>http://embrangler.com/2010/05/access-control-django</id>
   <content type="html">&lt;p&gt;The past week I&amp;#8217;ve been working with Django and permissions. I&amp;#8217;ll talk here about the available options and go into details about the one &lt;a href='https://wiki.mozilla.org/Support/Kitsune'&gt;Kitsune&lt;/a&gt; is using.&lt;/p&gt;

&lt;p&gt;Here are the options I looked at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='#djangos_default_permissions'&gt;Django&amp;#8217;s default permissions&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='#amos_access_app'&gt;AMO&amp;#8217;s access app&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='#djangoauthority'&gt;django-authority&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='djangos_default_permissions'&gt;Django&amp;#8217;s default permissions&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Read more about this in &lt;a href='http://docs.djangoproject.com/en/dev/topics/auth/'&gt;Django&amp;#8217;s documentation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Django provides a convenient and easy to use permissions system. The documentation covers it all, so I&amp;#8217;m just going to summarize the pros and cons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comes with Django (=&amp;gt; robust, well-written and stable)&lt;/li&gt;

&lt;li&gt;Permission functions attached to the user object&lt;/li&gt;

&lt;li&gt;Per-group permissions and per-user permissions&lt;/li&gt;

&lt;li&gt;Model-level (a.k.a. per content_type) permissions (can add custom model-level permissions as well)&lt;/li&gt;

&lt;li&gt;Work with Jinja templates, simply use &lt;code&gt;user.has_perm(&amp;#39;perm_name&amp;#39;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No per-object permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='amos_access_app'&gt;AMO&amp;#8217;s access app&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://github.com/davedash/zamboni/blob/master/docs/topics/acl.rst'&gt;AMO&amp;#8217;s access app&lt;/a&gt; adds some flexibility on assigning permissions with wildcards and also stores the user&amp;#8217;s groups in &lt;code&gt;request.groups&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flexible way to assign all permissions in a certain group&lt;/li&gt;

&lt;li&gt;Does not store permissions in the models &lt;code&gt;Meta&lt;/code&gt; class&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replaces django&amp;#8217;s permission system (either use one or the other)&lt;/li&gt;

&lt;li&gt;No per-object permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use this, you can do something like this in your views:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;access&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;acl&lt;/span&gt;

&lt;span class='c'&gt;# ...&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;some_view_action&lt;/span&gt;&lt;span class='p'&gt;():&lt;/span&gt;
    &lt;span class='n'&gt;acl&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;action_allowed&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Group&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;PermissionName&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='djangoauthority'&gt;django-authority&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://packages.python.org/django-authority/'&gt;django-authority&lt;/a&gt; was the most promising of the three. It adds per-object permissions and wraps Django&amp;#8217;s default permissions system without forcing an either-or choice. It also has a decent documentation, and although still in draft at the time of this writing, it was enough for me to figure out how to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-object permissions&lt;/li&gt;

&lt;li&gt;Wraps Django&amp;#8217;s default permissions system, so you can use both&lt;/li&gt;

&lt;li&gt;Custom permissions&lt;/li&gt;

&lt;li&gt;Add permissions to &lt;code&gt;appname/permissions.py&lt;/code&gt;, so they are completely separate from the models (unlike Django&amp;#8217;s permissions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Needs a bit of work to hook in with &lt;a href='http://jinja.pocoo.org/2/documentation'&gt;Jinja&lt;/a&gt; templates&lt;/li&gt;

&lt;li&gt;Admin functionality not yet complete - the admin form &lt;a href='http://packages.python.org/django-authority/handling_admin.html'&gt;generated by the &lt;code&gt;edit_permissions&lt;/code&gt;&lt;/a&gt; &lt;a href='http://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/'&gt;admin action&lt;/a&gt; does not work at the time of this writing. The alternative is not too bad however, you can manually add to the Permission model using that model&amp;#8217;s admin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id='djangoauthority_on_kitsune'&gt;django-authority on Kitsune&lt;/h3&gt;

&lt;p&gt;We are currently focused on rewriting SUMO&amp;#8217;s discussion forums. For Kitsune, we have multiple forums and we wanted the ability to have different forum moderators per forum. We&amp;#8217;re also using Jinja. django-authority comes with default Django template support, so if you&amp;#8217;re using that, you don&amp;#8217;t need to write your own template functions/filters.&lt;/p&gt;

&lt;p&gt;For Jinja support, just add this to some app&amp;#8217;s helpers.py&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;jinja2&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;jingo&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;register&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;authority&lt;/span&gt;

&lt;span class='c'&gt;# ...&lt;/span&gt;

&lt;span class='nd'&gt;@register.function&lt;/span&gt;
&lt;span class='nd'&gt;@jinja2.contextfunction&lt;/span&gt;
&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;has_perm&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;perm&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;obj&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class='sd'&gt;    Check if the user has a permission on a specific object.&lt;/span&gt;

&lt;span class='sd'&gt;    Returns boolean.&lt;/span&gt;
&lt;span class='sd'&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='n'&gt;check&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;authority&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_check&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;request&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;perm&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;check&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;obj&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, in your templates, you can just use:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='jinja'&gt;&lt;span class='x'&gt;[% if has_perm(&amp;#39;perm_cls.codename_model&amp;#39;, object) %]&lt;/span&gt;
&lt;span class='x'&gt;  &lt;/span&gt;&lt;span class='c'&gt;{# grats, you&amp;#39;ve got the perms! #}&lt;/span&gt;&lt;span class='x' /&gt;
&lt;span class='x'&gt;[% endif %]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;(Couldn&amp;#8217;t figure out how to escape braces in jekyll, so you&amp;#8217;ll need to replace the square brackets above.)&lt;/p&gt;

&lt;p&gt;See &lt;a href='http://packages.python.org/django-authority/create_per_object_permission.html'&gt;this link&lt;/a&gt; for more information on the &lt;code&gt;perm_cls&lt;/code&gt; and &lt;code&gt;codename&lt;/code&gt; variables.&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re interested, here&amp;#8217;s the &lt;a href='http://github.com/pcraciunoiu/kitsune/commit/f2f256b5e70ef3c89cda95dde29823ae48e057bb'&gt;Kitsune code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hope that helps others who may decide to go this route. Questions and suggestions for improvement are always welcome, of course.&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>My SUMO local development environment</title>
   <link href="http://embrangler.com/2010/01/my-sumo-local-development-environment"/>
   <updated>2010-01-04T00:00:00-08:00</updated>
   <id>http://embrangler.com/2010/01/my-sumo-local-development-environment</id>
   <content type="html">&lt;h2 id='summary'&gt;Summary&lt;/h2&gt;

&lt;p&gt;A while back, James wrote about &lt;a href='http://coffeeonthekeyboard.com/local-web-development-323/' title='James&amp;apos; local development environment'&gt;his development environment&lt;/a&gt;. I wanted to summarize my approach, and go into a bit more detail about the specific setup you would need to have SUMO work locally. For more information, see also &lt;a href='http://https://wiki.mozilla.org/Support:Sumodev#Get_Involved'&gt;this wiki page&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id='my_lde_ubuntu_linux'&gt;My LDE: Ubuntu Linux&lt;/h2&gt;

&lt;p&gt;My &lt;abbr title='local development environment'&gt;LDE&lt;/abbr&gt; is slightly different: I dual boot Windows 7 and Kubuntu 9.10. As of now I&amp;#8217;m still a student, and I work on my own machine. There are several reasons I find dual booting a great solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Best performance&lt;/strong&gt; while developing. Although less and less of an issue, being in the native &lt;abbr title='Operating System'&gt;OS&lt;/abbr&gt; is always be faster than running a &lt;abbr title='Virtual Machine'&gt;VM&lt;/abbr&gt;.&lt;/li&gt;

&lt;li&gt;A &lt;strong&gt;full powered OS&lt;/strong&gt; that I can use for other work. I do schoolwork and other contracting work, and sometimes Windows is not an option. Even a VM has its limitations when it comes to certain drivers (one example is audio &amp;#8211; which I needed for a &lt;a href='http://code.google.com/p/music-visualizer-cs160/' title='Music Visualizer for CMPS160'&gt;graphics project&lt;/a&gt;)&lt;/li&gt;

&lt;li&gt;Keeps my &lt;strong&gt;work and pleasure separate&lt;/strong&gt; &amp;#8211; having dual boot is basically like having two machines. Typically, when I&amp;#8217;m in Linux, I do work, otherwise I&amp;#8217;m in Windows to do personal things.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only downside is some additional wait time for rebooting if you need to switch to the other OS. Since I work on Linux only, I rarely need to switch while working. However, everything works fine for me on Linux &amp;#8211; sound, video, even webcam and VPN (will blog later about all of these).&lt;/p&gt;

&lt;h2 id='lamp'&gt;LAMP&lt;/h2&gt;

&lt;p&gt;For web development in general, you&amp;#8217;ll need to have the &lt;a href='http://en.wikipedia.org/wiki/LAMP_%28software_bundle%29'&gt;LAMP stack&lt;/a&gt; installed. Fortunately, Ubuntu provides an easy way to do this in one line. From a terminal, run:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;sudo tasksel
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Select &lt;em&gt;LAMP server&lt;/em&gt; from the list, and choose OK (TAB + ENTER). Follow the steps to choose a MySQL password and any other configuration there may be. Once that&amp;#8217;s done, you should be able to go to &lt;code&gt;http://localhost&lt;/code&gt; and see the &amp;#8220;It works!&amp;#8221; text. Here are some useful paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web root: &lt;code&gt;/var/www/&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;Hosts file: &lt;code&gt;/etc/hosts&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;Apache sites: &lt;code&gt;/etc/apache2/sites-available/&lt;/code&gt; and &lt;code&gt;/etc/apache2/sites-enabled/&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;PHP ini: &lt;code&gt;/etc/php5/apache2/php.ini&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;MySQL ini: &lt;code&gt;/etc/php5/conf.d/mysql.ini&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='sumo_setup'&gt;SUMO setup&lt;/h2&gt;

&lt;p&gt;What you&amp;#8217;ll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LAMP stack &amp;#8211; see &lt;a href='#lamp'&gt;above&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;A host (optional) &amp;#8211; see &lt;a href='#host'&gt;below&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;SSL &amp;#8211; see &lt;a href='http://www.tc.umn.edu/~brams006/selfsign_ubuntu.html'&gt;this guide&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;Sphinx &amp;#8211; see &lt;a href='https://wiki.mozilla.org/Support/Sphinx_Installation' title='installing Sphinx'&gt;this guide&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;Memcached &amp;#8211; I just install the package and that&amp;#8217;s enough: &lt;code&gt;sudo apt-get install memcached php5-memcache&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;Virtualbox (optional) &amp;#8211; for testing: &lt;code&gt;sudo apt-get install virtualbox&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;A copy of our database &amp;#8211; you may ask James, Laura or myself for this &amp;#8211; &lt;a href='#help'&gt;see how, below&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;SVN &amp;#8211; &lt;code&gt;sudo apt-get install subversion&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;Checking out and configuring the SUMO codebase &amp;#8211; &lt;a href='https://wiki.mozilla.org/Support/SUMO_install_process' title='checking out and configuring SUMO'&gt;see this guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span id='host'&gt;To set up a SUMO host, I add the entry for it in `/etc/hosts`. Here is the top of my file:&lt;/span&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;127.0.0.1   localhost
127.0.1.2   sumo
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, add the site info to apache. Here&amp;#8217;s my complete conf file (with SSL), in &lt;code&gt;/etc/apache2/sites-available/sumo&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&amp;lt;VirtualHost 127.0.1.2:80&amp;gt;
    ServerAdmin webmaster@sumo
    ServerName sumo
    ServerAlias sumo
    DocumentRoot /var/www/trunk/webroot
    &amp;lt;Directory /var/www/trunk/webroot&amp;gt;
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;

&amp;lt;IfModule mod_ssl.c&amp;gt;
&amp;lt;VirtualHost _default_:443&amp;gt;

    ServerAdmin webmaster@sumo
    ServerName sumo
    ServerAlias sumo
    DocumentRoot /var/www/trunk/webroot
    &amp;lt;Directory /var/www/trunk/webroot&amp;gt;
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
    &amp;lt;/Directory&amp;gt;
    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/apache.pem
&amp;lt;/VirtualHost&amp;gt;
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To enable the site, just do this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nb'&gt;cd&lt;/span&gt; /etc/apache2/sites-enabled
sudo ln -s ../sites-available/sumo
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, restart apache:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;sudo /etc/init.d/apache2 restart
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='testing'&gt;Testing&lt;/h2&gt;

&lt;p&gt;For testing, I also use a VM in VirtualBox, running WinXP with multiple IEs. I chose WinXP to save space &amp;#8211; only takes up 3GB. You should allow 4GB just in case you want to install more stuff. Using multiple IEs is not the best, as &lt;a href='http://www.quirksmode.org/css/condcom.html'&gt;quirksmode points out&lt;/a&gt;, so you may consider running a VM for each IE version you wish to test. However, I haven&amp;#8217;t experienced that many problems. On WinXP I&amp;#8217;ve also installed Firefox (of course), Chrome, and Opera. On Ubuntu I just use the defaults: Firefox and Konqueror (since I&amp;#8217;m running Kubuntu).&lt;/p&gt;

&lt;h2 id='help'&gt;Help?&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve tried to cover everything, but of course there may be stuff I missed. If you need additional info, you may leave a comment. To obtain a copy of our database, you can find &lt;a href='https://wiki.mozilla.org/Support:Sumodev#Get_Involved'&gt;us on IRC or contact us by email&lt;/a&gt;.&lt;/p&gt;</content>
 </entry>
 

</feed>

