Parbhat Purihttps://parbhatpuri.com/2019-06-05T20:00:00+05:30Benefits of remote work from an Indian context2019-06-05T20:00:00+05:302019-06-05T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2019-06-05:/benefits-of-remote-work-from-an-indian-context.html<p>Exploring the remote work as an Indian</p><p>We are living in the age of Remote work. More and more people are shifting from traditional office jobs to work from anywhere. A person current location no longer determines his or her profession. In the last decade, it was necessary to move from small towns to growing big cities for better opportunities. But this is no longer true. My location now plays a small role in my work life, especially for digital workers.</p>
<p>If you search for the benefits of remote work, there are a number of great articles on the internet. Some common benefits are</p>
<ul>
<li>Increased productivity</li>
<li>Flexible schedule</li>
<li>Lesser commute time</li>
<li>More savings</li>
</ul>
<p>The goal of this article is to highlight the benefits of remote work from an Indian perspective. We are a diverse country and our needs are different from the rest of the world. A large number of people are working remotely from India for foreign companies. Because the number of Indian companies adopting remote work is very few as compared to countries like the USA. These are some benefits specific to Indians and in no particular order.</p>
<h3>Global Opportunity</h3>
<p>Remote work opens up the borders set up by the governments. Remote Indian is no longer limited within the boundaries of India to find work. No visa requirements and we can work for global companies from the comfort of the home country. One important point to discuss here is</p>
<h4>Legal status: Contractor or Employee</h4>
<p>Most remote companies hire remote workers as contractors. This makes it easy for companies to hire people globally. Only a small percentage of companies hire remote workers on Payroll and mostly through third-party companies managing the payroll. Does it matter? No, the only important part is the work performed by the remote worker for the company. But many Indians still see Job status as a safety. Provide great quality work, build trust and forget the rest.</p>
<p>The above point is for remote jobs. For freelancers, remote work makes the whole world open where Indians can do consulting. Working for foreign companies have an unfair advantage for Indians. Earning in foreign currency and spending in rupees.</p>
<h3>Living in Indian tech cities is a choice and not compulsion</h3>
<p>Most good tech jobs in India are based in big tech cities like Bangalore, Hyderabad, Pune and Delhi/NCR. These tech cities are one of the fastest growing cities in the World by GDP. But this development is coming at the cost of <a href="https://www.bloomberg.com/news/features/2018-10-21/the-world-s-fastest-growing-economy-has-the-world-s-most-toxic-air" target="_blank">increased pollution</a>, lesser water resources. As per <a href="https://www.financialexpress.com/india-news/delhi-bangalore-hyderabad-to-run-out-of-water-by-2020-here-is-what-niti-aayog-report-says/1208830/" target="_blank">Niti Aayog</a>, Delhi, Bangalore, Hyderabad to run out of water soon. Buying a home is like a dream. For an average salaried person, it can take about 15-20 years for own a house after paying EMIs all these years.</p>
<p>Remote work gives an option to move out of these cities. And it is no longer mandatory to live in these cities because of the Job. It becomes a personal preference instead of compulsion. The intention is not to hurt someone but highlighting the benefits of remote work. These cities provide great resources to grow like jobs, meetups and conferences. Remote work gives an option to move out if people want.</p>
<h3>Money $$$</h3>
<p>Being a remote worker from India has a financial benefit. There are two types of companies hiring people from India. The good companies hire quality workers and pay good rates. The other type is looking for cheaper work. By working for the first type of companies, people in India can generate more income. Indian companies are at a disadvantage because they can not pay similar to other global companies while looking for talented people. It also helps the Indian economy because of foreign currency coming into the country.</p>
<h3>Is remote work perfect?</h3>
<p>Absolutely not. It has its own issues like an office Job. Remote work demands discipline, self-motivation etc. For Indians, there are issues like society outlook which consider remote workers as unemployed. I feel the reasons to go remote are personal. For me, the benefits of remote work in the Indian context are far greater than the possible issues.</p>
<p>Are you a remote worker from India or planning to go remote? I would love to hear your reasons to work remotely in the comments.</p>
<p>Thanks for reading. Do you know there is a great community of remote workers in India? Join the slack group of <a href="https://remoteindian.com/" target="_blank">Remote Indian</a> community.</p>Amplify a Wagtail/Django site - Validation (Part 2)2019-01-01T20:00:00+05:302019-01-01T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2019-01-01:/amplify-wagtail-django-site-validation-part-2.html<p>Second part of the series on valid AMP pages as only valid pages appears in Google search results.</p><p>The driving force behind creating AMP pages for a content website is to appear at the top of Google search results. AMP pages appear in search results with a lightning bolt icon. Page speed is also one of the important factors in the ranking of search results. As AMP pages are very fast due to its strict specification, it is common to see AMP version of a page appearing at the top. AMP pages with structured data also appear in the Top stories carousel, host carousel of rich results, Visual stories, and rich results in mobile Search results.</p>
<p>This blog post is the second part of the series: Amplify a Wagtail/Django site.</p>
<ul>
<li>Part 1: <a href="https://parbhatpuri.com/amplify-wagtail-django-site-urls-part-1.html">Amplify a Wagtail/Django site - URLs</a></li>
<li><strong>Part 2: Amplify a Wagtail/Django site - Validation (current article)</strong></li>
<li>Part 3: Amplify a Wagtail/Django site - CORS</li>
</ul>
<p style="text-align: center">
<img src="https://i.imgur.com/WsEBnXk.gif" alt="AMP pages in google search" height="500px">
</p>
<p>In the above gif, notice the ⚡ icon next to search results. Most of the results are AMP pages and only the last search result is a normal page. It is a highly likely case that mobile users only click the top links. Therefore AMP page becomes a necessity for online content publishers who publish 100s of articles daily.</p>
<h3>How to make AMP page discoverable to Google search?</h3>
<p>The initial requirement is to have a <em>link</em> tag in the non-AMP page with <em>rel="amphtml"</em> and <em>href</em> contains URL of AMP version.</p>
<p>Add the following to the non-AMP page:</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"amphtml"</span> <span class="na">href</span><span class="o">=</span><span class="s">"https://www.example.com/url/to/amp/document.html"</span><span class="p">></span>
</code></pre></div>
<p>And this to the AMP page:</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"canonical"</span> <span class="na">href</span><span class="o">=</span><span class="s">"https://www.example.com/url/to/non-amp/document.html"</span><span class="p">></span>
</code></pre></div>
<p>The <strong>important</strong> requirement is the AMP page should be valid. When a mobile user clicks on an AMP search result, Google search retrieves the page from the <a href="https://developers.google.com/amp/cache/">Google AMP cache</a>. Only valid AMP pages are cached. Valid AMP pages are preloaded efficiently and stored in Google cache to improve the user experience.</p>
<h3>Render valid AMP pages in Wagtail/Django powered site</h3>
<p>Valid means AMP page should not contain tags which are restricted as per AMP specification. AMP HTML is a subset of HTML, it puts some restrictions on the full set of tags and functionality available through HTML. For performance, AMP HTML does not allow author written JavaScript beyond what is provided through the custom elements.</p>
<p>In Wagtail powered site, AMP templates should not use restricted tags. For example, <code><img></code> tag should be replaced with <code><amp-img></code>. <code><img></code> does not have an end tag. However, <code><amp-img></code> does have an end tag <code></amp-img></code>.</p>
<ul>
<li><strong>Change tags for fields whose properties can be accessed</strong>: It is easy to change tags in cases where we can directly access field properties. Wagtail image tag render <code><img></code> tag so we can directly access image properties to create <code><amp-img></code> tag:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="c"><!-- Template: https://github.com/Parbhat/wagtail-amp/blob/master/blog/templates/blog/blog_page_amp.html --></span>
{% for item in page.gallery_images.all %}
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
{% image item.image fill-600x500 as gallery_image %}
<span class="p"><</span><span class="nt">amp-img</span> <span class="na">alt</span><span class="o">=</span><span class="s">"{{ gallery_image.alt }}"</span>
<span class="na">src</span><span class="o">=</span><span class="s">"{{ gallery_image.url }}"</span>
<span class="na">width</span><span class="o">=</span><span class="s">"{{ gallery_image.width }}"</span>
<span class="na">height</span><span class="o">=</span><span class="s">"{{ gallery_image.height }}"</span>
<span class="na">layout</span><span class="o">=</span><span class="s">"responsive"</span><span class="p">></span>
<span class="p"></</span><span class="nt">amp-img</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>{{ item.caption }}<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
{% endfor %}
</code></pre></div>
<ul>
<li><strong>Change tags in Richtext and Streamfield</strong>: The main feature of a CMS is a WYSIWYG HTML Editor to create content. Wagtail also uses Draftail in Richtext field and Streamfield's Richtext block. As the content created with these fields is stored as HTML, we do not have direct access to images etc. To create a valid AMP page, we can use a Python package called <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">Beautiful Soup</a> to modify the HTML.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span>
<span class="k">def</span> <span class="nf">amplify_html</span><span class="p">(</span><span class="n">rendered_html</span><span class="p">):</span>
<span class="n">bs</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="n">rendered_html</span><span class="p">)</span>
<span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">bs</span><span class="o">.</span><span class="n">find_all</span><span class="p">(</span><span class="s1">'img'</span><span class="p">,</span> <span class="n">attrs</span><span class="o">=</span><span class="p">{</span><span class="s1">'src'</span><span class="p">:</span> <span class="kc">True</span><span class="p">}):</span>
<span class="n">amp_img</span> <span class="o">=</span> <span class="n">bs</span><span class="o">.</span><span class="n">new_tag</span><span class="p">(</span>
<span class="s1">'amp-img'</span><span class="p">,</span> <span class="n">src</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"src"</span><span class="p">),</span>
<span class="n">alt</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"alt"</span><span class="p">,</span> <span class="s2">""</span><span class="p">),</span>
<span class="n">layout</span><span class="o">=</span><span class="s2">"responsive"</span><span class="p">,</span>
<span class="n">width</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"width"</span><span class="p">,</span> <span class="mi">550</span><span class="p">),</span>
<span class="n">height</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"height"</span><span class="p">,</span> <span class="mi">368</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">amp_img</span><span class="p">[</span><span class="s1">'class'</span><span class="p">]</span> <span class="o">=</span> <span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"class"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
<span class="n">image</span><span class="o">.</span><span class="n">replace_with</span><span class="p">(</span><span class="n">amp_img</span><span class="p">)</span>
<span class="c1"># ...</span>
<span class="c1"># Complete code: https://github.com/Parbhat/wagtail-amp/blob/master/blog/utils.py</span>
<span class="k">return</span> <span class="n">bs</span><span class="o">.</span><span class="n">decode_contents</span><span class="p">()</span>
</code></pre></div>
<p>In the first <a href="https://parbhatpuri.com/amplify-wagtail-django-site-urls-part-1.html">part</a> of the series, we learned the Wagtail serving mechanism. Serve method on a Page model is responsible for sending HTML response. Body field is a Streamfield and we can get the rendered HTML using the <code>__html__()</code> method. The body field HTML is non-AMP, it can contain tags which are restricted in AMP pages. Therefore we get the AMP HTML using <code>amplify_html</code> method and add it in the context.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">AmpBlogPage</span><span class="p">(</span><span class="n">Page</span><span class="p">):</span>
<span class="c1"># ...</span>
<span class="c1"># Complete code: https://github.com/Parbhat/wagtail-amp/blob/8abd9d6b0ee36fe2183167ad6c5f4556dd825590/blog/models.py#L92</span>
<span class="k">def</span> <span class="nf">get_template_amp</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="s1">'blog/amp_blog_page_amp.html'</span>
<span class="k">def</span> <span class="nf">serve</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">is_amp_request</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'is_amp_request'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_amp_request</span><span class="p">:</span>
<span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s1">'is_amp_request'</span><span class="p">)</span>
<span class="n">context</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_context</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">body_html</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">__html__</span><span class="p">()</span>
<span class="n">body_amp_html</span> <span class="o">=</span> <span class="n">amplify_html</span><span class="p">(</span><span class="n">body_html</span><span class="p">)</span>
<span class="n">context</span><span class="p">[</span><span class="s1">'body_amp_html'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mark_safe</span><span class="p">(</span><span class="n">body_amp_html</span><span class="p">)</span>
<span class="k">return</span> <span class="n">TemplateResponse</span><span class="p">(</span>
<span class="n">request</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">get_template_amp</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">),</span>
<span class="n">context</span>
<span class="p">)</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">AmpBlogPage</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">serve</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="c1"># ...</span>
</code></pre></div>
<p><code>body_amp_html</code> is used in <a href="https://github.com/Parbhat/wagtail-amp/blob/master/blog/templates/blog/amp_blog_page_amp.html">template</a> instead of <code>page.body</code>.</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">article</span><span class="p">></span>
{{ body_amp_html }}
<span class="p"></</span><span class="nt">article</span><span class="p">></span>
</code></pre></div>
<p>Similarly we can get AMP HTML for Richtext field. In Richtext field, we need to use <a href="https://github.com/wagtail/wagtail/blob/stable/2.4.x/wagtail/core/rich_text/__init__.py#L17">expand_db_html</a> function to expand database-representation HTML into proper HTML.</p>
<p>We can also use a separate field for AMP content and use publish hook to fill it. Then we do not have to parse and modify HTML in the request-response cycle. But we can avoid it. As the process is fast and also pages are served from Google cache in Google search. Each time a user accesses AMP content from the cache, the content is automatically updated, and the updated version is served to the next user once the content has been cached.</p>
<h4>For streamfield, there is another option:</h4>
<p>If your project uses Streamfield's Richtext block without the image, embed <a href="https://docs.wagtail.io/en/v2.4/advanced_topics/customisation/page_editing_interface.html#limiting-features-in-a-rich-text-field">feature</a> then you can skip the beautiful soup method. It is recommended to use Richtext field with a small set of features and use <a href="https://docs.wagtail.io/en/v2.4/topics/streamfield.html#imagechooserblock">Image chooser block</a>. New Streamfield blocks should be created depending on the UI requirements. We only need to add <code>is_amp_request</code> in context.</p>
<div class="highlight"><pre><span></span><code> <span class="k">def</span> <span class="nf">serve</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">is_amp_request</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'is_amp_request'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_amp_request</span><span class="p">:</span>
<span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s1">'is_amp_request'</span><span class="p">)</span>
<span class="n">context</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_context</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">context</span><span class="p">[</span><span class="s1">'is_amp_request'</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">return</span> <span class="n">TemplateResponse</span><span class="p">(</span>
<span class="n">request</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">get_template_amp</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">),</span>
<span class="n">context</span>
<span class="p">)</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">AmpBlogPage</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">serve</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c"><!-- Template for streamfield block like Service block --></span>
{% if is_amp_request %}
{% image value.image fill-400x300 as amp_img %}
<span class="p"><</span><span class="nt">amp-img</span> <span class="na">alt</span><span class="o">=</span><span class="s">"{{ amp_img.alt }}"</span>
<span class="na">src</span><span class="o">=</span><span class="s">"{{ amp_img.url }}"</span>
<span class="na">width</span><span class="o">=</span><span class="s">"{{ amp_img.width }}"</span>
<span class="na">height</span><span class="o">=</span><span class="s">"{{ amp_img.height }}"</span>
<span class="na">layout</span><span class="o">=</span><span class="s">"responsive"</span><span class="p">></span>
<span class="p"></</span><span class="nt">amp-img</span><span class="p">></span>
{% else %}
{% image value.image fill-400x300 %}
{% endif %}
</code></pre></div>
<p>With these small changes, our AMP page is ready to fly high with Wagtail. In the next part of the series, we will add CORS headers required in <a href="https://www.ampproject.org/docs/reference/components/amp-list">amp-list</a> and <a href="https://www.ampproject.org/docs/reference/components/amp-form">amp-form</a> components.</p>
<p>Thanks for reading. You can also follow the Github <a href="https://github.com/Parbhat/wagtail-amp">repo</a>. AMP-lify!</p>
<p>Useful links:</p>
<ol>
<li><a href="https://www.ampproject.org/docs/fundamentals/validate">Validate AMP pages</a></li>
<li><a href="https://www.ampproject.org/docs/fundamentals/discovery">Make your page discoverable</a></li>
<li><a href="https://developers.google.com/amp/cache/overview">Google AMP Cache</a></li>
<li><a href="https://developers.google.com/search/docs/data-types/article#amp">Google search</a></li>
<li><a href="https://developers.google.com/search/docs/guides/about-amp">Understand how AMP looks in search results</a></li>
<li><a href="https://www.ampproject.org/docs/fundamentals/spec">AMP HTML Specification</a></li>
<li><a href="https://www.ampproject.org/docs/fundamentals/how_cached">How AMP pages are cached</a></li>
</ol>Amplify a Wagtail/Django site - URLs (Part 1)2018-07-20T20:00:00+05:302018-07-20T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2018-07-20:/amplify-wagtail-django-site-urls-part-1.html<p>First part of the series which list multiple ways to create URLs for AMP pages.</p><p>Accelerated Mobile Pages (AMP) is an Open Source project with a focus on creating a fast and smooth experience for website users. AMP version of a page also shows at the top of Google search results for mobile users. AMP is must for every content site these days. If you already have a running website, AMP is just another version of an existing page on the site. AMP version loads fast due to AMP spec like max CSS size can be 50KB only. You can read more about AMP on its <a href="https://www.ampproject.org/">site</a>.</p>
<p>This blog post is the first part of the series: Amplify a Wagtail/Django site.</p>
<ul>
<li><strong>Part 1: Amplify a Wagtail/Django site - URLs (current article)</strong></li>
<li>Part 2: <a href="https://parbhatpuri.com/amplify-wagtail-django-site-validation-part-2">Amplify a Wagtail/Django site - Validation</a></li>
<li>Part 3: Amplify a Wagtail/Django site - CORS</li>
</ul>
<p><img src="https://i.imgur.com/6Fg7V9Y.jpg" alt="Wagtail and AMP logo." width="550px"/></p>
<p>Wagtail is a beautiful CMS built on top of the Django web framework. This blog series will list the steps to create an AMP version of your existing Wagtail pages. A demo project is available on <a href="https://github.com/Parbhat/wagtail-amp">Github</a>.</p>
<h2>URL of the AMP version of page</h2>
<p>As AMP is just another version of an existing page, it will have a new URL. Commonly <em>amp</em> keyword is added to the normal page URL. Suppose we want to Amplify a page like <a href="https://wagtail.io/blog/graphql-with-streamfield/">https://wagtail.io/blog/graphql-with-streamfield/</a>. AMP version of this page can have the following URLs:</p>
<ul>
<li>https://wagtail.io/blog/graphql-with-streamfield/<strong>amp/</strong></li>
<li>https://wagtail.io/<strong>amp/</strong>blog/graphql-with-streamfield/</li>
</ul>
<p>The first URL is easy to create in Wagtail. But the second URL needs some customization in the Wagtail serving mechanism. This blog post will list the steps to use any of these URLs in your project.</p>
<h3>URL 1: AMP keyword at end of Normal URL</h3>
<p>Wagtail comes with a contrib app called <a href="http://docs.wagtail.io/en/v2.1.1/reference/contrib/routablepage.html">RoutablePageMixin</a>. The RoutablePageMixin extend a page to respond on multiple sub URLs with different views.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">BlogPage</span><span class="p">(</span><span class="n">RoutablePageMixin</span><span class="p">,</span> <span class="n">Page</span><span class="p">):</span>
<span class="c1"># Complete code: https://github.com/Parbhat/wagtail-amp/blob/master/blog/models.py#L47</span>
<span class="c1"># ...</span>
<span class="c1"># ...</span>
<span class="nd">@route</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^amp/$'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">amp</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="n">context</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_context</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">TemplateResponse</span><span class="p">(</span>
<span class="n">request</span><span class="p">,</span> <span class="s1">'blog/blog_page_amp.html'</span><span class="p">,</span> <span class="n">context</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
<span class="c1"># ...</span>
</code></pre></div>
<p>In the above example, we get the Blog Page object context and use it in the AMP template of a blog page. So, if a blog page has a URL <a href="https://wagtail.io/blog/wagtail-space-usa/">https://wagtail.io/blog/wagtail-space-usa/</a>. AMP version will exist at https://wagtail.io/blog/wagtail-space-usa/<strong>amp/</strong></p>
<h4>Template requirements</h4>
<p>Normal page template (<a href="https://github.com/Parbhat/wagtail-amp/blob/master/blog/templates/blog/blog_page.html#L8">blog_page.html</a>) should include this Meta tag. It helps Google know the location of AMP version of this page.</p>
<div class="highlight"><pre><span></span><code>{% load wagtailroutablepage_tags %}
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"amphtml"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ request.site.root_url }}{% routablepageurl page 'amp' %}"</span> <span class="p">/></span>
</code></pre></div>
<p>AMP template (<a href="https://github.com/Parbhat/wagtail-amp/blob/master/blog/templates/blog/blog_page_amp.html#L5">blog_page_amp.html</a>) should include</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"canonical"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ page.full_url }}"</span><span class="p">/></span>
</code></pre></div>
<h3>URL 2: AMP keyword after site root URL</h3>
<p>Using the first approach is sufficient in most cases. In some custom Django projects, this approach will be useful if the non Wagtail powered AMP pages also use amp keyword after root URL. So, we need to maintain consistency in the URL pattern of Wagtail powered and other URLs. We have to customize the Wagtail serving mechanism. First, we will understand the Wagtail serving mechanism and then work on the customization.</p>
<h4>Wagtail Serving mechanism:</h4>
<ol>
<li>
<p>When a request is made, it is passed to Wagtail <a href="https://github.com/wagtail/wagtail/blob/stable/2.1.x/wagtail/core/views.py#L10">serve</a> view.</p>
</li>
<li>
<p>Serve view creates path components list for the requested path. For example, if a request is made for URL <a href="https://wagtail.io/blog/graphql-with-streamfield/">https://wagtail.io/blog/graphql-with-streamfield/</a>. Path components list will be</p>
<div class="highlight"><pre><span></span><code><span class="n">path_components</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'blog'</span><span class="p">,</span> <span class="s1">'graphql-with-streamfield'</span><span class="p">]</span>
</code></pre></div>
</li>
<li>
<p>Then current site's root page <a href="https://github.com/wagtail/wagtail/blob/stable/2.1.x/wagtail/core/models.py#L600">route</a> method is called with <em>path_components</em>. In demo project, root page is <a href="https://github.com/Parbhat/wagtail-amp/blob/master/home/models.py#L9">home.HomePage</a>.</p>
</li>
<li>
<p>Wagtail checks the page existence by slug one by one from the <em>path_components</em> list. For example, it first checks the root page (HomePage) has a child with <em>slug: blog</em>. If a page is found, the <em>route</em> method of the blog index page is called with remaining path components. In case a page is not found with a slug, 404 error is raised.</p>
</li>
<li>
<p>The process continues and takes a slug from the <em>path_components</em> list until it becomes empty. And if a page is live it is returned to serve view.</p>
</li>
<li>
<p>In serve view, the serve method of the page object is called.</p>
</li>
</ol>
<p>Wagtail docs also have a great explanation on <a href="http://docs.wagtail.io/en/v2.1.1/reference/pages/theory.html#anatomy-of-a-wagtail-request">Anatomy of a Wagtail Request</a>.</p>
<h4>Changes in the Serving mechanism for AMP</h4>
<p>As the AMP version of Wagtail page should be served from https://wagtail.io/amp/<strong>*</strong>, some changes are required in above workflow:</p>
<ol>
<li>
<p>As the HomePage <em>route</em> method is first called. We can have a flag (<em>is_amp_request</em>) which helps to determine the request is for the AMP page. If the first item in the <em>path_components</em> list is <em>amp</em>, we set the Flag on and reduce the list to <em>path_components[1:]</em>. So, we can check page with remaining path components exists. For example, if the request is made for <strong>https://wagtail.io/amp/blog/graphql-with-streamfield/</strong></p>
<p>Wagtail creates path components list in serve view.</p>
<div class="highlight"><pre><span></span><code><span class="n">path_components</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'amp'</span><span class="p">,</span> <span class="s1">'blog'</span><span class="p">,</span> <span class="s1">'graphql-with-streamfield'</span><span class="p">]</span>
</code></pre></div>
<p>In HomePage <a href="https://github.com/Parbhat/wagtail-amp/blob/master/home/models.py#L18">route</a> method, it is changed to</p>
<div class="highlight"><pre><span></span><code><span class="n">path_components</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'blog'</span><span class="p">,</span> <span class="s1">'graphql-with-streamfield'</span><span class="p">]</span>
</code></pre></div>
<p>Overriding route method</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">HomePage</span><span class="p">(</span><span class="n">Page</span><span class="p">):</span>
<span class="n">body</span> <span class="o">=</span> <span class="n">RichTextField</span><span class="p">(</span><span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">route</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">path_components</span><span class="p">):</span>
<span class="c1"># Check the request is for AMP page</span>
<span class="n">is_amp_request</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">if</span> <span class="n">path_components</span> <span class="ow">and</span> <span class="n">path_components</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'amp'</span><span class="p">:</span>
<span class="n">is_amp_request</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Remove the amp from path components to check if the page exist</span>
<span class="n">path_components</span> <span class="o">=</span> <span class="n">path_components</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="n">page</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">HomePage</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">route</span><span class="p">(</span>
<span class="n">request</span><span class="p">,</span> <span class="n">path_components</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">is_amp_request</span><span class="p">:</span>
<span class="c1"># If the page has amp template serve it otherwise raise 404</span>
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">page</span><span class="p">,</span> <span class="s1">'get_template_amp'</span><span class="p">):</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">'is_amp_request'</span><span class="p">]</span> <span class="o">=</span> <span class="n">is_amp_request</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">Http404</span>
<span class="k">return</span> <span class="n">page</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span>
<span class="n">content_panels</span> <span class="o">=</span> <span class="n">Page</span><span class="o">.</span><span class="n">content_panels</span> <span class="o">+</span> <span class="p">[</span>
<span class="n">FieldPanel</span><span class="p">(</span><span class="s1">'body'</span><span class="p">,</span> <span class="n">classname</span><span class="o">=</span><span class="s2">"full"</span><span class="p">),</span>
<span class="p">]</span>
<span class="n">parent_page_types</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'wagtailcore.Page'</span><span class="p">]</span>
</code></pre></div>
<ul>
<li>
<p>If the page exists in Wagtail with the remaining <em>path_components</em>, we check the page has an AMP version by method <em>get_template_amp</em>. If no, we raise 404 error as the AMP version for the page not exists. If yes, we add a kwarg argument <em>kwargs['is_amp_request'] = is_amp_request</em>.</p>
</li>
<li>
<p>The page object, args and kwargs are returned to Wagtail Serve view from the root page's route method.</p>
</li>
</ul>
</li>
<li>
<p>In Wagtail serve view, page's <em>serve</em> method is called with <em>args, kwargs</em>. In the case of a blog page, we customised the Serve method to handle AMP requests. Using the kwarg argument <em>is_amp_request</em>, we return the AMP version of the blog page.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">AmpBlogPage</span><span class="p">(</span><span class="n">Page</span><span class="p">):</span>
<span class="c1"># Complete code: https://github.com/Parbhat/wagtail-amp/blob/master/blog/models.py#L85</span>
<span class="c1"># ...</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">get_template_amp</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="s1">'blog/amp_blog_page_amp.html'</span>
<span class="k">def</span> <span class="nf">serve</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">is_amp_request</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'is_amp_request'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_amp_request</span><span class="p">:</span>
<span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s1">'is_amp_request'</span><span class="p">)</span>
<span class="n">context</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_context</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">TemplateResponse</span><span class="p">(</span>
<span class="n">request</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">get_template_amp</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">),</span>
<span class="n">context</span>
<span class="p">)</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">AmpBlogPage</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">serve</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="c1"># ...</span>
</code></pre></div>
</li>
</ol>
<h2>What's Next</h2>
<p>Part 1 of this series list two options to choose a URL for AMP version. The focus of next parts in the series will be</p>
<ul>
<li>
<p><a href="https://parbhatpuri.com/amplify-wagtail-django-site-validation-part-2">Part 2 on Validation</a>: AMP spec has some restriction on the elements like <em>img</em> tag. AMP page should pass Validation to get served from Google AMP cache. In the next part, we will work on creating Valid Wagtail powered AMP pages.</p>
</li>
<li>
<p><strong>Part 3 on CORS</strong>: When using AMP components like <a href="https://ampbyexample.com/components/amp-list/">amp-list</a>, the server should include CORS headers in the response. It is not specific to Wagtail and applies to any Django project.</p>
</li>
</ul>
<p>You can also follow the Github <a href="https://github.com/Parbhat/wagtail-amp">repo</a>. AMP-lify!</p>An introduction to data migrations in Django2018-05-24T20:00:00+05:302018-05-24T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2018-05-24:/introduction-data-migrations-django.html<p>Learn to use Data migrations in Django using an example</p><p>Migrations are one of the great features that come out of the box with Django. It automates the process of changing database after modifications in the models. With few simple commands, model changes will reflect in the database. For example, In a model named <em>Image</em>, we add a new field called <em>file_size</em>. The database schema will change after running some simple commands like <em>makemigrations</em> and <em>migrate</em>. We have changed the database schema without touching the database.</p>
<p>Migrations are of two types:</p>
<ol>
<li>
<p><strong>Schema migrations</strong>: It is the most commonly used and often referred as migrations. These help to change database schema like creating database tables, adding new fields to tables and changing fields etc. These are generated automatically by Django.</p>
</li>
<li>
<p><strong>Data migrations</strong>: As the name suggests, migrations that alter data are called Data migrations. Django can not automatically generate data migrations, as it depends on your use case. Some examples where data migrations can be useful are:</p>
<ul>
<li>
<p>We add a new field named <em>full_name</em> to our <em>User</em> model. We use the schema migration which will add the field to the User database table. User model already has fields like <em>first_name</em> and <em>last_name</em>. Our production database has more than a thousand users. How will we populate the <em>full_name</em> for these users? Data migration will help here. We can write data migration to combine the first_name and last_name then the result can be saved to full_name.</p>
</li>
<li>
<p>Our project uses a third party Django package which provides an option to use a custom model. Initially, we do not require the custom model. But later we want to use the custom model to add new fields. As we are using the package in production, there is data stored in the old model. We have to migrate data from the old model to new model. Data migration can be created to accomplish this.</p>
</li>
</ul>
</li>
</ol>
<h3>Learning by example</h3>
<p>Hope you understand what data migrations are and when to use them. So, let's write a data migration. Suppose we have a model in our project like</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">django.utils.translation</span> <span class="kn">import</span> <span class="n">ugettext_lazy</span> <span class="k">as</span> <span class="n">_</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">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="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="n">_</span><span class="p">(</span><span class="s1">'title'</span><span class="p">))</span>
<span class="n">file</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">verbose_name</span><span class="o">=</span><span class="n">_</span><span class="p">(</span><span class="s1">'file'</span><span class="p">),</span> <span class="n">upload_to</span><span class="o">=</span><span class="s1">'uploads/'</span><span class="p">,</span>
<span class="n">width_field</span><span class="o">=</span><span class="s1">'width'</span><span class="p">,</span> <span class="n">height_field</span><span class="o">=</span><span class="s1">'height'</span>
<span class="p">)</span>
<span class="n">width</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">IntegerField</span><span class="p">(</span><span class="n">verbose_name</span><span class="o">=</span><span class="n">_</span><span class="p">(</span><span class="s1">'width'</span><span class="p">),</span> <span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="n">height</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">IntegerField</span><span class="p">(</span><span class="n">verbose_name</span><span class="o">=</span><span class="n">_</span><span class="p">(</span><span class="s1">'height'</span><span class="p">),</span> <span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="n">created_at</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateTimeField</span><span class="p">(</span>
<span class="n">verbose_name</span><span class="o">=</span><span class="n">_</span><span class="p">(</span><span class="s1">'created at'</span><span class="p">),</span> <span class="n">auto_now_add</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">db_index</span><span class="o">=</span><span class="kc">True</span>
<span class="p">)</span>
<span class="k">def</span> <span class="fm">__str__</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">title</span>
</code></pre></div>
<p>The <em>Image</em> model has fields like title, file, width, height and created_at. The file is an <strong>ImageField</strong> which has a method called <strong>size</strong>. The size <a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.fields.files.FieldFile.size">method</a> returns the total size of the image in bytes. Instead, to call this method when we need to get the image size, we can add a new field called <strong>file_size</strong>.</p>
<h4>Schema migration</h4>
<hr>
<p>A new line to add in above model is:</p>
<div class="highlight"><pre><span></span><code><span class="n">file_size</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">PositiveIntegerField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</code></pre></div>
<p>Now Django can automatically create a schema migration to add the field to the Image table in database.</p>
<div class="highlight"><pre><span></span><code>python manage.py makemigrations
</code></pre></div>
<p>A new migration is generated with name like <em>0002_image_file_size.py</em>.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># Generated by Django 2.0.5 on 2018-05-23 08:55</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">migrations</span><span class="p">,</span> <span class="n">models</span>
<span class="k">class</span> <span class="nc">Migration</span><span class="p">(</span><span class="n">migrations</span><span class="o">.</span><span class="n">Migration</span><span class="p">):</span>
<span class="n">dependencies</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'images'</span><span class="p">,</span> <span class="s1">'0001_initial'</span><span class="p">),</span>
<span class="p">]</span>
<span class="n">operations</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">AddField</span><span class="p">(</span>
<span class="n">model_name</span><span class="o">=</span><span class="s1">'image'</span><span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="s1">'file_size'</span><span class="p">,</span>
<span class="n">field</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">PositiveIntegerField</span><span class="p">(</span><span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">),</span>
<span class="p">),</span>
<span class="p">]</span>
</code></pre></div>
<p>The above migration uses migration operation called AddField to add a new field to the Image model. We do not have to write it. Now we have what changes we want in our database table of Image. Run the migrate command to make the changes in the database.</p>
<div class="highlight"><pre><span></span><code>python manage.py migrate
</code></pre></div>
<h4>Data migration</h4>
<hr>
<p>We have a new field called <em>file_size</em> in the Image model. Say there are more than thousand objects of the Image model. But the file_size field value for all these image objects is <strong>Null</strong>. We have to update all the current image objects with the file size. We will write a data migration to accomplish the task.</p>
<p>Create an empty migration file</p>
<div class="highlight"><pre><span></span><code><span class="err">python manage.py makemigrations --empty images</span>
</code></pre></div>
<p>Here <em>images</em> is the name of the Django app which contains the Image model.</p>
<p>Data Migration that will update the <em>file_size</em> field to image size will be</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">migrations</span>
<span class="k">def</span> <span class="nf">set_file_size</span><span class="p">(</span><span class="n">apps</span><span class="p">,</span> <span class="n">schema_editor</span><span class="p">):</span>
<span class="c1"># We can't import the Image model directly as it may be a newer</span>
<span class="c1"># version than this migration expects. We use the historical version.</span>
<span class="n">Image</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'images'</span><span class="p">,</span> <span class="s1">'Image'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">image</span> <span class="ow">in</span> <span class="n">Image</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">file_size__isnull</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">image</span><span class="o">.</span><span class="n">file_size</span> <span class="o">=</span> <span class="n">image</span><span class="o">.</span><span class="n">file</span><span class="o">.</span><span class="n">size</span>
<span class="n">image</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">update_fields</span><span class="o">=</span><span class="p">[</span><span class="s1">'file_size'</span><span class="p">])</span>
<span class="k">except</span> <span class="ne">OSError</span><span class="p">:</span>
<span class="c1"># The file doesn't exist. Internally it uses os.path.getsize</span>
<span class="c1"># to get size of image. Raise OSError if the file does not exist</span>
<span class="c1"># https://docs.python.org/3/library/os.path.html#os.path.getsize</span>
<span class="k">pass</span>
<span class="k">def</span> <span class="nf">do_nothing</span><span class="p">(</span><span class="n">apps</span><span class="p">,</span> <span class="n">schema_editor</span><span class="p">):</span>
<span class="c1"># The reverse function is not required here as we do not</span>
<span class="c1"># want to set the file size to Null again.</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">Migration</span><span class="p">(</span><span class="n">migrations</span><span class="o">.</span><span class="n">Migration</span><span class="p">):</span>
<span class="n">dependencies</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'images'</span><span class="p">,</span> <span class="s1">'0002_image_file_size'</span><span class="p">),</span>
<span class="p">]</span>
<span class="n">operations</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">RunPython</span><span class="p">(</span><span class="n">set_file_size</span><span class="p">,</span> <span class="n">do_nothing</span><span class="p">),</span>
<span class="p">]</span>
</code></pre></div>
<p>Run the Data migration like the schema migration.</p>
<div class="highlight"><pre><span></span><code>python manage.py migrate
</code></pre></div>
<ul>
<li>
<p>Schema migration has an operation called <em>AddField</em> which adds a field to the database table. Similarly, we use <strong>RunPython</strong> operation in data migration to run custom Python code. We pass two functions called <em>set_file_size</em> and <em>do_nothing</em>. The first function is a <strong>forward function</strong> which gets called when we run migrate command. The second function is necessary if we want to rollback a migration.</p>
</li>
<li>
<p>The <em>set_file_size</em> function gets all the Image objects which have the <em>Null</em> value of <em>file_size</em>. Then set its value to file size and save to the database.</p>
</li>
<li>
<p>Here we have empty reverse function called <em>do_nothing</em> because we do not want to set the <em>file_size</em> to <em>Null</em> again. The do_nothing function will help to roll back the migration.</p>
<p>We can roll back the data migration by</p>
<div class="highlight"><pre><span></span><code>./manage.py migrate images 0002_image_file_size
</code></pre></div>
<p><em>0002_image_file_size</em> is the schema migration we created before the data migration. we can use the <em>showmigrations</em> command to see applied and unapplied migrations.</p>
<div class="highlight"><pre><span></span><code>python manage.py showmigrations images
images
<span class="o">[</span>X<span class="o">]</span> 0001_initial
<span class="o">[</span>X<span class="o">]</span> 0002_image_file_size
<span class="o">[</span> <span class="o">]</span> 0003_auto_20180523_0907
</code></pre></div>
<p>We can again apply the migration using <em>migrate</em> command. We can also change the data migration name <em>0003_auto_20180523_0907</em> to meaningful like <em>0003_data_migration_image_set_file_size</em>.</p>
</li>
<li>
<p>Data migrations should not be used to load test data. As migrations will run into the production database as well. We do not want test data in the production database. Fixtures can be used to load test data.</p>
</li>
</ul>
<p>Hope the example is useful. In some cases, we have to write the reverse function as well. Simply it reverses the work performed by the forward function. Another example which migrates data from one model to other model is in the blog post. <a href="https://parbhatpuri.com/switch-to-custom-image-model-in-a-production-wagtail-project.html">Switch to Custom image model in a Production Wagtail project</a>.</p>
<p>Thanks for reading.</p>
<p>Update:</p>
<p>We can also use <code>migrations.RunPython.noop</code> as mentioned in official <a href="https://docs.djangoproject.com/en/stable/howto/writing-migrations/">docs</a> instead of <code>do_nothing</code> function above. Thanks to Blair Gemmer for suggesting it in comments.</p>Auto Select collection in Image Upload form of Wagtail2018-05-12T20:00:00+05:302018-05-12T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2018-05-12:/auto-select-collection-in-image-upload-form-of-wagtail.html<p>This guide will list the steps to auto select collection in Image Upload form while editing a Wagtail Page.</p><p>Wagtail has some great features for Images like Automatic feature detection using OpenCV, custom cropping, Multiple image upload and organising images into collections. This blog post requires some basic understanding of Wagtail. If you are new to Wagtail, you can get started with this <a href="http://docs.wagtail.io/en/stable/getting_started/tutorial.html">tutorial</a>.</p>
<h2>Feature description</h2>
<p>Images in Wagtail can be organised into collections. Collections can be created in Wagtail admin from <em>Settings > Collections</em> menu item. In an example Wagtail site, there are two collections namely <em>Blog</em> and <em>Photo Gallery</em>. On image upload form the user has the option to upload an image into a particular collection. This helps to organise the images into groups like Blog. The image upload form looks like</p>
<p><img alt="Image upload form wagtail" src="https://i.imgur.com/AREjqfe.png"></p>
<p>This works great but editor can sometimes forget to select a collection while uploading images. The default selected collection is Root. It would be great if the collection is automatically selected while editing a particular page. For example, a user editing or creating blog pages should upload images to <em>blog</em> collection. Instead of the user selecting <em>blog</em> collection we can detect the type of page user is creating and automatically select the <em>blog</em> collection. This blog post will list the steps to implement this feature.</p>
<p><img alt="Demo of auto select collection in Upload form" src="https://i.imgur.com/SCIOvA4.gif"></p>
<h2>Implementation</h2>
<p>The implementation is tested using Wagtail 2.0.1 and 1.13.1. All code examples are in Wagtail 2.0. To use the code in Wagtail 1.13.1 some import lines should be changed. More details can be found in 2.0 release <a href="http://docs.wagtail.io/en/v2.0.1/releases/2.0.html#wagtail-module-path-updates">notes</a>.</p>
<ol>
<li>
<p>In above gif, <em>blog</em> Collection is automatically selected on a page of type <em>Blog Page</em>. So, we need a model to save relation between Collections and Page models. We can use the Wagtail Snippets feature to make relation model editable in Wagtail admin.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">wagtail.core.models</span> <span class="kn">import</span> <span class="n">get_page_models</span>
<span class="kn">from</span> <span class="nn">wagtail.snippets.models</span> <span class="kn">import</span> <span class="n">register_snippet</span>
<span class="kn">from</span> <span class="nn">wagtail.admin.edit_handlers</span> <span class="kn">import</span> <span class="n">FieldPanel</span>
<span class="nd">@register_snippet</span>
<span class="k">class</span> <span class="nc">PageRelationWithImageCollection</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">page_models</span> <span class="o">=</span> <span class="n">get_page_models</span><span class="p">()</span>
<span class="n">page_types</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">_meta</span><span class="o">.</span><span class="n">model_name</span><span class="p">,</span> <span class="n">model</span><span class="o">.</span><span class="n">get_verbose_name</span><span class="p">())</span>
<span class="k">for</span> <span class="n">model</span> <span class="ow">in</span> <span class="n">page_models</span>
<span class="p">)</span>
<span class="n">page</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="mi">255</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="n">page_types</span><span class="p">,</span> <span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">collection</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span>
<span class="s1">'wagtailcore.Collection'</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'+'</span>
<span class="p">)</span>
<span class="n">panels</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">FieldPanel</span><span class="p">(</span><span class="s1">'page'</span><span class="p">),</span>
<span class="n">FieldPanel</span><span class="p">(</span><span class="s1">'collection'</span><span class="p">),</span>
<span class="p">]</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="s2">"</span><span class="si">{0}</span><span class="s2"> related to </span><span class="si">{1}</span><span class="s2"> collection"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">page</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">collection</span><span class="o">.</span><span class="n">name</span>
<span class="p">)</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">verbose_name</span> <span class="o">=</span> <span class="s1">'Pages to Collection relation'</span>
</code></pre></div>
<ul>
<li>
<p>We use the <a href="https://github.com/wagtail/wagtail/blob/stable/2.0.x/wagtail/core/models.py#L173">get_page_models</a> function from Wagtail core to get a list of all non-abstract Page model classes.</p>
</li>
<li>
<p>Page field is <em>unique</em> as we do not want a page like <em>blogpage</em> to be related to multiple collections. Multiple pages can be related to the same collection.</p>
</li>
</ul>
</li>
<li>
<p>We have a model to store relation between Page models and collections. We need a way to get a collection while user is editing a particular page. For example, admin user editing or creating a page of type Blog Page. We need to get the collection related to the blog page. We need a view to get the collection.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">JsonResponse</span>
<span class="kn">from</span> <span class="nn">wagtail.core.models</span> <span class="kn">import</span> <span class="n">Page</span>
<span class="kn">from</span> <span class="nn">wagtail.admin.utils</span> <span class="kn">import</span> <span class="n">PermissionPolicyChecker</span>
<span class="kn">from</span> <span class="nn">wagtail.images.permissions</span> <span class="kn">import</span> <span class="n">permission_policy</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">PageRelationWithImageCollection</span>
<span class="n">permission_checker</span> <span class="o">=</span> <span class="n">PermissionPolicyChecker</span><span class="p">(</span><span class="n">permission_policy</span><span class="p">)</span>
<span class="nd">@permission_checker</span><span class="o">.</span><span class="n">require</span><span class="p">(</span><span class="s1">'add'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_collection_by_page</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="n">page</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'page'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="n">page_type</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'type'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="n">data</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">if</span> <span class="n">page</span> <span class="ow">and</span> <span class="n">page_type</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="n">page_type</span> <span class="o">==</span> <span class="s1">'id'</span><span class="p">:</span>
<span class="n">page_obj</span> <span class="o">=</span> <span class="n">Page</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="nb">id</span><span class="o">=</span><span class="n">page</span><span class="p">)</span>
<span class="n">page_modal_name</span> <span class="o">=</span> <span class="n">page_obj</span><span class="o">.</span><span class="n">specific</span><span class="o">.</span><span class="n">_meta</span><span class="o">.</span><span class="n">model_name</span>
<span class="k">elif</span> <span class="n">page_type</span> <span class="o">==</span> <span class="s1">'pagemodel'</span><span class="p">:</span>
<span class="n">page_modal_name</span> <span class="o">=</span> <span class="n">page</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="n">JsonResponse</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="n">related_collection</span> <span class="o">=</span> <span class="n">PageRelationWithImageCollection</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">page</span><span class="o">=</span><span class="n">page_modal_name</span><span class="p">)</span><span class="o">.</span><span class="n">collection</span>
<span class="n">user_collection_perm</span> <span class="o">=</span> <span class="n">permission_policy</span><span class="o">.</span><span class="n">_check_perm</span><span class="p">(</span>
<span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="p">,</span> <span class="p">[</span><span class="s1">'add'</span><span class="p">],</span> <span class="n">related_collection</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">user_collection_perm</span><span class="p">:</span>
<span class="n">data</span><span class="p">[</span><span class="s1">'collection'</span><span class="p">]</span> <span class="o">=</span> <span class="n">related_collection</span><span class="o">.</span><span class="n">id</span>
<span class="k">except</span> <span class="p">(</span>
<span class="n">Page</span><span class="o">.</span><span class="n">DoesNotExist</span><span class="p">,</span> <span class="n">PageRelationWithImageCollection</span><span class="o">.</span><span class="n">DoesNotExist</span>
<span class="p">):</span>
<span class="k">pass</span>
<span class="k">return</span> <span class="n">JsonResponse</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div>
<ul>
<li>
<p>The view will be accessible to admin users who have <em>add</em> permission for images. The collection will be returned in the response only if the user has <em>add</em> permission for the related collection. For example, the user belongs to Teachers Group and only has permission for teachers collection. If the related collection is students we will not add it to the response as the requesting user is not having access.</p>
</li>
<li>
<p>The view will return a JSON response as we will later call this view with an Ajax request.</p>
</li>
</ul>
<p>Add a URL for this view to your project.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="o"><</span><span class="n">CHANGE</span><span class="o">-</span><span class="n">ME</span><span class="p">(</span><span class="n">app</span><span class="o">-</span><span class="n">name</span><span class="p">)</span><span class="o">>.</span><span class="n">views</span> <span class="kn">import</span> <span class="nn">get_collection_by_page</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">...</span>
<span class="c1"># Other Urls</span>
<span class="n">url</span><span class="p">(</span>
<span class="sa">r</span><span class="s1">'^admin/ajax/get_collection/$'</span><span class="p">,</span> <span class="n">get_collection_by_page</span><span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="s1">'get_collection_by_page'</span>
<span class="p">),</span>
<span class="o">...</span>
<span class="c1"># Other Urls</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">''</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtail_urls</span><span class="p">)),</span>
<span class="p">]</span>
</code></pre></div>
</li>
<li>
<p>This is the last step. We have created a Model snippet and View. Now the question is where we will make an Ajax request while a user is editing a Page. The Image chooser panel which opens a modal work on the
<a href="https://github.com/wagtail/wagtail/blob/stable/2.0.x/wagtail/admin/modal_workflow.py#L7">render_modal_workflow</a>. Simply when you click to choose an image in the page editor, a request is made to the server which returns the rendered <em>HTML</em> and <em>JS</em> chunk. The HTML creates the modal in the page editor and JS is executed. The <em>HTML</em> is rendered from Django template: <em>templates/wagtailimages/chooser/chooser.html</em> and <em>JS</em> from <em>templates/wagtailimages/chooser/chooser.js</em>.</p>
<p>We need to override the <em>wagtailimages/chooser/chooser.js</em> so that we can add the Javascript code for AJAX request. You can take the code from Wagtail <a href="https://github.com/wagtail/wagtail/blob/stable/2.0.x/wagtail/images/templates/wagtailimages/chooser/chooser.js">chooser.js</a> and add the custom code. We can not use the <em>insert_editor_js</em> hook to add our JS code because the modal is changed every time user click to choose an image.</p>
<h4>Overriding template</h4>
<p>You can either put the <em>chooser.js</em> template in your project's templates directory or in an application's templates directory. For example, you can place the template in your project utils app like <em>utils/templates/wagtailimages/chooser/chooser.js</em>. Also, make sure your <em>utils</em> app is placed before <em>wagtail.images</em> app in <em>INSTALLED_APPS</em> setting. You can also read Django <a href="https://docs.djangoproject.com/en/2.0/howto/overriding-templates/">Overriding templates</a> documentation.</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span><span class="o">%</span> <span class="nx">load</span> <span class="nx">i18n</span> <span class="o">%</span><span class="p">}</span>
<span class="kd">function</span><span class="p">(</span><span class="nx">modal</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">searchUrl</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'form.image-search'</span><span class="p">,</span> <span class="nx">modal</span><span class="p">.</span><span class="nx">body</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s1">'action'</span><span class="p">);</span>
<span class="cm">/* currentTag stores the tag currently being filtered on, so that we can</span>
<span class="cm"> preserve this when paginating */</span>
<span class="kd">var</span> <span class="nx">currentTag</span><span class="p">;</span>
<span class="c1">//************* Custom Code**************//</span>
<span class="c1">// Overriding the chooser.js so that we can autoselect</span>
<span class="c1">// the image collection based on Page type. Wagtail Image</span>
<span class="c1">// chooser works on render modal workflow.</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">url</span> <span class="s1">'get_collection_by_page'</span> <span class="nx">as</span> <span class="nx">get_collection_url</span> <span class="o">%</span><span class="p">}</span>
<span class="kd">var</span> <span class="nx">pathArray</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">'/'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">edit_word_index</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">inArray</span><span class="p">(</span><span class="s1">'edit'</span><span class="p">,</span> <span class="nx">pathArray</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">add_word_index</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">inArray</span><span class="p">(</span><span class="s1">'add'</span><span class="p">,</span> <span class="nx">pathArray</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">pages_word_index</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">inArray</span><span class="p">(</span><span class="s1">'pages'</span><span class="p">,</span> <span class="nx">pathArray</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">data_dict</span> <span class="o">=</span> <span class="p">{};</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">pages_word_index</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span> <span class="o">&&</span> <span class="p">(</span><span class="nx">edit_word_index</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span> <span class="o">||</span> <span class="nx">add_word_index</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">edit_word_index</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">data_dict</span><span class="p">[</span><span class="s1">'page'</span><span class="p">]</span> <span class="o">=</span> <span class="nx">pathArray</span><span class="p">[</span><span class="nx">edit_word_index</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span>
<span class="nx">data_dict</span><span class="p">[</span><span class="s1">'type'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'id'</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">add_word_index</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">data_dict</span><span class="p">[</span><span class="s1">'page'</span><span class="p">]</span> <span class="o">=</span> <span class="nx">pathArray</span><span class="p">[</span><span class="nx">add_word_index</span> <span class="o">+</span> <span class="mi">2</span><span class="p">];</span>
<span class="nx">data_dict</span><span class="p">[</span><span class="s1">'type'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'pagemodel'</span>
<span class="p">};</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="nx">url</span><span class="o">:</span> <span class="s2">"{{ get_collection_url }}"</span><span class="p">,</span>
<span class="nx">data</span><span class="o">:</span> <span class="nx">data_dict</span><span class="p">,</span>
<span class="nx">dataType</span><span class="o">:</span> <span class="s1">'json'</span><span class="p">,</span>
<span class="nx">success</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">collection</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="s2">"#id_collection option[value='"</span> <span class="o">+</span> <span class="nx">data</span><span class="p">.</span><span class="nx">collection</span> <span class="o">+</span> <span class="s2">"']"</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">"selected"</span><span class="p">,</span><span class="s2">"selected"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="c1">//************** End Custom Code************//</span>
<span class="kd">function</span> <span class="nx">ajaxifyLinks</span> <span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'.listing a'</span><span class="p">,</span> <span class="nx">context</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">modal</span><span class="p">.</span><span class="nx">loadUrl</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">});</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'.pagination a'</span><span class="p">,</span> <span class="nx">context</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">page</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="s2">"data-page"</span><span class="p">);</span>
<span class="nx">setPage</span><span class="p">(</span><span class="nx">page</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="p">...</span>
<span class="p">...</span>
<span class="c1">// COMPLETE IT FROM ORIGINAL CHOOSER.JS</span>
<span class="c1">// https://github.com/wagtail/wagtail/blob/stable/2.0.x/wagtail/images/templates/wagtailimages/chooser/chooser.js</span>
<span class="p">}</span>
</code></pre></div>
<ul>
<li>Ajax request is created with two parameters <em>page</em> and <em>type</em>. The reason is to detect the page that is currently being edited/created we use the URL path. The URL when a page is edited contain the page id so we send the request with page id with type id. But when a new page is created we have the <em>Page modal name</em> in the URL. So, we create request with page model name and type <em>pagemodel</em>.</li>
</ul>
</li>
</ol>
<h4>The steps can be summed up into:</h4>
<ol>
<li>
<p>Admin User clicks to choose an image, a request is sent to the server which returns HTML and JS chunk. HTML chunk will create the modal.</p>
</li>
<li>
<p>We have overridden the <em>chooser.js</em> template so it will create an AJAX request to get the related collection.</p>
</li>
<li>
<p>If a collection is returned, it will be automatically selected in the Upload form.</p>
</li>
</ol>
<p>Thanks for reading.</p>A 3-year journey of an Indian remote Software developer2018-04-28T20:00:00+05:302018-04-28T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2018-04-28:/a-3-year-journey-of-an-indian-remote-software-developer.html<p>Things I learned by working remotely for three years.</p><p>On 13th April 2015 sprints started at <a href="https://us.pycon.org/2015/community/sprints/">Pycon US</a> and also my remote internship. I was not attending Pycon US but working remotely from my college hostel. Like every Indian engineering student, I was looking for an internship in my third year of B.tech. Got a positive response from one company but they want it to be remote. Remote sounds good to me and I decided to give it a shot. This remote internship was the first step in my career. My 3-year journey can be divided into:</p>
<ol>
<li>
<p><strong>Remote Intern</strong>: I applied for the internship in february 2015. After clearing the interview, the internship was scheduled for May - July during my summer vacation. Then I realised why not start the internship now as it is remote. I requested my mentor and he was happy to start the internship during his visit to Pycon US. The internship was supposed to end in July but then I again realised why not continue the internship as it is remote. My mentor was happy to continue it. This is the real benefit of remote working.</p>
</li>
<li>
<p><strong>Remote Software Developer (part-time) + Freelancer</strong>: After completing the college, the title changed to Remote Software Developer. One benefit of remote work I like the most is you can work with different companies across the globe. So, I decided to keep working with my previous company on a part-time basis and dive into the world of freelancing. Freelancing sounds cool but it takes time to get some initial clients. I have worked with companies based in the USA and Europe.</p>
</li>
<li>
<p><strong>Fulltime Freelance Software Developer</strong>: After having an initial taste of freelancing, I am now working as a Full-time Freelance Software Developer. This has its own pros and cons. Like no fixed monthly income with the benefit of no limit to the income, you can earn.</p>
</li>
</ol>
<p><center>
<img alt="A macbook laptop, diary, pen and phone on a table." src="https://i.imgur.com/6Z7zCrp.jpg"></p>
<p>Image Source: Pixabay
</center></p>
<h3>Remote work Mantra</h3>
<p><em>Communicate</em> early <em>Communicate</em> often - repeat it three times.</p>
<p>Not sure if this is the good way to emphasize the importance of communication in remote work. But communication plays an important role. No matter how good you are as a programmer. If you can not understand the project requirements and communicate with your remote team you can not succeed in the remote world. Some points specific to communication are:</p>
<ol>
<li>
<p><strong>Communicate early</strong>: Great, you got your first project. You received the feature document but wait some things are not clear to you. Early communication is necessary so you do not go in a different direction. Jump to your team communication tool like Slack and discuss early.</p>
</li>
<li>
<p><strong>Communicate often</strong>: You have started the feature development work but you are not sure about the coding approach. Again discuss with your team and continue work.</p>
</li>
<li>
<p><strong>Read twice</strong>: Please read the message again that you just typed before hitting send button. Typos are common but sometimes the meaning from your text can be different from what you want to convey. So, read it twice.</p>
</li>
</ol>
<h3>Some learnings and observations</h3>
<ol>
<li>
<p><strong>Contribute to Open Source projects</strong>: Open Source work is mostly remote work. Open Source projects have a range of contributors working from different time zones. Open Source teaches you important skills like practical coding skills, communication etc. These skills will help you a lot when applying for remote work and day to day activities.</p>
</li>
<li>
<p><strong>Do not commit if you cannot deliver (Learn to say No)</strong>: In freelancing, you may get requests for new work when you are fully occupied with work. Do not make false promises to work as you know you will not be able to work with your current schedule. Again comes the communication Mantra. Discuss with the client about your availability. If the work is not urgent, the client will be happy to wait for your availability.</p>
</li>
<li>
<p><strong>Have a work schedule and not be available 24 X 7</strong>: People working remotely have a hard time separating work from their personal lives. In case of onsite office work, developers coming out of office close their working day and will again continue next day. Creating such a boundary line in remote work is harder as there is no one to tell you to stop. So, create a work schedule depending on the type of person you are. I am a morning person and do not like to work late at night. So, I work mostly in the daytime. I have clients in vary timezones sometimes with a time difference of 9 hours and 30 minutes. So, work completed by me in the daytime can be reviewed and deployed in my client's daytime. I respond to questions using my phone and jump to laptop only in case of urgent work. So, create a schedule that suits you, discuss it with your clients and do not be available 24 X 7.</p>
</li>
<li>
<p><strong>Your work is your attendance</strong>: In case of traditional office setup, a developer presence marks the attendance. But when you are working remotely, your attendance is marked by the commits you push and communication you take part in.</p>
</li>
<li>
<p><strong>Identifying bad clients</strong>: There is no full proof method to identify bad clients but I look for some hints in my conversation with clients. Some clients use words like <em>it is very easy</em> again and again. They use it again and again. With no technical perspective, they make the assumption. One reason may be they are in the search of cheap labour. Better is to avoid these clients to have a peace of mind.</p>
</li>
<li>
<p><strong>Do not start with freelancing as a beginner</strong>: Good companies often hire remote freelance developers because they have a limited in-house team or do not have experience with a particular technology. So, they only hire the experienced developers. As a beginner, it is better to start contributing to Open Source projects and find a remote internship.</p>
</li>
<li>
<p><strong>Do not depend upon freelance marketplaces</strong>: Platforms like Upwork are great to start but do not depend upon it entirely. Create a portfolio of projects and apply to companies directly you want to work with. Create your online presence so clients can contact you directly.</p>
</li>
<li>
<p><strong>Exercise and eat healthy food</strong>: We developers have to admit our work is not friendly to our health. Sitting long hours in front of a screen cannot be healthy. So, develop the habit of exercise each day and eat healthy food.</p>
</li>
</ol>
<h3>There are two hard things in freelancing: calculating hourly rates and project estimation</h3>
<p>A project specification that does not change is very rare. So, working on a project basis makes it harder to have future change requests. Changes in the specification mean you have to estimate the project price again. When you are working alone, this makes it even more problematic. As a freelancer, it is better to work on hourly basis.</p>
<p>Solving one problem creates another problem - Calculating hourly rates.</p>
<p>Hourly rates depend on a number of factors like your skills, location etc. In theory, your skills should be the only important factor. But your location also matters. Like the cost of living is very different in the USA than India. So, perform some research on the market value of your skills. And have two hourly rates depending upon commitment duration. For short-term commitment, you should charge high. You can give a discount for long-term commitments.</p>
<p>You are still here. Thanks for reading my journey. If you have some specific questions, feel free to ask it in the comments below. Happy remote working.</p>Switch to Custom image model in a Production Wagtail project2018-01-22T20:00:00+05:302018-01-22T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2018-01-22:/switch-to-custom-image-model-in-a-production-wagtail-project.html<p>This guide will list the steps to switch from Wagtail Default image model to Custom image model in a production Wagtail project.</p><p>A software project is always evolving. New requirements can arise at any stage of the project. One example of a new feature required in a production Wagtail project is to store the <code>source</code> of the Image. This post will list the steps involved to make the migration from Wagtail default Image model to Custom Image model. Therefore it requires some basic understanding of Wagtail. If you heard about the Wagtail for the first time. You should check it at <a href="http://wagtail.io/">wagtail.io</a>.</p>
<h3>Brief description of the feature</h3>
<p>Wagtail has a wonderful admin which also allows handling images. Internally wagtail has <code>wagtailimages</code> app which contains the default Image model. But the fields provided in the default Image model may not be sufficient for your project. Like we want to store the <code>source</code> of every image and display on our site. Source field is not present in Image model. Fortunately, Wagtail allows switching to custom Image model in which new fields can be added based on the project requirements. In our case, we want to add the source field.</p>
<h3>Implementation</h3>
<p>Like Django, Wagtail also has a great documentation. It also has documentation on the <a href="http://docs.wagtail.io/en/stable/advanced_topics/images/custom_image_model.html">Custom image models</a>. After following the docs, our project will be switched to Custom Image model with new fields like source. But when we open Images section in Wagtail admin, it will be empty. The reason is mentioned in docs:</p>
<p>When changing an existing site to use a custom image model, no images will be copied to the new model automatically. Copying old images to the new model would need to be done manually with a data migration.</p>
<p>So, how much progress we have made?</p>
<ul>
<li>Switched to custom image model - Yes</li>
<li>Migrated old data from default image model to Custom image model - No</li>
</ul>
<h4>What are data migrations?</h4>
<p>There are basically two types of migrations in a Django project.</p>
<ul>
<li>
<p><strong>Schema migrations</strong>: It is the most commonly used and often referred as migrations. These help to change database schema like creating database tables, adding new fields to tables and changing fields etc.</p>
</li>
<li>
<p><strong>Data migrations</strong>: As the name suggests, migrations that alter data are called Data migrations. For example, we add a new field named <code>full_name</code> to the User model. Schema migrations will create the new field in the database. Data migrations help to combine first_name and last_name of the existing users and store it in the new field named full_name. You can read more in the Django <a href="https://docs.djangoproject.com/en/2.0/topics/migrations/#data-migrations">documentation</a>.</p>
</li>
</ul>
<h4>Why we need Data migrations to switch to Custom Image model?</h4>
<p>In our Wagtail project, we changed the Image model. We created the new model but the image records data is present in old default Image model. To copy images from old to the new model we need to create a data migration which copies the image records. To create data migration:</p>
<ul>
<li>Make an empty migration file</li>
</ul>
<div class="highlight"><pre><span></span><code>python manage.py makemigrations --empty yourappname
</code></pre></div>
<ul>
<li>Copy the following code to the generated file.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">migrations</span><span class="p">,</span> <span class="n">models</span>
<span class="k">def</span> <span class="nf">forwards_func</span><span class="p">(</span><span class="n">apps</span><span class="p">,</span> <span class="n">schema_editor</span><span class="p">):</span>
<span class="c1"># We get the model from the versioned app registry;</span>
<span class="n">wagtail_image_model</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'wagtailimages'</span><span class="p">,</span> <span class="s1">'Image'</span><span class="p">)</span>
<span class="c1"># Change the pages with your project's app name and also model name if different</span>
<span class="n">extended_image_model</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'pages'</span><span class="p">,</span> <span class="s1">'ExtendedImage'</span><span class="p">)</span>
<span class="n">tagged_item_model</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'taggit'</span><span class="p">,</span> <span class="s1">'TaggedItem'</span><span class="p">)</span>
<span class="n">django_content_type</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'contenttypes'</span><span class="p">,</span> <span class="s1">'contenttype'</span><span class="p">)</span>
<span class="n">db_alias</span> <span class="o">=</span> <span class="n">schema_editor</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">alias</span>
<span class="c1"># Get images stored in default wagtail image model</span>
<span class="n">images</span> <span class="o">=</span> <span class="n">wagtail_image_model</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">new_images</span> <span class="o">=</span> <span class="p">[]</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">new_images</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">extended_image_model</span><span class="p">(</span>
<span class="nb">id</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">id</span><span class="p">,</span>
<span class="n">title</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">title</span><span class="p">,</span>
<span class="n">file</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">file</span><span class="p">,</span>
<span class="n">width</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">width</span><span class="p">,</span>
<span class="n">height</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">height</span><span class="p">,</span>
<span class="n">created_at</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">created_at</span><span class="p">,</span>
<span class="n">focal_point_x</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">focal_point_x</span><span class="p">,</span>
<span class="n">focal_point_y</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">focal_point_y</span><span class="p">,</span>
<span class="n">focal_point_width</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">focal_point_width</span><span class="p">,</span>
<span class="n">focal_point_height</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">focal_point_height</span><span class="p">,</span>
<span class="n">file_size</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">file_size</span><span class="p">,</span>
<span class="n">collection</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">collection</span><span class="p">,</span>
<span class="n">uploaded_by_user</span><span class="o">=</span><span class="n">image</span><span class="o">.</span><span class="n">uploaded_by_user</span><span class="p">,</span>
<span class="p">))</span>
<span class="c1"># Create images in new model</span>
<span class="n">extended_image_model</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">bulk_create</span><span class="p">(</span><span class="n">new_images</span><span class="p">)</span>
<span class="c1"># Leave all images in previous model</span>
<span class="c1"># Move tags from old image to new image model. Moving tags is</span>
<span class="c1"># a little different case. The lookup table taggit_taggeditem looks like this:</span>
<span class="c1"># id object_id content_type_id tag_id</span>
<span class="c1"># 1 1 10 1</span>
<span class="c1"># 2 1 10 2</span>
<span class="c1"># 3 1 10 3</span>
<span class="c1"># 4 1 10 4</span>
<span class="c1"># In our case, the object_id will be same for old and new image model</span>
<span class="c1"># objects. So, we have to only change the content_type_id</span>
<span class="n">ct_extended_model</span><span class="p">,</span> <span class="n">created</span> <span class="o">=</span> <span class="n">django_content_type</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span>
<span class="n">app_label</span><span class="o">=</span><span class="s1">'pages'</span><span class="p">,</span>
<span class="n">model</span><span class="o">=</span><span class="s1">'extendedimage'</span>
<span class="p">)</span>
<span class="n">ct_wagtail_model</span> <span class="o">=</span> <span class="n">django_content_type</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">app_label</span><span class="o">=</span><span class="s1">'wagtailimages'</span><span class="p">,</span>
<span class="n">model</span><span class="o">=</span><span class="s1">'image'</span>
<span class="p">)</span>
<span class="n">tagged_item_model</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span>
<span class="n">content_type_id</span><span class="o">=</span><span class="n">ct_wagtail_model</span><span class="o">.</span><span class="n">id</span><span class="p">)</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
<span class="n">content_type_id</span><span class="o">=</span><span class="n">ct_extended_model</span><span class="o">.</span><span class="n">id</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">reverse_func</span><span class="p">(</span><span class="n">apps</span><span class="p">,</span> <span class="n">schema_editor</span><span class="p">):</span>
<span class="c1"># We get the model from the versioned app registry;</span>
<span class="n">extended_image_model</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'pages'</span><span class="p">,</span> <span class="s1">'ExtendedImage'</span><span class="p">)</span>
<span class="n">tagged_item_model</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'taggit'</span><span class="p">,</span> <span class="s1">'TaggedItem'</span><span class="p">)</span>
<span class="n">django_content_type</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'contenttypes'</span><span class="p">,</span> <span class="s1">'contenttype'</span><span class="p">)</span>
<span class="n">db_alias</span> <span class="o">=</span> <span class="n">schema_editor</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">alias</span>
<span class="c1"># Move tags from new image model to old wagtail model</span>
<span class="n">ct_extended_model</span> <span class="o">=</span> <span class="n">django_content_type</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">app_label</span><span class="o">=</span><span class="s1">'pages'</span><span class="p">,</span>
<span class="n">model</span><span class="o">=</span><span class="s1">'extendedimage'</span>
<span class="p">)</span>
<span class="n">ct_wagtail_model</span> <span class="o">=</span> <span class="n">django_content_type</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
<span class="n">app_label</span><span class="o">=</span><span class="s1">'wagtailimages'</span><span class="p">,</span>
<span class="n">model</span><span class="o">=</span><span class="s1">'image'</span>
<span class="p">)</span>
<span class="n">tagged_item_model</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span>
<span class="n">content_type_id</span><span class="o">=</span><span class="n">ct_extended_model</span><span class="o">.</span><span class="n">id</span><span class="p">)</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
<span class="n">content_type_id</span><span class="o">=</span><span class="n">ct_wagtail_model</span><span class="o">.</span><span class="n">id</span>
<span class="p">)</span>
<span class="c1"># Delete all images created in the new model</span>
<span class="n">extended_image_model</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()</span><span class="o">.</span><span class="n">delete</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">Migration</span><span class="p">(</span><span class="n">migrations</span><span class="o">.</span><span class="n">Migration</span><span class="p">):</span>
<span class="n">dependencies</span> <span class="o">=</span> <span class="p">[</span>
<span class="c1"># Change the following according to your project</span>
<span class="c1"># ('pages', '0021_auto_20180122_1100'),</span>
<span class="p">(</span><span class="s1">'wagtailimages'</span><span class="p">,</span> <span class="s1">'0019_delete_filter'</span><span class="p">),</span>
<span class="p">]</span>
<span class="n">operations</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">RunPython</span><span class="p">(</span><span class="n">forwards_func</span><span class="p">,</span> <span class="n">reverse_func</span><span class="p">),</span>
<span class="p">]</span>
</code></pre></div>
<h4>Details about the steps in this data migration</h4>
<ul>
<li>
<p>First, this data migration gets the default Image model objects and create the new Custom Image model objects using <a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#bulk-create">bulk_create</a>.</p>
</li>
<li>
<p><strong>Migrating Tags</strong>: Migrating tags is little different than other fields like title. Tags in wagtail work using django-taggit. So, let's use a simple example to understand the concept. This is not the actual model from Django taggit but simplified for this use case.</p>
</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">TaggedItem</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">tag</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">SlugField</span><span class="p">()</span>
<span class="n">content_type</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="n">ContentType</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">)</span>
<span class="n">object_id</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">PositiveIntegerField</span><span class="p">()</span>
<span class="n">content_object</span> <span class="o">=</span> <span class="n">GenericForeignKey</span><span class="p">(</span><span class="s1">'content_type'</span><span class="p">,</span> <span class="s1">'object_id'</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__str__</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">tag</span>
</code></pre></div>
<p>Django includes a <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/contenttypes/">contenttypes</a> app which can track all of the models installed in your Django-powered project. In this case, the Tags objects point to old Image model using content_type. So, we can filter the TaggedItem model to get the tags used in images and can then update these with the new image model content_type.</p>
<div class="highlight"><pre><span></span><code> <span class="n">tagged_item_model</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="n">db_alias</span><span class="p">)</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span>
<span class="n">content_type_id</span><span class="o">=</span><span class="n">ct_wagtail_model</span><span class="o">.</span><span class="n">id</span><span class="p">)</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
<span class="n">content_type_id</span><span class="o">=</span><span class="n">ct_extended_model</span><span class="o">.</span><span class="n">id</span>
<span class="p">)</span>
</code></pre></div>
<p>content types have various uses like Tags but diving into it further will deviate from the topic of this blog post. Will write a new post on content types.</p>
<ul>
<li>You may notice we are using <strong>get_or_create</strong> to get the content type of the new Image model. New instances of ContentType are automatically created whenever new models are installed. But in our case, we have two migrations. The first one create the Schema for new Image model and second one migrate data. You may not encounter the issue running locally but when both the migrations are run during deployment new content types may not be created. In that case, we can create the content type for the new model if not exist.</li>
</ul>
<h3>Issues with PostgreSQL</h3>
<p>One issue you will encounter if you are using PostgreSQL in your project while uploading new images:</p>
<div class="highlight"><pre><span></span><code><span class="err">psycopg2.IntegrityError: duplicate key value violates unique constraint "pages_extendedimage_pkey"</span>
<span class="c">DETAIL: Key (id)=(1) already exists.</span>
</code></pre></div>
<p>The current key is set to 1 so we have to change it to Max. If your project is using a small number of images say < 10 then you can try uploading again and again and error will be removed when the current key equal to Max. But it is not feasible in case of large number of images. So, in that case, you can run SQL query on your database like</p>
<div class="highlight"><pre><span></span><code>SELECT pg_catalog.setval<span class="o">(</span>pg_get_serial_sequence<span class="o">(</span><span class="s1">'pages_extendedimage'</span>, <span class="s1">'id'</span><span class="o">)</span>, <span class="o">(</span>SELECT MAX<span class="o">(</span>id<span class="o">)</span> FROM pages_extendedimage<span class="o">))</span><span class="p">;</span>
</code></pre></div>
<hr>
<p>I have tried to cover all the possible cases encountered by me while migrating a production site to Custom Image model. If you face any issues, feel free to comment below. Thanks for reading.</p>
<p>References:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/41931590/data-migration-of-image-model">https://stackoverflow.com/questions/41931590/data-migration-of-image-model</a></li>
</ul>Add download CSV option in Wagtail modeladmin2017-09-15T18:00:00+05:302017-09-15T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2017-09-15:/add-download-csv-option-in-wagtail-modeladmin.html<p>This guide will list the steps to add download CSV option in Wagtail modeladmin.</p><p><a href="https://wagtail.io/">Wagtail</a> is a free and open source CMS written in Python and built on the <a href="http://djangoproject.com/">Django</a> framework. If you heard about the Wagtail for the first time. You should check it out and you will love it.</p>
<p>The <a href="http://docs.wagtail.io/en/stable/reference/contrib/modeladmin/index.html">modeladmin</a> module provided by Wagtail allows to create customisable listing pages for any model in your Wagtail project. Suppose you have a model named <code>EventRegistration</code>. Django developers are used to performing CRUD operations on this model using the Django admin. The modeladmin module from Wagtail helps to perform CRUD operations from the Wagtail admin interface. Wagtail admin has a great UI and non developers using your project will love it.</p>
<p>This blog lists the steps to add download <em>CSV</em> option in Wagtail admin listing view of your model. For the Wagtail users, you may have seen the similar button in the form submissions listing page. You can follow the modeladmin <a href="http://docs.wagtail.io/en/stable/reference/contrib/modeladmin/index.html">documentation</a> to register your model and display the model in Wagtail admin. The simplest example is to add the following code in <em>wagtail_hooks.py</em> file in the app directory.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">wagtail.contrib.modeladmin.options</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">ModelAdmin</span><span class="p">,</span> <span class="n">modeladmin_register</span><span class="p">)</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">EventRegistration</span>
<span class="k">class</span> <span class="nc">EventRegistrationAdmin</span><span class="p">(</span><span class="n">ModelAdmin</span><span class="p">):</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">EventRegistration</span>
<span class="n">menu_label</span> <span class="o">=</span> <span class="s1">'Event registrations'</span> <span class="c1"># ditch this to use verbose_name_plural from model</span>
<span class="n">menu_icon</span> <span class="o">=</span> <span class="s1">'form'</span> <span class="c1"># change as required</span>
<span class="n">menu_order</span> <span class="o">=</span> <span class="mi">200</span> <span class="c1"># will put in 3rd place (000 being 1st, 100 2nd)</span>
<span class="n">add_to_settings_menu</span> <span class="o">=</span> <span class="kc">False</span> <span class="c1"># or True to add your model to the Settings sub-menu</span>
<span class="n">exclude_from_explorer</span> <span class="o">=</span> <span class="kc">False</span> <span class="c1"># or True to exclude pages of this type from Wagtail's explorer view</span>
<span class="n">list_display</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'title'</span><span class="p">,</span> <span class="s1">'example_field2'</span><span class="p">,</span> <span class="s1">'example_field3'</span><span class="p">,</span> <span class="s1">'register'</span><span class="p">)</span>
<span class="n">list_filter</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'register'</span><span class="p">,</span> <span class="s1">'example_field2'</span><span class="p">,</span> <span class="s1">'example_field3'</span><span class="p">)</span>
<span class="n">search_fields</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'title'</span><span class="p">,)</span>
<span class="c1"># Now you just need to register your customised EventRegistrationAdmin class with Wagtail</span>
<span class="n">modeladmin_register</span><span class="p">(</span><span class="n">EventRegistrationAdmin</span><span class="p">)</span>
</code></pre></div>
<p>Now you can see the <code>Event registrations</code> menu item in Wagtail admin. When you click on it, you can see the listing page which lists all the event registration entries. The top of the page will be similar to</p>
<p><img alt="listing-page-header" src="https://i.imgur.com/mgw4nBP.png"></p>
<p>To add <code>Download CSV</code> button next to search input follow the steps:</p>
<ul>
<li>The first step is to override the default IndexView to handle CSV creation at the backend and return CSV file.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">csv</span>
<span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">django.utils.encoding</span> <span class="kn">import</span> <span class="n">smart_str</span>
<span class="kn">from</span> <span class="nn">django.contrib.auth.decorators</span> <span class="kn">import</span> <span class="n">login_required</span>
<span class="kn">from</span> <span class="nn">django.utils.decorators</span> <span class="kn">import</span> <span class="n">method_decorator</span>
<span class="kn">from</span> <span class="nn">django.core.exceptions</span> <span class="kn">import</span> <span class="n">PermissionDenied</span>
<span class="kn">from</span> <span class="nn">wagtail.contrib.modeladmin.views</span> <span class="kn">import</span> <span class="n">IndexView</span>
<span class="kn">from</span> <span class="nn">wagtail.contrib.modeladmin.options</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">ModelAdmin</span><span class="p">,</span> <span class="n">modeladmin_register</span><span class="p">)</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">EventRegistration</span>
<span class="k">class</span> <span class="nc">EventRegistrationCustomIndexView</span><span class="p">(</span><span class="n">IndexView</span><span class="p">):</span>
<span class="nd">@method_decorator</span><span class="p">(</span><span class="n">login_required</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">dispatch</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="c1"># Only continue if logged in user has list permission</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">permission_helper</span><span class="o">.</span><span class="n">user_can_list</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="p">):</span>
<span class="k">raise</span> <span class="n">PermissionDenied</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'action'</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'CSV'</span><span class="p">:</span>
<span class="c1"># Get all registrations</span>
<span class="n">registrations</span> <span class="o">=</span> <span class="n">EventRegistration</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">data_headings</span> <span class="o">=</span> <span class="p">[</span><span class="n">field</span><span class="o">.</span><span class="n">verbose_name</span> <span class="k">for</span> <span class="n">field</span>
<span class="ow">in</span> <span class="n">EventRegistration</span><span class="o">.</span><span class="n">_meta</span><span class="o">.</span><span class="n">get_fields</span><span class="p">()]</span>
<span class="c1"># Get query parameters. If the user has filtered the list view.</span>
<span class="c1"># Suppose there is a boolean field named register in the model.</span>
<span class="n">registered</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'register__exact'</span><span class="p">)</span>
<span class="c1"># Filter by registered or not</span>
<span class="k">if</span> <span class="n">registered</span><span class="p">:</span>
<span class="n">registrations</span> <span class="o">=</span> <span class="n">registrations</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span>
<span class="n">register</span><span class="o">=</span><span class="n">registered</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1"># return a CSV instead</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">content_type</span><span class="o">=</span><span class="s1">'text/csv; charset=utf-8'</span><span class="p">)</span>
<span class="n">response</span><span class="p">[</span><span class="s1">'Content-Disposition'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'attachment;filename='</span> <span class="o">+</span> \
<span class="s1">'registrations.csv'</span>
<span class="c1"># Prevents UnicodeEncodeError for labels with non-ansi symbols</span>
<span class="n">data_headings</span> <span class="o">=</span> <span class="p">[</span><span class="n">smart_str</span><span class="p">(</span><span class="n">label</span><span class="p">)</span> <span class="k">for</span> <span class="n">label</span> <span class="ow">in</span> <span class="n">data_headings</span><span class="p">]</span>
<span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">writer</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
<span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">data_headings</span><span class="p">)</span>
<span class="k">for</span> <span class="n">reg</span> <span class="ow">in</span> <span class="n">registrations</span><span class="p">:</span>
<span class="n">data_row</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">data_row</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span>
<span class="n">reg</span><span class="o">.</span><span class="n">title</span><span class="p">,</span> <span class="n">reg</span><span class="o">.</span><span class="n">example_field2</span><span class="p">,</span> <span class="n">reg</span><span class="o">.</span><span class="n">example_field3</span>
<span class="p">])</span>
<span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">data_row</span><span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">EventRegistrationCustomIndexView</span><span class="p">,</span>
<span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">dispatch</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div>
<ul>
<li>Now our view can handle CSV download requests. We need to also create the new index view template like <code>event_model_admin_index.html</code> to display the <code>Download CSV</code> button.</li>
</ul>
<div class="highlight"><pre><span></span><code>{% extends "modeladmin/index.html" %}
{% load i18n modeladmin_tags %}
{% block header_extra %}
<span class="c"><!-- Download CSV --></span>
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{% if request.GET|length == 0 %}?action=CSV{% else %}{{ request.get_full_path }}&amp;action=CSV{% endif %}"</span> <span class="na">class</span><span class="o">=</span><span class="s">"button bicolor icon icon-download"</span><span class="p">></span>{% trans 'Download CSV' %}<span class="p"></</span><span class="nt">a</span><span class="p">></span>
{{ block.super }}
{% endblock %}
</code></pre></div>
<ul>
<li>Now we have the custom <strong>index view</strong> and <strong>template</strong> for the event registration. You can customize them further according to your model. We need to add the following attributes to EventRegistrationAdmin class.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">EventRegistrationAdmin</span><span class="p">(</span><span class="n">ModelAdmin</span><span class="p">):</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">EventRegistration</span>
<span class="n">index_view_class</span> <span class="o">=</span> <span class="n">EventRegistrationCustomIndexView</span>
<span class="n">index_template_name</span> <span class="o">=</span> <span class="s1">'events/event_model_admin_index.html'</span>
<span class="o">....</span>
<span class="o">....</span>
</code></pre></div>
<p>That's it. You can now see a <code>Download CSV</code> button and can download CSVs.</p>
<p><img alt="listing-page-header-csv" src="https://i.imgur.com/K3Olh6P.png"></p>
<p>Thanks for reading.</p>
<h2><strong>Update</strong>:</h2>
<p>I got a message from a Linkedin user that he liked this article and also pointed to <a href="https://stackoverflow.com/questions/46206693/adding-a-button-to-wagtail-dashboard">this</a> stackoverflow question. The answer by <code>Loïc Teixeira</code> uses an alternate approach and better than the above. The difference between the two approaches is: the above approach overrides the same index view to handle CSV logic and the below approach create a new view which is called by a different URL. So, here is the approach which uses the modeladmin <a href="http://docs.wagtail.io/en/stable/reference/contrib/modeladmin/primer.html#overriding-helper-classes">helper classes</a>.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">csv</span>
<span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">django.utils.encoding</span> <span class="kn">import</span> <span class="n">smart_str</span>
<span class="kn">from</span> <span class="nn">django.contrib.auth.decorators</span> <span class="kn">import</span> <span class="n">login_required</span>
<span class="kn">from</span> <span class="nn">django.core.urlresolvers</span> <span class="kn">import</span> <span class="n">reverse</span>
<span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span>
<span class="kn">from</span> <span class="nn">django.utils.translation</span> <span class="kn">import</span> <span class="n">ugettext</span> <span class="k">as</span> <span class="n">_</span>
<span class="kn">from</span> <span class="nn">django.utils.decorators</span> <span class="kn">import</span> <span class="n">method_decorator</span>
<span class="kn">from</span> <span class="nn">django.core.exceptions</span> <span class="kn">import</span> <span class="n">PermissionDenied</span>
<span class="kn">from</span> <span class="nn">wagtail.contrib.modeladmin.views</span> <span class="kn">import</span> <span class="n">IndexView</span>
<span class="kn">from</span> <span class="nn">wagtail.contrib.modeladmin.options</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">ModelAdmin</span><span class="p">,</span> <span class="n">modeladmin_register</span><span class="p">)</span>
<span class="kn">from</span> <span class="nn">wagtail.contrib.modeladmin.helpers</span> <span class="kn">import</span> <span class="n">AdminURLHelper</span><span class="p">,</span> <span class="n">ButtonHelper</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">EventRegistration</span>
<span class="k">class</span> <span class="nc">ExportButtonHelper</span><span class="p">(</span><span class="n">ButtonHelper</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> This helper constructs all the necessary attributes to create a button.</span>
<span class="sd"> There is a lot of boilerplate just for the classnames to be right :(</span>
<span class="sd"> """</span>
<span class="n">export_button_classnames</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'icon'</span><span class="p">,</span> <span class="s1">'icon-download'</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">export_button</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">classnames_add</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">classnames_exclude</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">if</span> <span class="n">classnames_add</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">classnames_add</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">if</span> <span class="n">classnames_exclude</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">classnames_exclude</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">classnames</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">export_button_classnames</span> <span class="o">+</span> <span class="n">classnames_add</span>
<span class="n">cn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">finalise_classname</span><span class="p">(</span><span class="n">classnames</span><span class="p">,</span> <span class="n">classnames_exclude</span><span class="p">)</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">_</span><span class="p">(</span><span class="s1">'Export </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">verbose_name_plural</span><span class="o">.</span><span class="n">title</span><span class="p">()))</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s1">'url'</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">url_helper</span><span class="o">.</span><span class="n">get_action_url</span><span class="p">(</span><span class="s1">'export'</span><span class="p">,</span> <span class="n">query_params</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="p">),</span>
<span class="s1">'label'</span><span class="p">:</span> <span class="n">text</span><span class="p">,</span>
<span class="s1">'classname'</span><span class="p">:</span> <span class="n">cn</span><span class="p">,</span>
<span class="s1">'title'</span><span class="p">:</span> <span class="n">text</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">class</span> <span class="nc">ExportAdminURLHelper</span><span class="p">(</span><span class="n">AdminURLHelper</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> This helper constructs the different urls.</span>
<span class="sd"> This is mostly just to overwrite the default behaviour</span>
<span class="sd"> which consider any action other than 'create', 'choose_parent' and 'index'</span>
<span class="sd"> as `object specific` and will try to add the object PK to the url</span>
<span class="sd"> which is not what we want for the `export` option.</span>
<span class="sd"> In addition, it appends the filters to the action.</span>
<span class="sd"> """</span>
<span class="n">non_object_specific_actions</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'create'</span><span class="p">,</span> <span class="s1">'choose_parent'</span><span class="p">,</span> <span class="s1">'index'</span><span class="p">,</span> <span class="s1">'export'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_action_url</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">action</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">query_params</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s1">'query_params'</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="n">url_name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_action_url_name</span><span class="p">(</span><span class="n">action</span><span class="p">)</span>
<span class="k">if</span> <span class="n">action</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">non_object_specific_actions</span><span class="p">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">reverse</span><span class="p">(</span><span class="n">url_name</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">reverse</span><span class="p">(</span><span class="n">url_name</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span><span class="o">=</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">if</span> <span class="n">query_params</span><span class="p">:</span>
<span class="n">url</span> <span class="o">+=</span> <span class="s1">'?</span><span class="si">{params}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="n">query_params</span><span class="o">.</span><span class="n">urlencode</span><span class="p">())</span>
<span class="k">return</span> <span class="n">url</span>
<span class="k">def</span> <span class="nf">get_action_url_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">action</span><span class="p">):</span>
<span class="k">if</span> <span class="n">action</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">non_object_specific_actions</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_action_url_pattern</span><span class="p">(</span><span class="n">action</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_object_specific_action_url_pattern</span><span class="p">(</span><span class="n">action</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">ExportView</span><span class="p">(</span><span class="n">IndexView</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> A Class Based View which will generate CSV</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="nf">export_csv</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">queryset</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">data_headings</span> <span class="o">=</span> <span class="p">[</span><span class="n">field</span><span class="o">.</span><span class="n">verbose_name</span> <span class="k">for</span> <span class="n">field</span>
<span class="ow">in</span> <span class="n">EventRegistration</span><span class="o">.</span><span class="n">_meta</span><span class="o">.</span><span class="n">get_fields</span><span class="p">()]</span>
<span class="c1"># return a CSV instead</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">content_type</span><span class="o">=</span><span class="s1">'text/csv; charset=utf-8'</span><span class="p">)</span>
<span class="n">response</span><span class="p">[</span><span class="s1">'Content-Disposition'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'attachment;filename='</span> <span class="o">+</span> \
<span class="s1">'registrations.csv'</span>
<span class="c1"># Prevents UnicodeEncodeError for labels with non-ansi symbols</span>
<span class="n">data_headings</span> <span class="o">=</span> <span class="p">[</span><span class="n">smart_str</span><span class="p">(</span><span class="n">label</span><span class="p">)</span> <span class="k">for</span> <span class="n">label</span> <span class="ow">in</span> <span class="n">data_headings</span><span class="p">]</span>
<span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">writer</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
<span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">data_headings</span><span class="p">)</span>
<span class="k">for</span> <span class="n">reg</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
<span class="n">data_row</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">data_row</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span>
<span class="n">reg</span><span class="o">.</span><span class="n">title</span><span class="p">,</span> <span class="n">reg</span><span class="o">.</span><span class="n">example_field2</span><span class="p">,</span> <span class="n">reg</span><span class="o">.</span><span class="n">example_field3</span>
<span class="p">])</span>
<span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">data_row</span><span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
<span class="nd">@method_decorator</span><span class="p">(</span><span class="n">login_required</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">dispatch</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">dispatch</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">export_csv</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">ExportModelAdminMixin</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> A mixin to add to your model admin which hooks the different helpers, the view and register the new urls.</span>
<span class="sd"> """</span>
<span class="n">button_helper_class</span> <span class="o">=</span> <span class="n">ExportButtonHelper</span>
<span class="n">url_helper_class</span> <span class="o">=</span> <span class="n">ExportAdminURLHelper</span>
<span class="n">export_view_class</span> <span class="o">=</span> <span class="n">ExportView</span>
<span class="k">def</span> <span class="nf">get_admin_urls_for_registration</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">urls</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">get_admin_urls_for_registration</span><span class="p">()</span>
<span class="n">urls</span> <span class="o">+=</span> <span class="p">(</span>
<span class="n">url</span><span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">url_helper</span><span class="o">.</span><span class="n">get_action_url_pattern</span><span class="p">(</span><span class="s1">'export'</span><span class="p">),</span>
<span class="bp">self</span><span class="o">.</span><span class="n">export_view</span><span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">url_helper</span><span class="o">.</span><span class="n">get_action_url_name</span><span class="p">(</span><span class="s1">'export'</span><span class="p">)</span>
<span class="p">),</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">urls</span>
<span class="k">def</span> <span class="nf">export_view</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="n">kwargs</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'model_admin'</span><span class="p">:</span> <span class="bp">self</span><span class="p">}</span>
<span class="n">view_class</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">export_view_class</span>
<span class="k">return</span> <span class="n">view_class</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)(</span><span class="n">request</span><span class="p">)</span>
</code></pre></div>
<ul>
<li>Now use the ExportModelAdminMixin in EventRegistrationAdmin class.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">EventRegistrationAdmin</span><span class="p">(</span><span class="n">ExportModelAdminMixin</span><span class="p">,</span> <span class="n">ModelAdmin</span><span class="p">):</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">EventRegistration</span>
<span class="n">index_template_name</span> <span class="o">=</span> <span class="s1">'events/event_model_admin_index.html'</span>
<span class="o">....</span>
<span class="o">....</span>
</code></pre></div>
<ul>
<li>Also update the template to use the button helper.</li>
</ul>
<div class="highlight"><pre><span></span><code>{% extends "modeladmin/index.html" %}
{% block header_extra %}
{% include 'modeladmin/includes/button.html' with button=view.button_helper.export_button %}
{{ block.super }}{% comment %}Display the original buttons {% endcomment %}
{% endblock %}
</code></pre></div>Reading code makes you a better Developer2016-07-06T18:00:00+05:302016-07-06T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2016-07-06:/reading-code-makes-you-a-better-developer.html<p>Reading code written by other developers make you a better developer.</p><p>Do you like reading code written by other developers?</p>
<p>Reading the code written by other programmers makes you a better developer. If you are an average developer, start reading code written by great programmers to improve your coding skills.</p>
<p>From quite some time, I am realising the importance of reading code. Whenever I was stuck in some task during development, reading the source code of library solves the problem. This also helps when the documentation of the library you are using does not answer you questions. What you should do? The answer is simple open the code of the library, read the relevant part of code and the problem is solved. You may also read the term <strong>"Source code never lies"</strong>.</p>
<p>We are lucky as most of the popular projects these days are open source. Reading code of good Open Source projects takes your programming skills to the next level. I am grateful to all great developers who open source their code for the benefit of all. The simple API provided by popular open source projects to developers, have some great code behind it.</p>
<p><strong>
Reading Open source code can also make you an Open Source contributor.
</strong></p>
<p>While reading the source code of an Open Source project, you may find some section of code that could be implemented in a much better way. Great, it's time to pay back to the Open Source world. Read the contributing guidelines of the project and submit Pull request. isn't it great.</p>
<p>What are you waiting for? Take a cup of coffee, find an Open Source project of your interest like <a href="https://github.com/pallets/flask">flask</a> and start spending some great time reading its code.</p>Open Sourcing pelican-blue theme used in this blog2016-01-18T18:00:00+05:302016-01-18T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2016-01-18:/open-sourcing-pelican-blue-theme-used-in-this-blog.html<p>Responsive theme for Pelican Static Site Generator, Powered by Python Programming language.</p><p>This blog is a static site generated by <a href="http://blog.getpelican.com/">Pelican</a> Static Site Generator, Powered by <a href="https://python.org/">Python</a> Programming language. If you are using Wordpress or any other blogging engine, you should think to move to Static Site Generators like <a href="http://blog.getpelican.com/">Pelican</a> or <a href="https://jekyllrb.com/">Jekyll</a>. Static sites are faster, the load time of this blog as tested on <a href="http://tools.pingdom.com/fpt/#!/bT0Pry/https://parbhatpuri.com/">Pingdom</a> is <strong>540ms</strong>. Static Site Generators offer the following benefits.</p>
<ul>
<li>Speed</li>
<li>Security</li>
<li>Less Hassle with the server</li>
<li>Comments can be setup with <a href="https://disqus.com/">Disqus</a></li>
<li>Free hosting on Github <a href="https://pages.github.com/">Pages</a></li>
</ul>
<p><a href="https://github.com/Parbhat/pelican-blue">Pelican-Blue</a> is the Pelican theme used in this blog. The theme is now Open Source and hosted on Github - <a href="https://github.com/Parbhat/pelican-blue">https://github.com/Parbhat/pelican-blue</a>.</p>
<p><center><img alt="homepage-mobile" src="https://i.imgur.com/SfSvZ7X.png"></center></p>
<h3>Features</h3>
<ul>
<li>Responsive (Mobile Friendly Test on <a href="https://search.google.com/search-console/mobile-friendly?id=9T_RtZq8rvowsGrbPF7Fsw">Google</a>)</li>
<li>Fast (Load time tested on <a href="http://tools.pingdom.com/fpt/#!/bT0Pry/https://parbhatpuri.com/">Pingdom</a>: 540ms)</li>
<li>Syntax highlighting for code blocks</li>
<li><a href="https://disqus.com/">Disqus</a> for Comments</li>
<li>Google Analytics</li>
<li>RSS/ATOM feeds</li>
<li>Easy to install</li>
</ul>
<h3>Installation</h3>
<p>You can install Pelican-Blue theme to your earlier Pelican project or create a new project from the Pelican <a href="http://docs.getpelican.com/en/3.6.3/quickstart.html">Quickstart</a> guide.</p>
<ul>
<li>Clone the repository</li>
</ul>
<div class="highlight"><pre><span></span><code> $ git clone https://github.com/Parbhat/pelican-blue.git
</code></pre></div>
<ul>
<li>Create a THEME variable in your pelicanconf.py file and set its value to the location of pelican-blue theme.</li>
</ul>
<div class="highlight"><pre><span></span><code> <span class="n">THEME</span> <span class="o">=</span> <span class="s1">'path-to-pelican-blue-theme'</span>
</code></pre></div>
<p>If you have placed the pelican-blue theme inside your project's pelican-themes folder, change the THEME variable in pelicanconf.py to</p>
<div class="highlight"><pre><span></span><code> <span class="n">THEME</span> <span class="o">=</span> <span class="s1">'pelican-themes/pelican-blue'</span>
</code></pre></div>
<ul>
<li>Add the following code to your pelicanconf.py file to display the social icons.</li>
</ul>
<div class="highlight"><pre><span></span><code> <span class="n">SOCIAL</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">(</span><span class="s1">'linkedin'</span><span class="p">,</span> <span class="s1">'https://www.linkedin.com/in/username'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'github'</span><span class="p">,</span> <span class="s1">'https://github.com/username'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'twitter'</span><span class="p">,</span> <span class="s1">'https://twitter.com/username'</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div>
<ul>
<li>That's it! You have installed pelican-blue. To see the Theme in action run the devserver</li>
</ul>
<div class="highlight"><pre><span></span><code> make devserver
</code></pre></div>
<p><strong>Note</strong>: If you are new to Pelican Static Site Generator, you can read the Pelican <a href="http://docs.getpelican.com/en/stable/">Docs</a> to learn the working of Pelican. You can also customize the theme after reading the documentation.</p>
<h3>Settings</h3>
<h4>pelicanconf.py</h4>
<p>Pelican-Blue theme use the following settings. You can add the following to your pelicanconf.py to get the site shown in the screenshots.</p>
<div class="highlight"><pre><span></span><code> <span class="n">SIDEBAR_DIGEST</span> <span class="o">=</span> <span class="s1">'Programmer and Web Developer'</span>
<span class="n">FAVICON</span> <span class="o">=</span> <span class="s1">'url-to-favicon'</span>
<span class="n">DISPLAY_PAGES_ON_MENU</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">TWITTER_USERNAME</span> <span class="o">=</span> <span class="s1">'twitter-user-name'</span>
<span class="n">MENUITEMS</span> <span class="o">=</span> <span class="p">((</span><span class="s1">'Blog'</span><span class="p">,</span> <span class="n">SITEURL</span><span class="p">),)</span>
</code></pre></div>
<p>When developing locally, set the following variable:</p>
<div class="highlight"><pre><span></span><code> <span class="n">SITEURL</span> <span class="o">=</span> <span class="s1">'http://localhost:8000'</span>
</code></pre></div>
<h4>publishconf.py</h4>
<p>When you are ready to publish your site add the following settings to publishconf.py file</p>
<div class="highlight"><pre><span></span><code> <span class="n">SITEURL</span> <span class="o">=</span> <span class="s1">'http://your-domain-address'</span>
<span class="n">FEED_ALL_ATOM</span> <span class="o">=</span> <span class="s1">'feeds/all.atom.xml'</span>
<span class="n">CATEGORY_FEED_ATOM</span> <span class="o">=</span> <span class="s1">'feeds/</span><span class="si">%s</span><span class="s1">.atom.xml'</span>
<span class="n">MENUITEMS</span> <span class="o">=</span> <span class="p">((</span><span class="s1">'Blog'</span><span class="p">,</span> <span class="n">SITEURL</span><span class="p">),)</span>
<span class="n">DISQUS_SITENAME</span> <span class="o">=</span> <span class="s2">""</span>
<span class="n">GOOGLE_ANALYTICS</span> <span class="o">=</span> <span class="s2">""</span>
</code></pre></div>
<p>For more information on publishing your site, read the publishing <a href="http://docs.getpelican.com/en/stable/publish.html">docs</a></p>
<p>Thanks for reading.</p>Git workflow to contribute to an Open Source project2015-08-02T18:00:00+05:302015-08-02T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2015-08-02:/git-workflow-to-contribute-to-an-open-source-project.html<p>Git workflow to contribute to an Open Source project and improve your skills.</p><p>Open Source is the way by which great projects are built. This is made possible by people like you who give their precious time to the community and great stuff is built. So you may be a beginner and wants to contribute to Open Source projects. This article will guide you to contribute to Open Source projects hosted on Collaboration platform <a target="_blank" href="https://github.com">Github</a>.</p>
<p><center><img alt="Github" src="https://i.imgur.com/IXBmJj4.jpg"></center></p>
<p>This summer I was working as an Intern with <a target="_blank" href="http://chrisdev.com/">ChrisDev</a>. The company is one of the corporate members of the <a target="_blank" href="https://www.djangoproject.com/foundation/corporate-members/">Django</a> Web Framework. The company works with open source technologies like Python/Django, Pandas, Postgresql, Ansible, Linux, etc. The company has a number of open source projects hosted on <a target="_blank" href="https://github.com/chrisdev">Github</a> and Pypi. I worked on a cookiecutter in Django. The project Url is- <a target="_blank" href="https://github.com/chrisdev/wagtail-cookiecutter-foundation">https://github.com/chrisdev/wagtail-cookiecutter-foundation</a></p>
<p>In this article, I will explain the workflow used by me to contribute to this project. The same workflow is applicable to almost any project on Github.</p>
<h1>Git and Github</h1>
<hr>
<p>Before continuing I want to clarify the difference between Git and Github. Git is a version control system(VCS) which is a tool to manage the history of our Source Code. GitHub is a hosting service for Git projects.</p>
<p>I assume you have created an account on <a target="_blank" href="https://github.com/join">Github</a> and installed <a target="_blank" href="http://git-scm.com/downloads">Git</a> on your System.</p>
<p>Now tell Git your name and E-mail (used on Github) address.</p>
<div class="highlight"><pre><span></span><code>$ git config --global user.name <span class="s2">"YOUR NAME"</span>
$ git config --global user.email <span class="s2">"YOUR EMAIL ADDRESS"</span>
</code></pre></div>
<p>This is an important step to mark your commits to your name and email.</p>
<h1>Fork a Project</h1>
<hr>
<p>You can use github explore - <a target="_blank" href="https://github.com/explore">https://github.com/explore</a> to find a project that interests you and match your skills. Once you find your cool project to workon, you can make a copy of project to your account. This process is called forking a project to your Github account. On Upper right side of project page on Github, you can see -</p>
<p><center><img alt="Github-fork" src="https://i.imgur.com/P0n6f97.png"></center></p>
<p>Click on fork to create a copy of project to your account. This creates a separate copy for you to workon.</p>
<h1>Finding a feature or bug to workon</h1>
<hr>
<p>Open Source projects always have something to workon and improves with each new release. You can see the issues section to find something you can solve or report a bug. The project managers always welcome new contributors and can guide you to solve the problem. You can find issues in the right section of project page.</p>
<p><center><img alt="github-issues" src="https://i.imgur.com/czVjpS7.png"></center></p>
<h1>Clone the forked project</h1>
<hr>
<p>You have forked the project you want to contribute to your github account. To get this project on your development machine we use clone command of git.</p>
<div class="highlight"><pre><span></span><code>$ git clone https://github.com/<your-account-username>/<your-forked-project>.git
</code></pre></div>
<p>Now you have the project on your local machine.</p>
<h1>Add a remote (upstream) to original project repository</h1>
<hr>
<p>Remote means the remote location of project on Github. By cloning, we have a remote called origin which points to your forked repository. Now we will add a remote to the original repository from where we had forked.</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">cd</span> <your-forked-project-folder>
$ git remote add upstream https://github.com/<author-account-username>/<project>.git
</code></pre></div>
<p>You will see the benefits of adding remote later.</p>
<h1>Synchronizing your fork</h1>
<hr>
<p>Open Source projects have a number of contributors who can push code anytime. So it is necessary to make your forked copy equal with the original repository. The remote added above called Upstream helps in this.</p>
<div class="highlight"><pre><span></span><code>$ git checkout master
$ git fetch upstream
$ git merge upstream/master
$ git push origin master
</code></pre></div>
<p>The last command pushes the latest code to your forked repository on Github. The origin is the remote pointing to your forked repository on github.</p>
<h1>Create a new branch for a feature or bugfix</h1>
<hr>
<p>Normally, all repositories have a master branch which is considered to remain stable and all new features should be made in a separate branch and after completion merged into master branch. So we should create a new branch for our feature or bugfix and start working on the issue.</p>
<div class="highlight"><pre><span></span><code>$ git checkout -b <feature-branch>
</code></pre></div>
<p>This will create a new branch out of master branch. Now start working on the problem and commit your changes.</p>
<div class="highlight"><pre><span></span><code>$ git add --all
$ git commit -m <span class="s2">"<commit message>"</span>
</code></pre></div>
<p>The first command adds all the files or you can add specific files by removing -a and adding the file names. The second command gives a message to your changes so you can know in future what changes this commit makes. If you are solving an issue on original repository, you should add the issue number like #35 to your commit message. This will show the reference to commits in the issue.</p>
<h1>Rebase your feature branch with upstream</h1>
<hr>
<p>It can happen that your feature takes time to complete and other contributors are constantly pushing code. After completing the feature your feature branch should be rebase on latest changes to upstream master branch.</p>
<div class="highlight"><pre><span></span><code>$ git checkout <feature-branch>
$ git pull --rebase upstream master
</code></pre></div>
<p>Now you get the latest commits from other contributors and check that your commits are compatible with the new commits. If there are any conflicts solve them.</p>
<h1>Squashing Your Commits</h1>
<hr>
<p>You have completed the feature, but you have made a number of commits which make less sense. You should squash your commits to make good commits.</p>
<div class="highlight"><pre><span></span><code>$ git rebase -i HEAD~5
</code></pre></div>
<p>This will open an editor which will allow you to squash the commits.</p>
<h1>Push code and create a Pull request</h1>
<hr>
<p>Till this point you have a new branch with the feature or bugfix you want in the project you had forked. Now push your new branch to your remote fork on github.</p>
<div class="highlight"><pre><span></span><code>$ git push origin <feature-branch>
</code></pre></div>
<p>Now you are ready to help the project by opening a pull request means you now tell the project managers to add the feature or bugfix to original repository. You can open a pull request by clicking on green icon -</p>
<p><center><img alt="github-pullrequest" src="https://i.imgur.com/aGaqAD5.png"></center></p>
<p>Remember your upstream base branch should be master and source should be your feature branch. Click on create pull request and add a name to your pull request. You can also describe your feature.</p>
<p>Awesome! You have made your first contribution. If you have any doubts please let me know in the comments.</p>
<h1>Be Open!</h1>Why you should Open Source your code?2015-05-27T18:00:00+05:302015-05-27T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2015-05-27:/why-you-should-open-source-your-code.html<p>Open Source your code is the best way to improve your Coding Skills.</p><p>Open source your code is the best way to improve your coding Skills. I have seen people who are reluctant to share their code with others because they know their code is messy. By Open Sourcing your code you learn the best practices in Developing higher quality code. In this article, I will share the reasons why Open source your code is the best practice that every Developer should follow. Below is the Github Open Source graph of a Famous Open Source Contributor.</p>
<p><center><img alt="Github-Graph" src="https://i.imgur.com/Exp1j8B.png"></center></p>
<p>The following points highlight the importance of Open Source Code.</p>
<ul>
<li>
<p>Open Source improves your coding skills. The code that is only open to your eyes can be more error prone. When you put your code in public, you test it thoroughly and improve it with recommendations from your team or others.</p>
</li>
<li>
<p>Open Source gives you a good reason to write Good Quality code rather than messy code that is just working and can be broken easily.</p>
</li>
<li>
<p>If you open source some cool project, then you get reviews and contributions from other great Developers.</p>
</li>
<li>
<p>If you are a company who is Open Source his code, then You can attract Good Developers to work with you. Great Developers love Open Source World.</p>
</li>
<li>
<p>You have used many Open Source technologies in your projects like Python, Linux, NodeJS, MongoDB, MySQL. By going Open Source you share your work with the Open Source Community.</p>
</li>
<li>
<p>Other Developers can also learn from your Good Quality code and improve their skills.</p>
</li>
</ul>
<p>So go ahead and create your profile on <a href="https://github.com" target="_blank">https://github.com</a> or any other social coding site and Open Source your projects and start something cool that can benefit the whole Community.</p>Why you should learn Flask before Django?2015-04-04T18:00:00+05:302015-04-04T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2015-04-04:/why-you-should-learn-flask-before-django.html<p>Flask is a Micro Web framework for Python which is expressive and powerful.</p><p>Recently I explored <a href="http://flask.pocoo.org/">Flask</a> and loved the simplicity and power of this Microframework. I learned Django as my first Python Web framework, but now I realise that Flask should be the first <a href="https://python.org">Python</a> web framework that everyone should learn while entering into the field of Web Development with Python.</p>
<p><center><img alt="Flask-logo" src="https://i.imgur.com/Os2jtnk.png"></center></p>
<p>Django is a full stack framework with the batteries included approach making it easy for developers to dive it into Web Applications. But for beginners, for learning the basics of <a href="http://wsgi.readthedocs.org/en/latest/index.html">WSGI</a> and Python web development and of dynamic web development, I do not know of any web framework that gives a better introduction to the concepts underlying it than Flask.</p>
<p>Starting with Flask is so simple that a <em>hello World</em> Web Application looks like this</p>
<div class="highlight"><pre><span></span><code><span class="c1"># from http://flask.pocoo.org/ tutorial</span>
<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span> <span class="c1"># take note of this decorator syntax, it's a common pattern</span>
<span class="k">def</span> <span class="nf">hello</span><span class="p">():</span>
<span class="k">return</span> <span class="s2">"Hello World!"</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</code></pre></div>
<p>With only 7 lines of code you have your Web Application running with Flask. From the above <em>Hello World</em> web application, a developer with no experience building Python Web applications can get Hacking immediately. You should learn flask because - </p>
<ul>
<li>Easy to understand</li>
<li>Extensive documentation</li>
<li>Built in development server and debugger</li>
<li>Supports REST</li>
<li>Code base small you can check the source code.</li>
<li>No ORM so you can use SQLAlchemy or storm</li>
<li>Uses Jinja2 templating</li>
<li>Support other templates like genshi, mako</li>
<li>Integrated unit testing support</li>
<li>support for secure cookies (client side sessions)</li>
<li>Module level integration</li>
<li>100% WSGI compliant.</li>
<li>Integration with gevent, twisted, tornado is possible.</li>
</ul>
<h2>Why it should be your first Python Web Framework?</h2>
<hr>
<ul>
<li>
<p>Flask is more Pythonic than Django because the code of flask Web Application in most cases is more explicit than the Django Web Application code. So it is easy for Python coders to pick up.</p>
</li>
<li>
<p>Beginners can learn a lot from the well documented Source Code.</p>
</li>
<li>
<p>Flask does include everything you need to start building something more complex than the above <em>Hello World</em> app. It integrates a templating engine (Jinja2) for you so you don't have to decide whether you would be better off using Genshi, Cheetah or Mako (though you could use). </p>
</li>
<li>
<p>The extra work at start to choose components for Flask apps yields more flexibility for developers whose use case doesn't fit a standard ORM, or who need to interoperate with different workflows or templating systems.</p>
</li>
<li>
<p>Django is a large framework which includes everything, whether you require it or not. So for smaller application Flask can give more performance. <a href="http://httpbin.org/">httpbin</a> is a smaller application built with Flask.</p>
</li>
<li>
<p>Flask also scales well. Pinterest uses Flask for their API. The API Handles over 12 billion requests per day, all in Flask. More information on How Pinterest uses Flask can be found on <a href="https://www.quora.com/What-challenges-has-Pinterest-encountered-with-Flask/answer/Steve-Cohen" target="_blank"> Quora. </a></p>
</li>
</ul>
<p>So, New Python developers should try Microframeworks like Flask before moving to large Frameworks like Django. You should definitely use Django after learning the small pieces of Web Applications.</p>
<h2>Learning Resources</h2>
<hr>
<ol>
<li><a href="http://flask.pocoo.org/docs/0.10/" target="_blank"> Official Documentation</a></li>
<li><a href="http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world" target="_blank">The Flask-Mega Tutorial</a></li>
<li><a href="https://exploreflask.com/" target="_blank">Explore Flask</a></li>
<li><a href="http://code.tutsplus.com/tutorials/an-introduction-to-pythons-flask-framework--net-28822" target="_blank">Tuts+ - An Introduction to Python’s Flask Framework</a></li>
<li><a href="http://maximebf.com/blog/2012/10/building-websites-in-python-with-flask/#.VR-rV-mUfeS" target="_blank">Building websites in Python with Flask</a></li>
<li><a href="https://www.udacity.com/course/ud088" target="_blank">Udacity - Full Stack Foundations</a></li>
<li><a href="http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask" target="_blank">Designing a RESTful API with Python and Flask</a></li>
</ol>What is an API?2015-03-14T18:00:00+05:302015-03-14T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2015-03-14:/what-is-an-api.html<p>API is Application Programming Interface.</p><p>API word has become so common on the web that you find it everywhere from technical blogs to books. I am sure you have heard about Google Maps API, Facebook Graph API, Twitter API. The defination of an API is - </p>
<p>An application programming interface (API) is a particular set of instructions that programs can follow to communicate with each other. It serves as an interface between different programs and facilitates their interaction, similar to the way the user interface facilitates interaction between humans and computers.</p>
<p>So what does this means? Suppose I am working on a Web Application and I want to use services of other applications on the web. APIs provides an interface to talk to other applications with our application. For example, Google Maps API allows to Access information about establishments, geographic locations, or prominent points of interest. Facebook Graph API allows us to extend the features provided by facebook to suit our needs, Ex - To Calculate number of likes that are common to a post and page, Mass Responding to Posts on Your Timeline.</p>
<p>Almost all of the popular services on the web have their APIs available to the developers so that they can build their applications on top it.</p>
<p>Some of the most popular APIs are :</p>
<ul>
<li><a href="https://developers.google.com/maps/" target="_blank">Google Maps API</a></li>
<li><a href="https://developers.google.com/youtube/" target="_blank">YouTube APIs</a></li>
<li><a href="http://www.flickr.com/services/api/" target="_blank">Flickr API:</a></li>
<li><a href="https://dev.twitter.com/" target="_blank">Twitter APIs</a></li>
<li><a href="https://affiliate-program.amazon.com/gp/advertising/api/detail/main.html" target="_blank">Amazon Product Advertising API</a></li>
</ul>
<p>APIs allows developers to integrate remote applications with their own applications. Thus reducing the efforts to write every part of application from scratch. For example, when we purchase something from a website by entering our credit card information, the website uses API to connect to remote application to check whether the information provided by you is correct. Once payment is confirmed, the remote application sends a response to the website that the payment is received and the product can be sold.</p>
<p>So, there are number of APIs available on the web waiting for you to use them. Find APIs according to your needs and integrate them in your next Cool Application.</p>AngularJS Superhero of Single-Page Application (SPA)2015-01-30T18:00:00+05:302015-01-30T18:00:00+05:30Parbhat Puritag:parbhatpuri.com,2015-01-30:/angularjs-superhero-of-single-page-application.html<p>AngularJS is the Superheroic JavaScript MVW Framework for enhancing HTML for Web Apps.</p><p>AngularJS is the Superheroic JavaScript MVW Framework for enhancing HTML for Web Apps.
<center><img alt="Angular-JS-logo" src="https://angularjs.org/img/AngularJS-large.png"></center></p>
<p>Today's Web Apps are not that Simple, they should offer more-native-app-like experience to the user. The Web Application should not reload the whole page whenever a user request something. First we have to familiar ourself with the concept of Single page application.</p>
<p>A single-page application (SPA), is a web application that fits on a single web page with the goal of providing a
more fluid user experience similar to a desktop application. In a SPA, either all necessary code – HTML,
JavaScript, and CSS – is retrieved with a single page load, or the appropriate resources are dynamically loaded
with technologies like AJAX and added to the page as necessary, usually in response to user actions. The page does
not reload at any point in the process, nor does control transfer to another page. Interaction with the single
page application often involves dynamic communication with the web server behind the scenes.</p>
<h3>Now, how angularJS fits itself into Single Page Application concept.</h3>
<p>AngularJS also known as angular is an Open Source Web Application Framework maintained by <strong>Google</strong>. It helps in
creating rich Internet Applications which are easy to maintain and test. Angular works by first reading the HTML
page, which has embedded into it additional custom tag attributes. Those attributes are interpreted as
directives telling Angular to bind input or output parts of the page to a model that is represented by standard
JavaScript variables. The values of those JavaScript variables can be manually set within the code, or retrieved
from static or dynamic JSON resources.</p>
<p>HTML was not designed for dynamic views, but it is great for declaring static documents. Angular helps to extend
HTML and gives behaviour to HTML which helps in declaring dynamic views in web-applications.</p>
<p>In my Future posts I will discuss the features of angular like directives, Controllers, Models, Routes, Factories, services etc. The following picture helps to understand the power of angular.</p>
<p><center><img alt="Angular-JS" src="https://i.imgur.com/U87M5vo.png"></center></p>Skills Essential for a Front End Web Developer in 20152015-01-11T11:00:00+05:302015-01-11T11:00:00+05:30Parbhat Puritag:parbhatpuri.com,2015-01-11:/skills-essential-for-a-front-end-web-developer-in-2015.html<p>Front End Developers are in great demand in 2015 but with the emergence of new devices and technologies their skills requirement has increased so I am laying out a checklist for your Front End Development skills.</p><p>Front End Web Developers are in great demand in 2015 but with the emergence of new devices and technologies their skills requirement has increased so I am laying out a checklist for your Front End Development skills. These skills will help you to land a job as a Front End Developer in 2015.</p>
<p><img alt="Front-End-Developer-needed" src="https://i.imgur.com/U1Iev4X.jpg"></p>
<p>In the early days of the Internet only Html, CSS and some Javascript were demanded from a Front End Web Developer but in the current scenario, these skills are <strong>not</strong> enough to call yourself a Good Front End Web Developer. The following skills are in great demand and you must include them in your skills set.</p>
<ul>
<li>HTML</li>
<li>CSS</li>
<li>Javascript</li>
<li>Responsive Web Design</li>
<li>CSS Frameworks</li>
<li>Javascript Frameworks</li>
<li>Version Control</li>
<li>Web Performance</li>
<li>Browser Development tools</li>
<li>Building and Automation tools</li>
<li>Testing</li>
<li>Soft skills</li>
</ul>
<p><a href="https://www.youtube.com/user/learncodeacademy" target="_blank">learncode.acedemy</a> in his <a href="https://www.youtube.com/watch?v=zXqs6X0lzKI" target="_blank">video</a> gives a graphical presentation of above skills.</p>
<p><a href="https://i.imgur.com/EB3TIdK.png" target="_blank"><center><img alt="skills-graph" src="https://i.imgur.com/EB3TIdK.png"></center></a></p>
<p>Now I will give you a brief description of the above skills.</p>
<ul>
<li>
<p><strong>HTML</strong> - It is the structural part of the web. It tells the browser the elements in the website, links to css and javascript files.</p>
</li>
<li>
<p><strong>CSS</strong> - Cascading style sheets makes the web beautiful. It is responsible for fonts, colors, images and positioning of different elements on the websites.</p>
</li>
<li>
<p><strong>Javascript</strong> - Javascript is the programming language which helps in making the web interactive. With the development of NodeJS, JS has become a language of the web. You can make a full stack Web Application with only Javascript and many developers are adopting this technology.</p>
</li>
<li>
<p><strong>Responsive Web Design</strong> - Mobile is the future. The number of users of the mobiles is increasing day by day and the larger traffic on the websites is coming from mobile phones. Responsive design means that your website should adjust itself to the device from which it is being viewed and give an appealing look on most devices. </p>
</li>
<li>
<p><strong>CSS Frameworks</strong> - Frameworks make the responsive development easier and already contain different classes for the buttons, forms and various elements on the page. It follows industry best practices. Examples are - <a href="http://getbootstrap.com/">Bootstrap</a>, <a href="http://foundation.zurb.com/">Foundation</a>.</p>
</li>
<li>
<p><strong>Javascript Frameworks</strong> - These frameworks help in creating web application easier and follow best practices. Single Page Applications (SPAs) can be made efficiently with these frameworks. Examples are - <a href="https://angularjs.org/">AngularJS</a>,<a href="http://emberjs.com/">Ember</a>,<a href="http://backbonejs.org/">Backbone</a>.</p>
</li>
<li>
<p><strong>Version Control</strong> - Version Control is a must for every project. Suppose you are working on a feature in your project and you messed up something, now what you will do. With Version Control Systems (VCS) like <strong>git</strong> you can view the changes you have made and go back to your previous state. <a href="https://github.com/">Github</a> is a web based hosting service for git repositories which makes collaboration between teams much easier. It gives features like forks and pull requests which helps in a big way to open source development.</p>
</li>
<li>
<p><strong>Web Performance</strong> - You have created your awesome website but if it takes long time to load people will not wait and leave your site. So you should optimize your images, Minify css and javscript to deliever fast and efficient websites to your users.</p>
</li>
<li>
<p><strong>Browser Development tools</strong> - Modern browsers are not just awesome for users But they are great for developers as well. These development tools help us to make changes to the DOM on the fly and see the changes. Debugging javascript is easier with these tools.</p>
</li>
<li>
<p><strong>Building and Automation tools</strong> - Who wants to do boring repetitive tasks. Tasks like running tests, optimize images, minify javascript and preparing code for deploying to production server can be automated with tools like <a href="http://gruntjs.com/">Grunt</a>, <a href="http://gulpjs.com/">gulp</a>. So you can focus only on building great web Applications.</p>
</li>
<li>
<p><strong>Testing</strong> - Testing is an essential part of creating web Applications. When you are working on a new feature you want to test your project so that you had not broken anything in the process. Examples of testing frameworks are <a href="http://mochajs.org/">Mocha</a>, <a href="http://jasmine.github.io/">Jasmine</a></p>
</li>
<li>
<p><strong>Soft skills</strong> - Soft skills are also important as technical skills. Soft skills help to communicate our ideas effectively with others, inspire others and see the big picture. Soft Skills include Strong Comminication, Agile Problem solving, Healthy passion, Self starting motivation.</p>
</li>
</ul>
<p>The above skills are demanded by Companies hiring Front End Developers. So whether you are just starting out or an expert in this field, You should add these skills to your tool set. The following resources will be helpful to learn -</p>
<ul>
<li><a href="https://www.udacity.com/" target="_blank">https://www.udacity.com</a></li>
<li><a href="http://www.codecademy.com/" target="_blank">http://www.codecademy.com</a></li>
<li><a href="https://www.youtube.com/user/DevTipsForDesigners" target="_blank">https://www.youtube.com/user/DevTipsForDesigners</a></li>
<li><a href="http://learncode.academy/" target="_blank">http://learncode.academy</a></li>
<li><a href="http://tutsplus.com/" target="_blank">http://tutsplus.com</a></li>
</ul>
<p>Not understand some terms in this post go ahead and search <a href="http://google.com" target="_blank">Google</a> and you will find a number of resources. The community is there to help you in every step of your learning. You just need to start with confidence.</p>Why you should learn Python in 2015?2015-01-04T23:00:00+05:302015-01-04T23:00:00+05:30Parbhat Puritag:parbhatpuri.com,2015-01-04:/Why-you-should-learn-python-in-2015.html<p>Python is the golden fish in the Programming languages world. It is being used everywhere from Scientific and Numeric to Web Programming.</p><p>Yesterday I saw a question on Quora - <a href="https://www.quora.com/Is-Python-worth-it-to-learn-as-a-programming-language">Is Python worth it to learn as a programming language?</a>. I answered the question on quora and decided to write a blog post on Importance of learning Python in 2015.</p>
<p>Happy New Year 2015. </p>
<p><a href="https://www.python.org/">Python</a> is the golden fish in the Programming languages world. It is being used everywhere from Scientific and Numeric to Web Programming. MIT - One of the Top universities in the world uses Python in its Introduction to Computer Science Course. It is being used by Biggest names in the industry like Google, YouTube, Quora, Pinterest, Instagram, Reddit, Redhat etc. </p>
<p>You have a number of options after learning this powerful Programming language. It is easy to learn and code. The open source community behind Python is really awesome and working collectively to improve it. Without any further writing I will show you the importance of this language in the images below -</p>
<p><center><img alt="python-companies" src="http://i.imgur.com/BBADQ4P.png"></center></p>
<p><a href="https://www.python.org/about/apps/">Use Python for</a></p>
<p><center><img alt="python" src="http://i.imgur.com/u4JA6SR.png"></center></p>
<p><center><img alt="python-percentages" src="http://i.imgur.com/ytryfJ2.png"></center></p>
<p>Jobs</p>
<p><center><img alt="python-graph-jobs" src="http://i.imgur.com/5oMliVZ.png"></center></p>
<p><center><img alt="python-use" src="http://i.imgur.com/DDNDRQH.png"></center></p>
<p>Python is a great skill that will surely pay off in 2015. It's clean syntax gives you Super powers. </p>
<p><center><img alt="python-xkcd" src="https://i.imgur.com/b6qQf8E.png"></center></p>
<p>So go ahead and dive in the world of Python full of learning and Adventures.
<br>
Best learning Resource - <a href="https://www.edx.org/course/introduction-computer-science-mitx-6-00-1x-0">6.00.1x introduction to computer science</a> from MIT.</p>
<p><center><img alt="python-keep-calm" src="http://i.imgur.com/VBOXm7Y.png"></center></p>VirtualenvWrapper on top of Virtualenv2014-11-27T20:00:00+05:302014-11-27T20:00:00+05:30Parbhat Puritag:parbhatpuri.com,2014-11-27:/virtualenvwrapper-on-top-of-virtualenv.html<p>Virtualenv is a tool for creating isolated Python environment which means you can have separate python environments for your projects.</p><h2>What is Virtualenv?</h2>
<p>Virtualenv is a tool for creating isolated Python environments which means you can have separate python environments for your projects.</p>
<p>Lets keep it simple suppose you are working on a project which depends on any python package like <strong>Django == 1.6.5</strong>. And wow the new version <strong>Django == 1.7</strong> is launched with some cool features like migrations etc. You want to start a new project on Django == 1.7 but wait how you are gonna do that?</p>
<h4>How to maintain two separate versions of Django.</h4>
<p>The answer is simple use <strong>Virtualenv</strong> to create separate python environments for them. Those two environments will be totally separated from each other as well as from the globally installed packages.</p>
<h4>Installing Virtualenv</h4>
<p><hr>
The easiest way to install virtualenv is to use <a href="http://pip.readthedocs.org/en/latest">pip</a></p>
<div class="highlight"><pre><span></span><code>$ pip install virtualenv
</code></pre></div>
<h4>Usage</h4>
<p><hr>
<strong>To create a Virtual environment</strong></p>
<div class="highlight"><pre><span></span><code>$ virtualenv ENV_NAME
</code></pre></div>
<p><strong> Activate the Virtual environment</strong></p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">source</span> bin/activate
</code></pre></div>
<h2>But wait where is <strike>Virtualenv</strike> VirtualenvWrapper</h2>
<h4>Virtualenv Wrapper is the Hero</h4>
<p>virtualenvwrapper is the extension to virtualenv. It makes the process of creating and managing virtual environments smoother and easier. It provides a set of commands which helps in managing multiple environments easily and remove conflicts.</p>
<h4>Installing Virtualenvwrapper</h4>
<p><hr>
As usual the best way to install any python package is to use pip.</p>
<div class="highlight"><pre><span></span><code>$ pip install virtualenvwrapper
</code></pre></div>
<p><strong>Some configuration</strong>
<hr></p>
<div class="highlight"><pre><span></span><code>vi ~/.bashrc
</code></pre></div>
<p>(maybe virtualenvwrapper.sh is in other location, change if necessary)</p>
<div class="highlight"><pre><span></span><code><span class="nb">export</span> <span class="nv">WORKON_HOME</span><span class="o">=</span><span class="nv">$HOME</span>/.virtualenvs
<span class="nb">source</span> /usr/local/bin/virtualenvwrapper.sh
</code></pre></div>
<p>Load file .bashrc</p>
<div class="highlight"><pre><span></span><code><span class="nb">source</span> ~/.bashrc
</code></pre></div>
<h4>Creating a Virtual Environment using Virtualenv Wrapper</h4>
<hr>
<div class="highlight"><pre><span></span><code>$ mkvirtualenv env_name
</code></pre></div>
<p><center><div class="imga"><img src="https://i.imgur.com/eCXWgI8.png"></div></center></p>
<h4>Activating the Virtual environment</h4>
<hr>
<div class="highlight"><pre><span></span><code>$ workon env_name
</code></pre></div>
<p>You can see the changes before the username. This indicates that your virtual
environment is activated.</p>
<p><center><img alt="virtualenv" src="https://i.imgur.com/MytTSyR.png"></center></p>
<h4>Installing and listing packages using pip</h4>
<hr>
<p><center><img alt="virtualenv" src="https://i.imgur.com/v0iftDF.png"></center></p>
<h4>Deactivate Environment</h4>
<hr>
<div class="highlight"><pre><span></span><code>$ deactivate
</code></pre></div>
<p><strong>Great we have come to the end of this post.</strong></p>
<p>virtualenv is a great tool for any python project and virtualenvwrapper makes the process easier. For more details refer to their Official Documentation -</p>
<ul>
<li><a href="http://virtualenv.readthedocs.org/en/latest/">http://virtualenv.readthedocs.org/en/latest/</a></li>
<li><a href="http://virtualenvwrapper.readthedocs.org/en/latest">http://virtualenvwrapper.readthedocs.org/en/latest</a></li>
</ul>
<h2>Oh I forget something</h2>
<div class="highlight"><pre><span></span><code>$ pip install have-a-great-development
</code></pre></div>Why use Linux for Python development?2014-11-22T19:40:00+05:302014-11-22T19:40:00+05:30Parbhat Puritag:parbhatpuri.com,2014-11-22:/why-use-linux-for-python-development.html<p>Python is a high level programming language.The development time is precious so using Linux based operating systems makes the development easier and fun.</p><p><a href="https://www.python.org/">Python</a> is a high level programming language. The development time is precious so using Linux based operating systems makes the development easier and fun.</p>
<p>I had been using windows for a couple of months for my <a href="https://www.djangoproject.com/">Django</a> Projects. Initially during my learning stage windows was working fine for me but as the knowledge and project size grew I faced many difficulties which I want to summarize here. I am not supporting any operating system here but want every Developer to understand the reasons for switching. For following reasons every Developer should switch to Linux based OS like <a href="http://www.ubuntu.com/">Ubuntu</a> for Python Development.</p>
<ul>
<li>
<p>Some packages are <strong>not</strong> available for windows, if they are available it will be difficult to configure them in your projects.</p>
</li>
<li>
<p>If you are working on a project in Django, flask and other frameworks you would like to <strong>deploy</strong> it so it will be available for public to see. In more than 90% of the cases your deployment environment will be Linux based so its best to test your application locally on the same environment as in deployment because here you are allowed to make mistakes and improve.</p>
</li>
<li>
<p>Almost every tutorial on Python use Linux based systems like Ubuntu. These tutorials are by experts so it's good to follow best practices used by experienced developers.</p>
</li>
<li>
<p>When your projects get complex its very easy to find Dependencies for your projects like extensions, libraries etc. in Linux based OS. In Windows you have to search for the right site to download the package and go through the hassle of installation and configuration. Sometimes the search for packages takes a lot of time, in Linux its just <em><strong>"apt-get"</strong></em> (or similar).</p>
</li>
<li>
<p>Python comes pre-installed in Ubuntu and other versions so no need to install python on your system.</p>
</li>
</ul>
<p><center><img alt="python-linux" src="https://i.imgur.com/lhlh6Lx.png"></center></p>
<p><em><strong>Switch to linux and use apt-get to install your packages</strong></em>.</p>
<p>Happy Coding.</p>Django (Python Web Framework) Tutorials2014-07-18T10:20:00+05:302014-07-18T19:30:00+05:30Parbhat Puritag:parbhatpuri.com,2014-07-18:/django_tutorials.html<p>The Web framework for perfectionists with deadlines.</p><h2>Django Tutorials</h2>
<p><em><a href="https://www.djangoproject.com" target="_blank">Django</a> is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.</em></p>
<ul>
<li><a href="https://docs.djangoproject.com/en/1.6/intro/tutorial01/" target="_blank">Official Tutorial </a> - It is the Official tutorial of django on the django site.</li>
<li><a href="http://www.tangowithdjango.com/" target="_blank">Tango with Django</a> - A Good Introduction to the django terminology.</li>
<li><a href="http://gettingstartedwithdjango.com" target="_blank">Getting Started With Django</a> - A video series to upgrade from beginner to pro.</li>
<li><a href="http://twoscoopspress.org" target="_blank">Two Scoops of Django</a> - A book covering the best practices in Django.</li>
</ul>