<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/cms/</id>
  <link href="https://www.endpointdev.com/blog/tags/cms/"/>
  <link href="https://www.endpointdev.com/blog/tags/cms/" rel="self"/>
  <updated>2022-10-07T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Integrating Contentful with NuxtJS</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/10/integrating-contentful-with-nuxt/"/>
      <id>https://www.endpointdev.com/blog/2022/10/integrating-contentful-with-nuxt/</id>
      <published>2022-10-07T00:00:00+00:00</published>
      <author>
        <name>Juan Pablo Ventoso</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/10/integrating-contentful-with-nuxt/fishing-rio-de-la-plata-sunset.webp&#34; alt=&#34;An orange sunset reflecting off of the sea at Rio de la Plata. A city skyline is silhouetted by the sunset.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Juan Pablo Ventoso --&gt;
&lt;p&gt;Some time ago, I had the opportunity to collaborate on a cool &lt;a href=&#34;https://nuxtjs.org/&#34;&gt;NuxtJS&lt;/a&gt; project. I&amp;rsquo;m still somewhat new to &lt;a href=&#34;https://vuejs.org/&#34;&gt;Vue.js&lt;/a&gt; and its related frameworks, meaning I&amp;rsquo;m discovering exciting new tools and third-party services that can be integrated with them every time a new requirement appears. And there is a particular concept that I heard of, but never worked with until this project: using a &lt;a href=&#34;https://en.wikipedia.org/wiki/Headless_content_management_system&#34;&gt;Headless CMS&lt;/a&gt; to deliver content.&lt;/p&gt;
&lt;p&gt;Essentially, a headless CMS permits creating a custom content model, making it accessible through one (or several) APIs that we can query, allowing us to choose whatever presentation layer we prefer to handle the display. This approach decouples the content management part (the &amp;ldquo;body&amp;rdquo;) of a project from the design, templates, and frontend logic (the &amp;ldquo;head&amp;rdquo;), becoming particularly useful when we have several application types that will interact with the same data, such as a website, a mobile app, or an IoT device.&lt;/p&gt;
&lt;p&gt;With that in mind, let&amp;rsquo;s have a quick look at &lt;a href=&#34;https://www.contentful.com/&#34;&gt;Contentful&lt;/a&gt;: It&amp;rsquo;s a headless CMS that is offered under the concept of content-as-a-service (&lt;a href=&#34;https://www.contentful.com/r/knowledgebase/content-as-a-service/&#34;&gt;CaaS&lt;/a&gt;), meaning the content is delivered on demand from a cloud platform to the consumer by implementing an API or web service.&lt;/p&gt;
&lt;h3 id=&#34;pricing&#34;&gt;Pricing&lt;/h3&gt;
&lt;p&gt;For individual or small websites, the free option should be sufficient. It has a limit of 5 users and a size limit of 50MB for assets, and the technical support area is disabled. The next option (Medium, $489/month) also includes an additional role (author), additional locales, and the possibility to create up to ten different user accounts. The asset size is also extended up to 1000MB.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/10/integrating-contentful-with-nuxt/contentful-pricing.webp&#34; alt=&#34;Contentful pricing: Community tier is free, Team tier is $489/month, Enterprise is custom pricing.&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can review the full pricing details &lt;a href=&#34;https://www.contentful.com/pricing/&#34;&gt;on Contentful&amp;rsquo;s website&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;creating-content&#34;&gt;Creating content&lt;/h3&gt;
&lt;p&gt;In order to start creating content, there are two essential steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We will need to &lt;a href=&#34;https://www.contentful.com/help/contentful-101/#step-2-create-a-space&#34;&gt;set up a new space&lt;/a&gt;. A space is an area where the content will be grouped into a single project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We need to define the &lt;a href=&#34;https://www.contentful.com/help/contentful-101/#step-3-create-the-content-model&#34;&gt;model for our content&lt;/a&gt;. The model is the type and structure that our content will have. For the integration below, we will need a new &amp;ldquo;Page&amp;rdquo; model, that will contain two fields to save the information that we need for a static page: &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt;. We can also include a &lt;code&gt;publishDate&lt;/code&gt; field, just to know when each page was created.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/10/integrating-contentful-with-nuxt/page-content-model.webp&#34; alt=&#34;Page content model. A GUI shows 3 fields: Title, a short text field; Content, a rich text field; and Publish date, a Date &amp;amp; time field.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;integration&#34;&gt;Integration&lt;/h3&gt;
&lt;p&gt;With our space created and our content model ready, it&amp;rsquo;s time to add Contentful to our NuxtJS app. The integration process is quite simple, thanks to the &lt;a href=&#34;https://www.npmjs.com/package/contentful&#34;&gt;JavaScript client library&lt;/a&gt; provided by Contentful. The library is based on the &lt;a href=&#34;https://github.com/axios/axios&#34;&gt;axios&lt;/a&gt; client, allowing it to run on the client as well as on the server, for &lt;a href=&#34;https://nuxtjs.org/docs/concepts/server-side-rendering/&#34;&gt;SSR&lt;/a&gt;. If we have &lt;a href=&#34;https://www.npmjs.com/&#34;&gt;npm&lt;/a&gt; set up, we can add it to our project by running:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm install --save contentful&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The most efficient way to use it across our app and have it ready for client and server-side rendering is to declare a new plugin. All we need to do is create a new file named &lt;code&gt;contentful.js&lt;/code&gt; under our project&amp;rsquo;s &lt;code&gt;plugins&lt;/code&gt; folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; contentful = require(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;contentful&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; config = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  space: process.env.CONTENTFUL_SPACE_ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  accessToken: process.env.CONTENTFUL_API_ACCESS_TOKEN,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;module.exports = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  createClient() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; contentful.createClient(config)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we need to add the new environment variables to our project&amp;rsquo;s &lt;code&gt;.env&lt;/code&gt; file. The values that we need to provide are our &lt;a href=&#34;https://www.contentful.com/help/find-space-id/&#34;&gt;space ID&lt;/a&gt; and the &lt;a href=&#34;https://www.contentful.com/developers/docs/references/authentication/&#34;&gt;access token&lt;/a&gt; for querying the API:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CONTENTFUL_SPACE_ID={our_space_id}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CONTENTFUL_API_ACCESS_TOKEN={our_access_token}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We&amp;rsquo;re all set! Now, we have our plugin ready to use. One neat extra step that we did for this particular project is creating a &lt;code&gt;ContentfulPage&lt;/code&gt; component that will automatically pull the contents from Contentful based on the given entry ID. By doing that, we can simply use the component in all the static pages that we have on our website.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s create the component, containing a simple wrapper for the template section, and an &lt;code&gt;entryId&lt;/code&gt; property that we will use to query the API. We can save it under &lt;code&gt;~/components/ContentfulPage.vue&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;:id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;entryId&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;v-if&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;$fetchState.pending&amp;#34;&lt;/span&gt;&amp;gt;Loading...&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;v-else&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {{ page.fields.title }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;class&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;page-content&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;v-html&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;$md.render(page.fields.content)&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; { createClient } from &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;~/plugins/contentful&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; contentful = createClient()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;ContentfulPage&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    props: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      entryId: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        type: &lt;span style=&#34;color:#038&#34;&gt;String&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        required: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        page: {},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; fetch() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;.page = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; contentful.getEntry(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;.entryId)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This component will asynchronously fetch the entry from Contentful, and display a &amp;ldquo;loading&amp;rdquo; legend while it does so. Once the query is complete, the content will be shown inside the &lt;code&gt;div&lt;/code&gt; element with the &lt;code&gt;page-content&lt;/code&gt; class. The component expects the returned page to have at least two attributes: &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With the new component added to our project, we are ready to create a page (for example, &lt;code&gt;index.vue&lt;/code&gt;) that uses it to render our content like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;contentful-page&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;entry-id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;{index_entry_id}&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; ContentfulPage from &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;~/components/ContentfulPage&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    components: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ContentfulPage,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All we need to do is get the entry ID from the content we created in Contentful, pass it to the &lt;code&gt;entry-id&lt;/code&gt; parameter of the component, and that&amp;rsquo;s it! Our content will be fetched from the API and displayed to the user. It will also work when rendering on the client, as well as for SSR.&lt;/p&gt;
&lt;h3 id=&#34;other-content-types&#34;&gt;Other content types&lt;/h3&gt;
&lt;p&gt;Of course, we&amp;rsquo;re not restricted to using this service to deliver static pages: We can store and deliver blog posts, listings, events, geolocation information, documents, and more. We have several field types that could be used for our content model, including location, media (images, videos), links, or JSON, among others. See the &lt;a href=&#34;https://www.contentful.com/developers/docs/concepts/data-model/&#34;&gt;data model section&lt;/a&gt; of their official documentation for reference.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/10/integrating-contentful-with-nuxt/add-new-field-types.webp&#34; alt=&#34;A GUI selection screen reading &amp;ldquo;Add new field&amp;rdquo; shows 9 field types: Rich text, Text, Number, Date and Time, Location, Media, Boolean, JSON object, and Reference.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;alternatives&#34;&gt;Alternatives&lt;/h3&gt;
&lt;p&gt;There are several alternatives to Contentful out there: &lt;a href=&#34;https://prismic.io/&#34;&gt;Prismic CMS&lt;/a&gt; or &lt;a href=&#34;https://graphcms.com/&#34;&gt;GraphCMS&lt;/a&gt; — which is based entirely in GraphQL — are the most popular. There are also downloadable products, like &lt;a href=&#34;https://www.silverstripe.org/&#34;&gt;SilverStrap CMS&lt;/a&gt;. Their pricing plans are varied, but all of them offer a free community plan for starters or small websites.&lt;/p&gt;
&lt;p&gt;Have you used any other headless CMS not listed here? We would love to hear your comments!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Advanced WordPress customizations</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/11/advanced-wordpress-customizations/"/>
      <id>https://www.endpointdev.com/blog/2020/11/advanced-wordpress-customizations/</id>
      <published>2020-11-27T00:00:00+00:00</published>
      <author>
        <name>Juan Pablo Ventoso</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/11/advanced-wordpress-customizations/wordpress-logo-phone.jpg&#34; alt=&#34;WordPress&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.flickr.com/photos/cdharrison/4289847815/&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://www.flickr.com/photos/cdharrison/&#34;&gt;Chris Harrison&lt;/a&gt; on Flickr, &lt;a href=&#34;https://creativecommons.org/licenses/by/2.0/&#34;&gt;CC BY 2.0&lt;/a&gt;, cropped&lt;/p&gt;
&lt;p&gt;WordPress is the &lt;a href=&#34;https://www.isitwp.com/popular-cms-market-share/&#34;&gt;most popular CMS&lt;/a&gt;, allowing you to create a professional-looking website at a relatively low cost. It also has a really strong community behind it, creating great content and supporting developers across the world.&lt;/p&gt;
&lt;p&gt;But being popular also means being the main target of hacker attacks, and that’s why it’s crucial to keep the CMS, the theme, and all the plugins updated. When the requirements go beyond what WordPress offers on the surface, we need to find an efficient way to add our custom logic into the CMS without interfering with version upgrades, keeping the focus on security.&lt;/p&gt;
&lt;h3 id=&#34;custom-css-and-javascript&#34;&gt;Custom CSS and JavaScript&lt;/h3&gt;
&lt;p&gt;A pretty common scenario in WordPress consists of installing a theme that fits most of our requirements and writing an extra layer of functionality over it to get that custom look and user experience we are looking for. Updating the theme files means that we cannot easily upgrade or change the theme without backing up our changes, and then restoring them after the upgrade, so that’s definitely not a good approach.&lt;/p&gt;
&lt;p&gt;To make our way around this issue, some themes offer a section to add custom JavaScript or CSS rules. But sometimes we need change themes in the middle of our developing process, so I usually rely on plugins to make my frontend changes. One simple, straightforward plugin I usually install to add custom styles and frontend scripts is &lt;a href=&#34;https://wordpress.org/plugins/custom-css-js/&#34;&gt;Simple Custom CSS and JS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/11/advanced-wordpress-customizations/wordpress-simple-custom-css-js.jpg&#34; alt=&#34;Simple Custom CSS and JS&#34;&gt;&lt;/p&gt;
&lt;p&gt;It has several customization options I usually need for SEO purposes, including the possibility to create several independent code segments and load each one in a different section (header/​footer) to improve loading speed. We can also include our custom content as embedded source/​styles or as external references. It also includes an editor with syntax highlighting, and allows adding custom content to the WordPress admin section.&lt;/p&gt;
&lt;h3 id=&#34;custom-php-code&#34;&gt;Custom PHP code&lt;/h3&gt;
&lt;p&gt;Another thing I usually need when customizing a WordPress website is the ability to run my own PHP code inside a WordPress page or post. That is not allowed by the CMS by default, but like most things in WordPress, there is a plugin that will make our lives easier: It’s called &lt;a href=&#34;https://wordpress.org/plugins/insert-php-code-snippet/&#34;&gt;Insert PHP Code Snippet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can create your own PHP routines as snippets that can be inserted with shortcodes into a WordPress page, posts, template, or whenever you can add a shortcode. This will allow running any custom backend code anywhere on the website.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/11/advanced-wordpress-customizations/wordpress-custom-php-snippet.jpg&#34; alt=&#34;Custom PHP Code Snippet&#34;&gt;&lt;/p&gt;
&lt;p&gt;To run the code on a page or post, all that needs to be done is pasting the shortcode on the content with the “PHP” button that appears on the button bar on the editor:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/11/advanced-wordpress-customizations/wordpress-custom-php-snippet-shortcode.jpg&#34; alt=&#34;Shortcode example&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;custom-hooks&#34;&gt;Custom hooks&lt;/h3&gt;
&lt;p&gt;When our logic needs to be fired up from an event on the CMS, or if we need to make changes to the default WordPress behavior or data, we will need to use the &lt;a href=&#34;https://developer.wordpress.org/reference/functions/add_action/&#34;&gt;add_action() function&lt;/a&gt; or the &lt;a href=&#34;https://developer.wordpress.org/reference/functions/add_filter/&#34;&gt;add_filter() function&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;add_action()&lt;/code&gt; allows us to execute a PHP function on specific points of the WordPress execution, for example when a post is created or commented, or when a user is created. A full list of actions is available &lt;a href=&#34;https://codex.wordpress.org/Plugin_API/Action_Reference&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;add_filter()&lt;/code&gt; allows us to update information associated with WordPress, for example, to set a custom CSS class for the body or to replace the login button text. A full list of filters is available &lt;a href=&#34;https://codex.wordpress.org/Plugin_API/Filter_Reference&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following example sends an email to the webmaster when a new comment is created, using the &lt;code&gt;add_action()&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;email_comment&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  wp_mail(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;webmaster@website.com&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;New comment&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;New comment posted on the website&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;add_action(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;comment_post&amp;#39;&lt;/span&gt;,&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;email_comment&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here’s another example that allows to perform a string replacement across the website by using the &lt;code&gt;add_filter()&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;replace_text&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$text&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; str_replace(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Text to search&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Text to replace with&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$text&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;add_filter(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;gettext&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;replace_text&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can add these code segments using the PHP Snippet plugin (recommended) or using the &lt;code&gt;functions.php&lt;/code&gt; file included in our theme. This last option is not recommended since it has the difficulty we discussed above about missing our custom content after upgrading the theme.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;WordPress is a great CMS with an elegant backend and a big list of themes and plugins we can use. But before we start adding custom code, we need to make sure it will persist after upgrading everything else to the latest version.&lt;/p&gt;
&lt;p&gt;The techniques we saw in this post are meant for websites that only need some small custom changes or improvements. If we need to include a complete layer of logic into a website, it’s always recommended to write a custom plugin from scratch, if there is nothing out there that serves the purpose. But that’s material for another blog post!&lt;/p&gt;
&lt;p&gt;At End Point, we build web solutions in WordPress and many other technologies, keeping security and high standards in mind. &lt;a href=&#34;/contact/&#34;&gt;Contact us&lt;/a&gt; if you want to find out more.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Craft: A CMS for developers</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/10/craft-a-cms-for-developers/"/>
      <id>https://www.endpointdev.com/blog/2020/10/craft-a-cms-for-developers/</id>
      <published>2020-10-31T00:00:00+00:00</published>
      <author>
        <name>Kevin Campusano</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/banner.png&#34; alt=&#34;Craft CMS banner&#34;&gt;&lt;/p&gt;
&lt;p&gt;As a software engineer, I thrive and thoroughly enjoy working on fully custom software products, applications conceived to model and help in the execution of some business process and that are built from the ground up by a team of developers.&lt;/p&gt;
&lt;p&gt;Such projects are often complex and expensive, though, and for some clients, they can be overkill. Some clients come up with requirements that are better served by off-the-shelf software solutions. One group of such solutions are &lt;a href=&#34;https://en.wikipedia.org/wiki/Content_management_system&#34;&gt;content management systems (CMS)&lt;/a&gt;. As a rule of thumb, if a client wants a website whose main purpose is to showcase some content, their brand or image, and custom business logic requirements are limited, then chances are that a CMS will fit the bill nicely.&lt;/p&gt;
&lt;p&gt;Lately we’ve been using the &lt;a href=&#34;https://craftcms.com/&#34;&gt;Craft CMS&lt;/a&gt; for a client that meets the aforementioned criteria, and I gotta say, I’ve been pleasantly surprised by the developer experience it offers.&lt;/p&gt;
&lt;p&gt;Unlike most of the technology and products we discuss in our blog, Craft CMS is not &lt;a href=&#34;https://opensource.org/osd&#34;&gt;Open Source&lt;/a&gt; or &lt;a href=&#34;https://www.gnu.org/philosophy/free-sw.html&#34;&gt;Free Software&lt;/a&gt;. The source code is readily available in &lt;a href=&#34;https://github.com/craftcms/cms&#34;&gt;GitHub&lt;/a&gt; for anybody to use, study, and modify, but commercial use of it is restricted and certain features are exclusive to a so-called “Pro” edition. Learn more by reading their &lt;a href=&#34;https://github.com/craftcms/cms/blob/develop/LICENSE.md&#34;&gt;license&lt;/a&gt; and their &lt;a href=&#34;https://craftcms.com/pricing&#34;&gt;pricing structure&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The features that we will discuss in this article are all part of the no-charge “Solo” edition of Craft CMS 3 that can be used for noncommercial websites.&lt;/p&gt;
&lt;p&gt;In this article I’m going to talk through a few of the key aspects of Craft that make me think that it’s really a CMS made for developers. Let’s get started:&lt;/p&gt;
&lt;h3 id=&#34;craft-is-easy-to-get-up-and-running&#34;&gt;Craft is easy to get up and running&lt;/h3&gt;
&lt;p&gt;Craft is just a PHP application. And it is as typical as modern PHP applications go, capable of being initially set up with Composer and of running on top of a MySQL database (it also supports Postgres!) and the Apache web server. &lt;a href=&#34;https://craftcms.com/docs/3.x/console-commands.html&#34;&gt;It can all be done via console&lt;/a&gt; too, if that’s how you roll.&lt;/p&gt;
&lt;p&gt;If you already have a box with Apache, PHP, MySQL and Composer, it all amounts to little more than creating a MySQL database for Craft, &lt;code&gt;composer install&lt;/code&gt;ing the Craft package, sorting out some permissions, running &lt;code&gt;php craft setup&lt;/code&gt;, following the prompts, and finally, configuring a virtual host in Apache to serve the &lt;code&gt;web&lt;/code&gt; directory from inside where Craft was installed.&lt;/p&gt;
&lt;p&gt;All of this is explained in Craft’s &lt;a href=&#34;https://craftcms.com/docs/3.x/installation.html&#34;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;craft-is-easy-to-put-in-containers&#34;&gt;Craft is easy to put in containers&lt;/h3&gt;
&lt;p&gt;For ease of development and project bootstrapping, I’ve created a containerized setup with Docker and &lt;a href=&#34;https://docs.docker.com/compose/&#34;&gt;Docker Compose&lt;/a&gt; that encapsulates some infrastructure tailored to my development needs. You can get the relevant files &lt;a href=&#34;https://github.com/megakevin/craft-cms-docker-bootstrap&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to follow along, clone that repo, and you’ll end up with this file structure (as shown by the &lt;code&gt;tree&lt;/code&gt; command):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── apache_config
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── 000-default.conf
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── docker-compose.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── README.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 directory, 4 files&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This setup includes two containers: one for running Apache and Craft, and another for running MySQL. The &lt;code&gt;apache_config/000-default.conf&lt;/code&gt; contains some Apache VirtualHost configuration for serving the site. &lt;code&gt;docker-compose.yml&lt;/code&gt; defines the whole infrastructure: both containers, a network that they use to talk to each other, and a volume to persist MySQL database files. The &lt;code&gt;Dockerfile&lt;/code&gt; is the definition of the image for the container that runs Apache and Craft.&lt;/p&gt;
&lt;p&gt;Feel free to explore the files; I’ve made sure to comment them so that they are easy to understand and modify as you see fit.&lt;/p&gt;
&lt;p&gt;Note: If you want to run this setup, be sure to change the &lt;code&gt;ServerAdmin&lt;/code&gt; value in &lt;code&gt;apache_config/000-default.conf&lt;/code&gt;, and the &lt;code&gt;USER&lt;/code&gt;, &lt;code&gt;UID&lt;/code&gt;, and &lt;code&gt;GID&lt;/code&gt; values in &lt;code&gt;docker-compose.yml&lt;/code&gt; under &lt;code&gt;services &amp;gt; web &amp;gt; build &amp;gt; args&lt;/code&gt; according to your environment and user account information.&lt;/p&gt;
&lt;p&gt;If you have Docker and Docker Compose installed in your machine, you can go to the directory just created by the &lt;code&gt;git clone&lt;/code&gt; and:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;docker-compose up&lt;/code&gt; to set up the infrastructure. You will see Docker and Docker Compose creating the image defined in &lt;code&gt;Dockerfile&lt;/code&gt; and the containers defined in &lt;code&gt;docker-compose.yml&lt;/code&gt;. Then the logs of the various containers will start showing. It you want to run this in the background, use &lt;code&gt;docker-compose up -d&lt;/code&gt; instead and it will give you control of the terminal immediately after it’s done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;docker-compose exec web bash&lt;/code&gt; to connect to the &lt;code&gt;web&lt;/code&gt; container. This is the container that has Craft’s code and is running Apache. You’ll be “logged into” the container and be placed in &lt;code&gt;/var/www&lt;/code&gt;. This is the directory where we will install Craft.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once in there, run &lt;code&gt;composer create-project craftcms/craft ./install&lt;/code&gt; to install Craft with Composer. In other words, it will download all of the files that Craft needs to run. You should see something like this at the end:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; @php craft setup/welcome
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ______ .______          ___       _______ .___________.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  /      ||   _  \        /   \     |   ____||           |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; |  ,----&amp;#39;|  |_)  |      /  ^  \    |  |__   `---|  |----`
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; |  |     |      /      /  /_\  \   |   __|      |  |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; |  `----.|  |\  \----./  _____  \  |  |         |  |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  \______|| _| `._____/__/     \__\ |__|         |__|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     A       N   E   W       I   N   S   T   A   L   L
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;               ______ .___  ___.      _______.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              /      ||   \/   |     /       |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             |  ,----&amp;#39;|  \  /  |    |   (----`
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             |  |     |  |\/|  |     \   \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             |  `----.|  |  |  | .----)   |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              \______||__|  |__| |_______/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Generating an application ID ... done (CraftCMS--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Generating a security key ... done (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Welcome to Craft CMS! Run the following command if you want to setup Craft from your terminal:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    /var/www/install/craft setup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After that’s done, use this bit of bash black magic:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#038&#34;&gt;shopt&lt;/span&gt; -s dotglob; mv -v ./install/* .)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command moves all the files that composer just downloaded into &lt;code&gt;/var/www/install&lt;/code&gt; out from there and into &lt;code&gt;/var/www&lt;/code&gt;. Then &lt;code&gt;rmdir install&lt;/code&gt; to remove the &lt;code&gt;install&lt;/code&gt; directory because we no longer need it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now do &lt;code&gt;php craft setup&lt;/code&gt; to use Craft’s CLI to set up the site, its configuration, and its database structure. Just follow the prompts. When it asks you for database configuration, choose &lt;code&gt;mysql&lt;/code&gt; as the database driver, set &lt;code&gt;mysql&lt;/code&gt; as the database server name (because that’s the name we’ve given it in our &lt;code&gt;docker-compose.yml&lt;/code&gt;), and use the environment variables defined in &lt;code&gt;docker-compose.yml&lt;/code&gt; around lines 14 to 17 for the rest of the values. You can change these to whatever you want as long as you make sure that it coincides with how you defined your MySQL database container in the &lt;code&gt;docker-compose.yml&lt;/code&gt; file. The prompts should look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Which database driver are you using? [mysql,pgsql,?]: mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database server name or IP address: [127.0.0.1] mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database port: [3306]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database username: [root] craft
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database password:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database name: craft_demo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Database table prefix:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Testing database credentials ... success!
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Saving database credentials to your .env file ... done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, sort out some Craft file permission requirements with this command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chmod -R o+w config storage web/cpresources&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These are directories that Craft needs write access to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now start up Apache with &lt;code&gt;sudo service apache2 start&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that’s it! Open a browser to &lt;code&gt;localhost&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt; and you should see your Craft 3 homepage:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/welcome_to_craft.jpg&#34; alt=&#34;Welcome to Craft browser screenshot&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can start playing with the control panel or the &lt;code&gt;templates/index.twig&lt;/code&gt; right away.&lt;/p&gt;
&lt;h3 id=&#34;crafts-design-makes-sense&#34;&gt;Craft’s design makes sense&lt;/h3&gt;
&lt;p&gt;When it comes to content modeling, Craft offers a set of abstractions that make sense. The main concepts to understand are &lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html&#34;&gt;sections and entries&lt;/a&gt;. Entries are the main pieces of content. An “article” in a news site or a “post” in a blog. Sections are the way Craft groups entries together. They are useful when your site has multiple streams of content. You can, for example, have a site where you publish news, opinion pieces, and random thoughts. With Craft, that would translate neatly into three separate sections, each one with its own type of entries.&lt;/p&gt;
&lt;p&gt;Craft also allows you to set up &lt;a href=&#34;https://craftcms.com/docs/3.x/fields.html&#34;&gt;custom fields&lt;/a&gt; for every type of entry. For example, the entries on your news section may need to include a link to the original source of the news, while your opinion pieces need a short description instead. You can configure your entries using custom fields so that they include the data that makes sense for your use case.&lt;/p&gt;
&lt;p&gt;Let’s see what that looks like in concrete terms. Now that we have a Craft instance running in &lt;code&gt;localhost&lt;/code&gt;, go to &lt;code&gt;localhost/admin&lt;/code&gt; in your browser. You should see Craft’s control panel. Click on the “Settings” option in the navigation bar to the left of the screen, then select the “Sections” item under “Content”:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_settings_sections.png&#34; alt=&#34;Screenshot of Control Panel &amp;gt; Settings &amp;gt; Sections&#34;&gt;&lt;/p&gt;
&lt;p&gt;Next, click on the “+ New Section” button by the top of the screen and you’ll be shown the section creation form. We will create a “News” section so let’s fill in the form like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_new_section.png&#34; alt=&#34;Screenshot of Create a new section&#34;&gt;&lt;/p&gt;
&lt;p&gt;The “Name” and “Handle” fields are pretty self explanatory. The “&lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html#section-types&#34;&gt;Section Type&lt;/a&gt;” is a concept we haven’t discussed yet. “&lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html#channels&#34;&gt;Channel&lt;/a&gt;” is the most appropriate for a news section, which is a stream of multiple entries with the same structure.&lt;/p&gt;
&lt;p&gt;There are other types: “&lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html#singles&#34;&gt;Single&lt;/a&gt;” is a type which you would use for entries that are unique, like a home or contact page. For sections of type “Single”, there’s generally one single entry that fits in them. This is unlike “Channels” which fit multiple entries. The other section type is “&lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html#structures&#34;&gt;Structure&lt;/a&gt;”, which also accommodates multiple entries, but rather than a stream of ever-growing content, it’s more appropriate for similar entries that share a certain theme. A “Structure” section type is appropriate for things like services offered or projects in a portfolio.&lt;/p&gt;
&lt;p&gt;Learn all about entries, sections, section types, and more in &lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html&#34;&gt;Craft’s official docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now that we’ve filled the form, click the “Save and edit entry types” button. This has created our new “News” section and defined the “&lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html#entry-types&#34;&gt;entry type&lt;/a&gt;” that this section will be able to contain. The control panel now shows this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_entry_type.png&#34; alt=&#34;Screenshot of The section’s default entry type&#34;&gt;&lt;/p&gt;
&lt;p&gt;In Craft, a section can contain multiple types of entries. For our purposes with the news section, though, just the default one is enough. Click on it, and you’ll see an editor where you can select fields that make up that entry type:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_entry_type_fields.png&#34; alt=&#34;Screenshot of The entry type’s fields&#34;&gt;&lt;/p&gt;
&lt;p&gt;The editor I mentioned before is below the “Field Layout” title. Here’s where we can pick and choose which fields make up the entries for the “News” section. We have a fresh installation of Craft though, so we don’t have any fields. Let’s create a few by going to Settings &amp;gt; Fields.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_new_fields.png&#34; alt=&#34;Screenshot of Defining new fields&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is where we can define new fields to be used for our entries throughout the site. Click the “+ New Field” button near the top of the screen and you’ll be presented with the field creation form where you can specify all manner of details. For now, we just care about “Name”, “Handle” and “Field Type”. Let’s create three fields:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One named “Heading” with a type of “Plain Text”.&lt;/li&gt;
&lt;li&gt;One named “Body” with a type of “Plain Text”.&lt;/li&gt;
&lt;li&gt;One named “Source” with a type of “URL”.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should end up with something like this in the control panel’s “Fields” page (&lt;code&gt;localhost/admin/settings/fields&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_three_fields.png&#34; alt=&#34;Screenshot of New fields ready&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now, if we go back to our “News” section’s default entry type at &lt;code&gt;http://localhost/admin/settings/sections/1/entrytypes/1&lt;/code&gt; or Settings &amp;gt; Sections &amp;gt; News &amp;gt; Entry Types &amp;gt; News…&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_field_layout.png&#34; alt=&#34;Screenshot of New fields for News entries&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can see how the new fields that we just created are present in the “Field Layout” panel. In order to make these fields available for our “News” entries, we just need to drag them into the box named “Content” inside the greyish area.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_field_layout_applied.png&#34; alt=&#34;Screenshot of New fields for News entries assigned&#34;&gt;&lt;/p&gt;
&lt;p&gt;Click the “Save” button at the top, and that’s all it takes to set up a “Channel” section, an entry type for it, and a few fields.&lt;/p&gt;
&lt;p&gt;Now that we’ve set up the blueprints for them, let’s actually create a few entries in the “News” section. To do so, click on the “Entries” link in the navigation bar to the left which should’ve revealed itself by now, and you’ll see this screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_entries.png&#34; alt=&#34;Screenshot of The entries screen&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you’re used to CMS back ends, this is pretty familiar. In this screen you can create new entries and browse existing ones.&lt;/p&gt;
&lt;p&gt;Click the big red “+ New Entry” button and select “News” in the resulting pop-up menu. You should see a form with the fields that we defined in the “Field Layout” panel during previous steps. Feel free to create a few news entries. I’ve created these two:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/control_panel_entries_created.png&#34; alt=&#34;Screenshot of Entries created&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;craft-gives-you-complete-freedom-over-your-front-end&#34;&gt;Craft gives you complete freedom over your front end&lt;/h3&gt;
&lt;p&gt;Most CMSs can be thought of as having two components: a front end and a back end. The back end is where content is authored and the front end is where the style and structure in which the content is presented. In Craft, most of the effort has gone into creating a solid, highly customizable back end.&lt;/p&gt;
&lt;p&gt;As we’ve just seen, Craft comes out of the box with a back end control panel where site administrators and content creators can author new content. As far as front end goes though, Craft has nothing. For a developer well versed in front end web technologies, this is freeing and transformative.&lt;/p&gt;
&lt;p&gt;Craft makes no assumption and makes no decision for you when it comes to developing your site’s look and feel. It gets out of your way and lets you do your job. There’s no concept of “theme”. There’s no obscure framework to learn and integrate into. There’s no proprietary templating language to struggle with. In Craft, you are completely free to write HTML, CSS and JS as you see fit to obtain your desired effect for your site.&lt;/p&gt;
&lt;p&gt;You can develop templates using the tried and true &lt;a href=&#34;https://twig.symfony.com/&#34;&gt;Twig&lt;/a&gt; templating engine, which, if you have some experience with PHP, you’ve most likely already encountered and worked with. All the content created in the back end is exposed to the Twig templates via objects. Let’s see how.&lt;/p&gt;
&lt;p&gt;First we need to specify a template for our sections. Continuing with our example, let’s assign a template to our “News” section. Go to Settings &amp;gt; Sections &amp;gt; News and scroll down to find the “Site Settings” area. In the table there, type &lt;code&gt;news&lt;/code&gt; into the “Template” column. Now go to the &lt;code&gt;template&lt;/code&gt; directory where craft was installed and create a new &lt;code&gt;news.twig&lt;/code&gt; file. The contents can be simple, like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;lang&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;charset&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;content&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;{{ entry.title }} - My Craft Demo&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;{{ entry.heading }}&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ entry.body }}&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;{{ entry.source }}&amp;#34;&lt;/span&gt;&amp;gt;Source&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The only noteworthy aspect of this template is how we are injecting the data that we defined in the back end into this template. We use double curly brackets to reference the &lt;code&gt;entry&lt;/code&gt; variable. This is provided to Twig by Craft and contains all the fields that we defined for our entries in the “News” section.&lt;/p&gt;
&lt;p&gt;With that done, save and visit any of the entries you created and you’ll see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/website_entry.png&#34; alt=&#34;Screenshot of Our first entry&#34;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, this entry’s URL is &lt;code&gt;localhost/news/i-just-learned-that-craft-uses-twig&lt;/code&gt;. Yours will obviously differ depending on the title (and slug) that you gave them.&lt;/p&gt;
&lt;p&gt;What this example lacks in complexity, it more than makes up for in potential. This is a plain old HTML document that we’ve created, with a Twig template, of course. This is the complete freedom that I like about Craft. From this point on, you can do whatever you want in terms of front end development: use whatever CSS or JavaScript framework or library you want, organize your template files in a way that makes sense to you, your team, and your website, etc. The sky is the limit.&lt;/p&gt;
&lt;p&gt;That’s good for individual news pages. But now let’s try to link to them from the homepage. To do so, we need to edit the &lt;code&gt;templates/index.twig&lt;/code&gt; file. Around line 174, remove the &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; that’s there along with all its &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;s and put this instead:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{% set entries = craft.entries().section(&amp;#39;news&amp;#39;).all() %}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {% for entry in entries %}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;{{ entry.url }}&amp;#34;&lt;/span&gt;&amp;gt;{{ entry.title }}&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {% endfor %}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, we leverage Twig’s templating engine capabilities, sprinkled with some of Craft’s features to obtain a list of all the entries in our “News” section. Then, we iterate over them to render links.&lt;/p&gt;
&lt;p&gt;Effectively, Craft enhances what you can do with Twig by exposing an API for accessing the data that exists in the CMS back end.&lt;/p&gt;
&lt;p&gt;If you’re familiar with any sort of templating language like those included in most web application frameworks like &lt;a href=&#34;https://rubyonrails.org/&#34;&gt;Ruby on Rails&lt;/a&gt;, &lt;a href=&#34;https://symfony.com/&#34;&gt;Symfony&lt;/a&gt;, &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-3.1&#34;&gt;ASP.NET Core MVC&lt;/a&gt;, etc., you’ll probably feel right at home with this.&lt;/p&gt;
&lt;p&gt;Here’s what the homepage looks like now:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/10/craft-a-cms-for-developers/homepage_with_links.jpg&#34; alt=&#34;Screenshot of Homepage is ready&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can click on any of the links and they will take you to the specific entry page.&lt;/p&gt;
&lt;p&gt;You can learn more about querying entries in Craft’s &lt;a href=&#34;https://craftcms.com/docs/3.x/entries.html#editing-entries&#34;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;craft-is-cool-&#34;&gt;Craft is cool 🕶️&lt;/h3&gt;
&lt;p&gt;So, in conclusion, I’ve found that Craft is a cool tool to have in the toolbox. It is a full-fledged CMS with tons of customization opportunities for how to model and organize the content and data of your site. When it comes to developing the front end, though, it gets out of your way and lets you do your job. That, to me, is a big win.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Drupal — rapid development</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/05/drupal-rapid-development/"/>
      <id>https://www.endpointdev.com/blog/2017/05/drupal-rapid-development/</id>
      <published>2017-05-26T00:00:00+00:00</published>
      <author>
        <name>Piotr Hankiewicz</name>
      </author>
      <content type="html">
        &lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2017/05/drupal-rapid-development/image-0.gif&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;279&#34; data-original-width=&#34;640&#34; src=&#34;/blog/2017/05/drupal-rapid-development/image-0.gif&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Here at End Point, we had the pleasure to be a part of multiple &lt;strong&gt;Drupal&lt;/strong&gt; &lt;strong&gt;6&lt;/strong&gt;, &lt;strong&gt;7&lt;/strong&gt; and &lt;strong&gt;8&lt;/strong&gt; projects. Most of our clients wanted to use the latest Drupal version, to have a long term support, stable platform.&lt;/p&gt;
&lt;p&gt;A few years ago, I already had big experience with PHP itself and other, various PHP frameworks like WordPress, Joomla! or TYPO3. I was happy to use all of them, but then one of our clients asked us for a simple Drupal 6 task. That’s how I started my Drupal journey which continues until now.&lt;/p&gt;
&lt;p&gt;To be honest, I had a difficult start, it was different, new and pretty inscrutable for me. After a few days of reading documentation and playing with the system I was ready to do some simple work. Here, I wanted to share my thoughts about Drupal and tell you why &lt;strong&gt;I LOVE!&lt;/strong&gt; it.&lt;/p&gt;
&lt;h3 id=&#34;low-learning-curve&#34;&gt;Low learning curve&lt;/h3&gt;
&lt;p&gt;It took, of course, a few months until I was ready to build something more complex, but it really takes a few days only to be ready for simple development. It’s not only about Drupal, but also PHP, it’s much cheaper to maintain and extend a project. Maybe it’s not so important with smaller projects, but definitely important for massive code bases. Programmers can jump in and start being &lt;strong&gt;productive&lt;/strong&gt; really quick.&lt;/p&gt;
&lt;h3 id=&#34;great-documentation&#34;&gt;Great documentation&lt;/h3&gt;
&lt;p&gt;Drupal documentation is well structured and constantly developed, usually you can find what you need within a few minutes. It’s critical and must have for any other framework and not so common unfortunately.&lt;/p&gt;
&lt;h3 id=&#34;big-community&#34;&gt;Big community&lt;/h3&gt;
&lt;p&gt;The Drupal community is one of the biggest IT communities I have ever encountered. They extend, fix and document the Drupal core regularly. Most of them have their other jobs and work on this project just for fun and with passion.&lt;/p&gt;
&lt;h3 id=&#34;its-free&#34;&gt;It’s free&lt;/h3&gt;
&lt;p&gt;It’s an open source project, that’s one of the biggest pros here. You can get it for free, you can get support &lt;strong&gt;for free&lt;/strong&gt;, you can join the community for free too (:)).&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2017/05/drupal-rapid-development/image-1-big.png&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;318&#34; data-original-width=&#34;318&#34; height=&#34;200&#34; src=&#34;/blog/2017/05/drupal-rapid-development/image-1.png&#34; width=&#34;200&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h3 id=&#34;modules&#34;&gt;Modules&lt;/h3&gt;
&lt;p&gt;On the official Drupal website you can find tons of free plugins/modules. It’s a time and money saver, you don’t need to reinvent the wheel for every new widget on your website and focus on fireworks.&lt;/p&gt;
&lt;p&gt;Usually you can just go there and find a proper component. E-commerce shop? Slideshow? Online classifieds website? No problem! It’s all there.&lt;/p&gt;
&lt;h3 id=&#34;php7-support&#34;&gt;PHP7 support&lt;/h3&gt;
&lt;p&gt;I can often hear from other developers that PHP is slow, well, it’s not the Road Runner, but come on, unless you are Facebook (and I think that they, correct me if I’m wrong, still use PHP :)) it’s just OK to use PHP.&lt;/p&gt;
&lt;p&gt;Drupal fully supports PHP7.&lt;/p&gt;
&lt;p&gt;With PHP7 it’s much faster, better and safer. To learn more: &lt;a href=&#34;https://pages.zend.com/rs/zendtechnologies/images/PHP7-Performance%20Infographic.pdf&#34;&gt;https://pages.zend.com/rs/zendtechnologies/images/PHP7-Performance%20Infographic.pdf&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the infographic you can see that &lt;strong&gt;PHP7&lt;/strong&gt; is much faster than &lt;strong&gt;Ruby&lt;/strong&gt;, &lt;strong&gt;Perl&lt;/strong&gt; and &lt;strong&gt;Python&lt;/strong&gt; when you try to render a Mandelbrot fractal. In general, you definitely can’t say that PHP is slow, same as Drupal.&lt;/p&gt;
&lt;h3 id=&#34;rest-api-support&#34;&gt;REST API support&lt;/h3&gt;
&lt;p&gt;Drupal has the built in, ready to use API system. In a few moments you can spawn a new API endpoint for you application. You don’t need to implement a whole API by yourself, I did it a few times in multiple languages, believe me, it’s problematic.&lt;/p&gt;
&lt;h3 id=&#34;perfect-for-a-backend-system&#34;&gt;Perfect for a backend system&lt;/h3&gt;
&lt;p&gt;Drupal is a perfect candidate for a backend system. Let’s imagine that you want to build a beautiful, mobile application. You want to let editors, other people to edit content. You want to grab this content through the API. It’s easy as pie with Drupal.&lt;/p&gt;
&lt;p&gt;Drupal’s web interface is stable and easy to use.&lt;/p&gt;
&lt;h3 id=&#34;power-of-taxonomies&#34;&gt;Power of taxonomies&lt;/h3&gt;
&lt;p&gt;Taxonomies are, really basically, just dictionaries. The best thing about taxonomies is that you don’t need to touch code to play with them.&lt;/p&gt;
&lt;p&gt;Let’s say that on your website you want to create a list of states in the USA. Using most of the frameworks you need to ask your developer/technical person to do so. With taxonomies you just need a few clicks and that’s it, you can put in on your website. That’s sweet, not only for non technical person, but for us, developers as well. Again, you can focus on actually making the website attractive, rather than spending time on things that can be automated.&lt;/p&gt;
&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Of course, Drupal is not perfect, but it’s undeniably a great tool. Mobile application, single page application, corporate website—​there are no limits for this content management system. And actually, it is, in my opinion, the best tool to manage your content and it does not mean that you need to use Drupal to present it. You can create a &lt;strong&gt;mobile&lt;/strong&gt;, &lt;strong&gt;ReactJS&lt;/strong&gt;, &lt;strong&gt;AngularJS&lt;/strong&gt;, &lt;strong&gt;VueJS&lt;/strong&gt; application and combine it with Drupal easily.&lt;/p&gt;
&lt;p&gt;I hope that you’ve had a good reading and wish to hear back from you! Thanks.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Testing Django Applications</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/12/testing-django-applications/"/>
      <id>https://www.endpointdev.com/blog/2015/12/testing-django-applications/</id>
      <published>2015-12-14T00:00:00+00:00</published>
      <author>
        <name>Zdeněk Maxa</name>
      </author>
      <content type="html">
        &lt;p&gt;This post summarizes some observations and guidelines originating from introducing the &lt;a href=&#34;http://pytest.org/latest/&#34;&gt;pytest&lt;/a&gt; unit testing framework into our CMS (Content Management System) component of the &lt;a href=&#34;https://www.visionport.com/&#34;&gt;Liquid Galaxy&lt;/a&gt;.
Our Django-based CMS allows users to define scenes, presentations and assets (StreetView, Earth tours, panos, etc) to be displayed on the
&lt;a href=&#34;https://www.youtube.com/watch?v=2VonXkA6YYg&#34;&gt;Liquid Galaxy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of this blog post is to capture my &lt;a href=&#34;https://www.djangoproject.com/&#34;&gt;Django&lt;/a&gt; and testing study points, summarize useful resource links as well as to itemize some guidelines for implementing tests for newcomers to the project. It also provides a comparison between Python’s standard &lt;a href=&#34;https://docs.python.org/2/library/unittest.html&#34;&gt;unittest library&lt;/a&gt; and the aforementioned pytest. Its focus is on Django database interaction.&lt;/p&gt;
&lt;h3 id=&#34;versions-of-software-packages-used&#34;&gt;Versions of software packages used&lt;/h3&gt;
&lt;p&gt;This post describes some of our experiences at End Point in designing and working on comprehensive QA/CI facilities for a new system which is closely related to the Liquid Galaxy.&lt;/p&gt;
&lt;p&gt;The experiments were done on Ubuntu Linux 14.04:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;python (2.7.6) and its corresponding version of unittest&lt;/li&gt;
&lt;li&gt;django 1.7 (current recent is 1.9 but our CMS uses still 1.7 version)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pytest-django.readthedocs.org/en/latest/&#34;&gt;pytest-django 2.8.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;pytest 2.7.2 (with py 1.4.30)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/virtualenv&#34;&gt;virtualenv 13.1.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://factoryboy.readthedocs.org/en/latest/&#34;&gt;factory_boy 2.6.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;testing-django-applications&#34;&gt;Testing Django Applications&lt;/h3&gt;
&lt;p&gt;We probably don’t need to talk much about the importance of testing. Writing tests along with the application code has become standard over the years. Surely, developers may fall into a trap of their own prejudice when creating testing conditions which would still result in faulty software but the likelihood of buggy software is certainly higher on a code that has no QA measures. If the code works and is untested, it means it works by accident, they say.&lt;/p&gt;
&lt;p&gt;As a rule of thumb, unit tests should be very brief testing items seldom interacting with any external services such as the database. Integration tests on the other hand often communicate with external components.&lt;/p&gt;
&lt;p&gt;This post will heavily reference an example minimal Django application written for the purpose of experimenting on
&lt;a href=&#34;https://github.com/zdenekmaxa/examples/tree/master/python/django-testing&#34;&gt;Django testing&lt;/a&gt;.
Its README file contains some set up and requirement notes. Also, I am not going to list (m)any code snippets here but rather reference the functional application and its test suite. Hence the points below qualify for more or less assorted little topics or observations.&lt;/p&gt;
&lt;p&gt;In order to benefit from this post, it will be helpful to follow the README and interact (run tests that is) with the demo django-testing application.&lt;/p&gt;
&lt;h3 id=&#34;basic-django-unittest-versus-pytest-basic-examples&#34;&gt;Basic Django unittest versus pytest basic examples&lt;/h3&gt;
&lt;p&gt;This pair of test modules shows the differences between Django TestCase (unittest) and pytest-django (pytest) frameworks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/zdenekmaxa/examples/blob/master/python/django-testing/tests/test_unittest_style.py&#34;&gt;test_unittest_style.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The base Django TestCase class derives along this tree:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    django.test.TestCase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        django.test.TransactionTestCase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            django.test.SimpleTestCase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                unittest.TestCase&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Django adds (among any other aspects) handling of database, the documentation is
&lt;a href=&#34;https://docs.djangoproject.com/en/1.7/topics/testing/overview/&#34;&gt;here&lt;/a&gt;, on top of the Python standard unittest library.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/zdenekmaxa/examples/blob/master/python/django-testing/tests/test_pytest_style.py&#34;&gt;test_pytest_style.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;this is a pytest style implementation of the same tests and pytest-django plug-in adds, among other features, Django database handling support.&lt;/p&gt;
&lt;p&gt;The advantage of unittest is that it comes with the Python installation—​it’s a standard library. That means that one does not have to install anything for writing tests, unlike pytest which is a third-party library and needs to be installed separately. While the absence of additional installation is certainly a plus, it’s dubious whether being a part of Python distribution is a benefit. I seem to recall Guido Van Rossum during Europython 2010 having said the the best thing for pytest is not being part of the Python standard set of libraries for its lively development and evolution would be slowed down by the inclusion.&lt;/p&gt;
&lt;p&gt;There are very good talks and articles summarizing advantages of pytest. For me personally, the reporting of error
context is supreme. No boiler-plate (no inheritance), using plain Python asserts instead of
many &lt;a href=&#34;https://docs.python.org/2/library/unittest.html#assert-methods&#34;&gt;assert* methods&lt;/a&gt; and
flexibility (function, class) are other big plus points&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://speakerdeck.com/pelme/testing-django-applications-with-py-dot-test-europython-2013&#34;&gt;Testing Django applications with py.test (EuroPython 2013)&lt;/a&gt; (very good)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://prezi.com/knnfwewm7lvw/pytest-rapid-multi-purpose-testing/&#34;&gt;pytest presentation from its author&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.pydanny.com/pytest-no-boilerplate-testing.html&#34;&gt;very brief pytest introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://mathieu.agopian.info/presentations/2015_06_djangocon_europe/&#34;&gt;switch to pytest&lt;/a&gt;, rich features descriptions, has some Django touches&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As the comment in the &lt;strong&gt;test_unittest_style.py&lt;/strong&gt; file says, this particular unittest-based test module can be run by both
Django &lt;strong&gt;manage.py&lt;/strong&gt; (which boils down to unittest lookup discovery on a lower layer) or by &lt;strong&gt;py.test&lt;/strong&gt; (pytest).&lt;/p&gt;
&lt;p&gt;It should also be noted, that pytest’s flexibility can &lt;a href=&#34;http://stackoverflow.com/questions/21430900/py-test-skips-test-class-if-constructor-is-defined&#34;&gt;bite back&lt;/a&gt; if something gets overlooked.&lt;/p&gt;
&lt;h3 id=&#34;django-database-interaction-unittest-versus-pytest-advanced-examples&#34;&gt;Django database interaction unittest versus pytest (advanced examples)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/zdenekmaxa/examples/blob/master/python/django-testing/tests/test_unittest_advanced.py&#34;&gt;test_unittest_advanced.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since this post concentrates on pytest and since it’s the choice for our LG CMS project
(naturally :-), this unittest example just shows how the
&lt;strong&gt;test (fresh) database&lt;/strong&gt; is determined and how Django migrations are run at each test suite execution. Just as described in
the &lt;a href=&#34;https://docs.djangoproject.com/en/1.7/topics/testing/overview/&#34;&gt;Django documentation&lt;/a&gt;: “If your tests rely on database access such as creating or querying models, be sure to create your test classes as subclasses of django.test.TestCase rather than unittest.TestCase.”&lt;/p&gt;
&lt;p&gt;That is true for database interaction but not completely true when using pytest. And “Using unittest.TestCase avoids the cost of running each test in a transaction and flushing the database, but if your tests interact with the database their behavior will vary based on the order that the test runner executes them. This can lead to unit tests that pass when run in isolation but fail when run in a suite.”
&lt;strong&gt;django.test.TestCase&lt;/strong&gt;, however, ensures that each test runs inside a transaction to provide isolation.
The transaction is rolled back once the test case is over.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/zdenekmaxa/examples/blob/master/python/django-testing/tests/test_pytest_advanced.py&#34;&gt;test_pytest_advanced.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This file represents the actual core of the test experiments for this blog / demo app and shows various pytest features and approaches typical for this framework as well as Django (pytest-django that is) specifics.&lt;/p&gt;
&lt;h3 id=&#34;django-pytest-notes-advanced-example&#34;&gt;Django pytest notes (advanced example)&lt;/h3&gt;
&lt;p&gt;Much like the unittest documentation, the &lt;a href=&#34;https://pytest-django.readthedocs.org/en/latest/&#34;&gt;pytest-django&lt;/a&gt; recommends avoiding database interaction in unittest and concentrate only on the logic which should be designed in such a fashion that it can be tested without database.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;test database name prefixed &lt;code&gt;test_&lt;/code&gt; (just like at the unittest example), the base value is taken from the database section of
the &lt;strong&gt;settings.py&lt;/strong&gt;. As a matter of fact, it’s possible to run the test suite after previously dropping the main database,
the test suite interacts only with &lt;code&gt;test_&lt;/code&gt; + DATABASE_NAME&lt;/li&gt;
&lt;li&gt;migration execution before any database interaction is carried out (similarly to unittest example)&lt;/li&gt;
&lt;li&gt;database interaction marked by a Python decorator &lt;strong&gt;@pytest.mark.django_db&lt;/strong&gt; on the method or class level (or stand-alone function level). It’s in fact the &lt;strong&gt;first&lt;/strong&gt; occurrence of this marker which triggers the database set up (its creation and migrations handling). Again analogously to unittest (django.test.TestCase), the test case is wrapped in a database transaction which puts the database back into the state prior to the test case. The database &lt;code&gt;test_&lt;/code&gt; + DATABASE_NAME itself is dropped once the test suite run is over. The database is not dropped if &lt;code&gt;--db-reuse&lt;/code&gt; option is used. The production DATABASE_NAME remains untouched during the test suite run (more about this below)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pytest_djangodb_only.py — setup_method&lt;/strong&gt; — run this module separately and the data created in setup_method end up &lt;strong&gt;NOT&lt;/strong&gt; in the &lt;code&gt;test_&lt;/code&gt; + DATABASE_NAME database but in the standard one (as configured in the &lt;strong&gt;settings.py&lt;/strong&gt; which would be the production database likely)! Also this data won’t be rolled back. When run separately, this test module will pass (but still the production database would be tainted). It may or may not fail on the second and subsequent run depending whether it creates any unique data. When run within the test suite, the database call from the setup_method will fail despite the presence of the class django_db marker. This has been very important to realize.
Recommendation: do not include database interaction in the pytest special methods
(such assetup_method or teardown_method, etc), &lt;strong&gt;only include database interaction in the test case methods&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The error message &lt;code&gt;Failed: Database access not allowed, use the &amp;quot;django_db&amp;quot; mark to enable&lt;/code&gt; was seen on a database error on a method which actually had the marker. This output is not to be 100% trusted&lt;/li&gt;
&lt;li&gt;data model &lt;strong&gt;factories&lt;/strong&gt; are discussed separately below&lt;/li&gt;
&lt;li&gt;lastly the test module shows Django Client instance and calling an HTTP resource&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;pytest-setup_method&#34;&gt;pytest setup_method&lt;/h3&gt;
&lt;p&gt;While the fundamental differences between unittest and pytest were discussed, there is something to be said about Django
specific differences of the two. There is different database-related behaviour of
&lt;strong&gt;unittest setUp method&lt;/strong&gt; versus the &lt;strong&gt;pytest setup_method method&lt;/strong&gt;. The setUp is included in the transaction and database interactions are rolled back once the test case is over. The setup_method is not included in the transaction. Moreover, interacting with the database from setup_method results in faulty behaviour and difference depending whether the test module is run on its own or as a part of the whole test suite.&lt;/p&gt;
&lt;p&gt;The bottom line is: do not include database interaction in setup_method. This setUp, setup_method behaviour was already shown in the basic examples. And more description and demonstration of this behaviour is in the file: &lt;strong&gt;pytest_djangodb_only.py&lt;/strong&gt;. This actually revealed the fact that using django_db database fixture is not supported in special pytest methods and the aforementioned error message is misleading (more references &lt;a href=&#34;https://github.com/pytest-dev/pytest-django/issues/297&#34;&gt;here&lt;/a&gt; and
&lt;a href=&#34;http://stackoverflow.com/questions/34089425/django-pytest-setup-method-database-issue&#34;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;When running the whole test suite, this file won’t be collected (its name lacks &lt;code&gt;test_&lt;/code&gt; string).
It needs to be renamed to be included in the test suite run.&lt;/p&gt;
&lt;h3 id=&#34;json-data-fixtures-versus-factories-pytest-advanced-example&#34;&gt;JSON data fixtures versus factories (pytest advanced example)&lt;/h3&gt;
&lt;p&gt;The traditional way of interacting with some test data was to perform following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;have data loaded in the database&lt;/li&gt;
&lt;li&gt;python manage.py dumpdata&lt;/li&gt;
&lt;li&gt;the produced JSON file is dragged along the application test code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;call_command(&amp;ldquo;loaddata&amp;rdquo;, fixture_json_file_name)&lt;/strong&gt; happens at each test suite run&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The load is expensive, the JSON dump file is hard to maintain manually if the original modified copy and the current needs diverge (the file has integer primary keys value, etc). Although even the recent Django testing documentation mentions usage of JSON data fixtures, the approach is considered discouraged and the goal is recommended to achieve by means of loading the data in migrations or using model data factories.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.caktusgroup.com/blog/2013/07/17/factory-boy-alternative-django-testing-fixtures/&#34;&gt;This talk&lt;/a&gt; for example compares the both approaches in favour of &lt;a href=&#34;https://factoryboy.readthedocs.org/en/latest/&#34;&gt;factory_boy library&lt;/a&gt;.
A quote from the article:
“Factory Boy is a Python port of a popular Ruby project called Factory Girl. It provides a declarative syntax for how new instances should be created. &amp;hellip; Using fixtures for complex data structures in your tests is fraught with peril. They are hard to maintain and they make your
tests slow. Creating model instances as they are needed is a cleaner way to write your tests which will make them faster and more maintainable.”&lt;/p&gt;
&lt;p&gt;The file &lt;strong&gt;test_pytest_advanced.py&lt;/strong&gt; demostrates interaction with factories defined in the module
&lt;strong&gt;factories.py&lt;/strong&gt;, the basic very easy-to-use features.&lt;/p&gt;
&lt;p&gt;Despite its ease of use, the factory_boy is a powerful library capable of modeling
&lt;a href=&#34;http://factoryboy.readthedocs.org/en/latest/recipes.html#many-to-many-relation-with-a-through&#34;&gt;Django’s ORM many-to-many relationships&lt;/a&gt;, among other features.&lt;/p&gt;
&lt;h3 id=&#34;additional-useful-links&#34;&gt;Additional useful links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.djangoproject.com/en/1.7/topics/testing/overview/&#34;&gt;Django 1.7 testing&lt;/a&gt; — version used in the demo application&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.djangoproject.com/en/2.0/topics/testing/overview/&#34;&gt;Django 2.0 testing&lt;/a&gt; — latest stable version&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.effectivedjango.com/&#34;&gt;Effective Django&lt;/a&gt; — testing covered already in the second chapter of the book&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.effectivedjango.com/orm.html#factoryboy-example&#34;&gt;Effective Django factory_boy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://carljm.github.io/django-testing-slides&#34;&gt;Django testing&lt;/a&gt; — excellent PyCon talk, slides covering pytest, fixtures vs factories, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;You should have a good idea about testing differences via unittest and pytest in the Django environment. The emphasis has been put on pytest (django-pytest) and some recommended approaches. The demo application django-testing brings functional test cases demonstrating the behaviour and features discussed. The articles and talks listed in this post were extremely helpful and instrumental in gaining expertise in the area and introducing rigorous testing approach into the production application.&lt;/p&gt;
&lt;p&gt;Any discrepancy between the behaviour described above and on your own setup may originate from different software versions. In any case, if anything is not clear enough, please let me know in the comments.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Web Development, Big Data and DevOps—​OSI Days 2014, India</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/01/web-development-big-data-and-devops-osi/"/>
      <id>https://www.endpointdev.com/blog/2015/01/web-development-big-data-and-devops-osi/</id>
      <published>2015-01-12T00:00:00+00:00</published>
      <author>
        <name>Selvakumar Arumugam</name>
      </author>
      <content type="html">
        &lt;p&gt;This is the second part of an article about the conference &lt;a href=&#34;https://opensourceindia.in/osidays/&#34;&gt;Open Source India&lt;/a&gt;, 2014 was held at Bengaluru, India. The first part is available &lt;a href=&#34;/blog/2014/11/mongodb-and-openstack-osi-days-2014/&#34;&gt;here&lt;/a&gt;. The second day of the conference started with the same excitement level. I plan to attend talks covering Web, Big Data, Logs monitoring and Docker.&lt;/p&gt;
&lt;h3 id=&#34;web-personalisation&#34;&gt;Web Personalisation&lt;/h3&gt;
&lt;p&gt;Jacob Singh started the first talk session with a wonderful presentation along with real-world cases which explained the importance of personalisation in the web. It extended to content personalisation for users and A/B testing (comparing two versions of a webpage to see which one performs better). The demo used the &lt;a href=&#34;https://www.drupal.org/project/acquia_lift&#34;&gt;Acquia Lift&lt;/a&gt; personalisation module for the &lt;a href=&#34;https://www.drupal.org/&#34;&gt;Drupal&lt;/a&gt; CMS which is developed by his team.&lt;/p&gt;
&lt;h3 id=&#34;mean-stack&#34;&gt;MEAN Stack&lt;/h3&gt;
&lt;p&gt;Sateesh Kavuri of Yodlee spoke about the &lt;a href=&#34;http://mean.io/&#34;&gt;MEAN&lt;/a&gt; stack which is a web development stack equivalent to popular LAMP stack. MEAN provides a flexible compatibility to web and mobile applications. He explained the architecture of MEAN stack.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-0.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;480&#34; src=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-0.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;He also provided an overview of each component involved in MEAN Stack.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.mongodb.org/&#34;&gt;MongoDB&lt;/a&gt; — NoSQL database with dynamic schema, in-built aggregation, mapreduce, JSON style document, auto-sharding, extensive query mechanism and high availability.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://expressjs.com/&#34;&gt;ExpressJS&lt;/a&gt; — A node.js framework to provide features to web and mobile applications.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://angularjs.org/&#34;&gt;AngularJS&lt;/a&gt; — seamless bi-directional model with extensive features like services and directives.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://nodejs.org/&#34;&gt;Node.js&lt;/a&gt; — A server side JavaScript framework with event based programming and single threaded (non blocking I/O with help of request queue).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://sailsjs.org/&#34;&gt;Sails.js&lt;/a&gt; — MEAN Stack provisioner to develop applications quickly.&lt;/p&gt;
&lt;p&gt;Finally he demonstrated a MEAN Stack demo application provisioned with help of Sails.js.&lt;/p&gt;
&lt;h3 id=&#34;moving-fast-with-high-performance-hack-and-php&#34;&gt;Moving fast with high performance Hack and PHP&lt;/h3&gt;
&lt;p&gt;Dushyant Min spoke about the way Facebook optimised the PHP code base to deliver better performance when they supposed to handle a massive growth of users. Earlier there were compilers HipHop for PHP (HPHPc) or HPHPi (developer mode) to convert the php code to C++ binary and executed to provide the response. After sometime, Facebook developed a new compilation engine called &lt;a href=&#34;https://hhvm.com/&#34;&gt;HipHop Virtual Machine&lt;/a&gt; which uses Just-In-Time (JIT) compilation approach and converts the code to HipHop ByteCode (HHBC). Both Facebook’s production and development environment code runs over HHVM.&lt;/p&gt;
&lt;p&gt;Facebook also created a new language called &lt;a href=&#34;http://hacklang.org/&#34;&gt;Hack&lt;/a&gt; which is very similar to PHP which added static typing and many other new features. The main reason for Hack is to get the fastest development cycle to add new features and release frequent versions. Hack also uses the HHVM engine.&lt;/p&gt;
&lt;p&gt;HHVM engine supports both PHP and Hack, also it provides better performance compare to Zend engine. So Zend Engine can be replaced with HHVM without any issues in the existing PHP applications to get much better performance. It is simple as below:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-1.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;480&#34; src=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-1.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Also PHP code can be migrated to Hack by changing the &lt;code&gt;&amp;lt;?php&lt;/code&gt; tag to &lt;code&gt;&amp;lt;?hh&lt;/code&gt; and there are some converters (hackficator) available for code migration. Both PHP and Hack provide almost the same performance on the HHVM engine, but Hack has some additional developer-focussed features.&lt;/p&gt;
&lt;h3 id=&#34;application-monitoring-and-log-management&#34;&gt;Application Monitoring and Log Management&lt;/h3&gt;
&lt;p&gt;Abhishek Dwivedi spoke about a stack to process the logs with various formats, myriad timestamp and no context. He explains a stack of tools to process the logs, store and visualize in a elegant way.&lt;/p&gt;
&lt;p&gt;ELK Stack = Elasticsearch, LogStash, Kibana. The architecture and the data flow of ELK stack is stated below:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-2.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;438&#34; src=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-2.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&#34;https://www.elastic.co/&#34;&gt;Elasticsearch&lt;/a&gt; — Open source full text search and analytics engine&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.elastic.co/products/logstash&#34;&gt;Log Stash&lt;/a&gt; — Open source tool for managing events and logs which has following steps to process the logs&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.elastic.co/products/kibana&#34;&gt;Kibana&lt;/a&gt; — seamlessly works with Elasticsearch and provides elegant user interface with various types of graphs&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-3.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;138&#34; src=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-3.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h3 id=&#34;apache-spark&#34;&gt;Apache Spark&lt;/h3&gt;
&lt;p&gt;Prajod and Namitha presented the overview of Apache Spark which is a real time data processing system. It can work on top of Hadoop Distributed FileSystem (HDFS). &lt;a href=&#34;https://spark.apache.org/&#34;&gt;Apache Spark&lt;/a&gt; performs 100x faster in memory and 10x faster in disk compare to Hadoop. It fits with Streaming and Interactive scale of Big Data processing.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-4.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;480&#34; src=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-4.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Apache Spark has certain features in processing the data to deliver the promising performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multistep Directed Acyclic Graph&lt;/li&gt;
&lt;li&gt;Cached Intermediate Data&lt;/li&gt;
&lt;li&gt;Resilient Distributed Data&lt;/li&gt;
&lt;li&gt;Spark Streaming — Adjust batch time to get the near real time data process&lt;/li&gt;
&lt;li&gt;Implementation of Lambda architecture&lt;/li&gt;
&lt;li&gt;Graphx and Mlib libraries play an important role&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;online-data-processing-in-twitter&#34;&gt;Online Data Processing in Twitter&lt;/h3&gt;
&lt;p&gt;Lohit Vijayarenu from Twitter spoke about the technologies used at Twitter and their contributions to Open Source. Also he explained the higher level architecture and technologies used in the Twitter microblogging social media platform.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-5.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;338&#34; src=&#34;/blog/2015/01/web-development-big-data-and-devops-osi/image-5.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The Twitter front end is the main data input for the system. The Facebook-developed Scribe log servers gather the data from the Twitter front end application and transfers the data to both batch and real time Big Data processing systems. Storm is a real time data processing system which takes care of the happening events at the site. Hadoop is a batch processing system which runs over historical data and generates result data to perform analysis. Several high level abstraction tools like PIG are used write the MR jobs. Along with these frameworks and tools at the high level architecture, there are plenty of Open Source tools used in Twitter. Lohit also strongly mentioned that in addition to using Open Source tools, Twitter contributes back to Open Source.&lt;/p&gt;
&lt;h3 id=&#34;docker&#34;&gt;Docker&lt;/h3&gt;
&lt;p&gt;Neependra Khare from Red Hat given a talk and demo on &lt;a href=&#34;https://www.docker.com/&#34;&gt;Docker&lt;/a&gt; which was very interactive session. The gist of Docker is to build, ship and run any application anywhere. It provides good performance and resource utilization compared to the traditional VM model. It uses the Linux core feature called containerization. The container storage is ephemeral, so the important data can be stored in persistent external storage volumes. Slides can be found &lt;a href=&#34;https://github.com/nkhare/presetations/blob/master/osidays/osi_docker.md&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Ecommerce in the Django World</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/12/ecommerce-in-django-world/"/>
      <id>https://www.endpointdev.com/blog/2014/12/ecommerce-in-django-world/</id>
      <published>2014-12-03T00:00:00+00:00</published>
      <author>
        <name>Kirk Harr</name>
      </author>
      <content type="html">
        &lt;p&gt;Mezzanine (&lt;a href=&#34;http://mezzanine.jupo.org/&#34;&gt;http://mezzanine.jupo.org/&lt;/a&gt;) is a powerful piece of software written for the Django Framework in Python that functions like a structured content management system similar to Drupal, WordPress, and others. Built on top of this CMS structure is a module which adds features for Ecommerce which is known as Cartridge (&lt;a href=&#34;http://cartridge.jupo.org/&#34;&gt;http://cartridge.jupo.org/&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;installing-cartridgemezzanine&#34;&gt;Installing Cartridge/Mezzanine&lt;/h3&gt;
&lt;p&gt;Installing the Mezzanine CMS system from scratch can be accomplished in two methods, either by using the pip python package manager, or you can clone the current source from the git repository from the software maintainers. If you were planning to modify either Mezzanine or Cartridge to customize the setup for your own needs the latter would likely be preferable as you could then easily begin creating custom branches off the original source to track the customization work. However for this example I will show the pip method of installation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install -U cartridge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once installed, there is a mezzanine-project command which will allow you to create a new blank Mezzanine environment within a new directory, and in this case we will also send an option to instruct the command to install the Cartridge module as well.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mezzanine-project -a cartridge new_cartridge_project&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point you will have a blank Mezzanine environment with the cartridge module installed, now the Django database must be populated with the model information for the application, and then the Django application server will be started.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd new_cartridge_project
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;python manage.py createdb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;python manage.py runserver&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If all went well, you should see the startup messages for the Django application server which will list the version numbers of the various libraries it will use, and then should be up and listening on the loopback interface. At this point to complete the setup you would just need to point your httpd at this loopback socket to send connections there.&lt;/p&gt;
&lt;h3 id=&#34;cartridge-product-models&#34;&gt;Cartridge Product Models&lt;/h3&gt;
&lt;p&gt;Products within Cartridge are defined within the python models for the application, there are three primary models to be concerned with in a Product definition, the Product, ProductVariation and ProductImage models specifically.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Product — Defines the primary attributes for a product like name, price, SKU, and can be populated with optional fields for sale prices, etc.&lt;/li&gt;
&lt;li&gt;ProductVariation — Defines a variant of a product SKU, these would be most commonly used for things like product sizes and colors.&lt;/li&gt;
&lt;li&gt;ProductImage — Defines the image for the picture of each product.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these three models exist within Django as their own data sets, but then they have foreign key references from each Product to all of its Variants and Images. While you can alter these model definitions using the django shell and other methods within python, you can also update and manage the products within the admin interface for the django site.&lt;/p&gt;
&lt;h3 id=&#34;adding-products--tracking-orders&#34;&gt;Adding Products / Tracking Orders&lt;/h3&gt;
&lt;p&gt;Adding products through the admin interface for Django was relatively easy and quick:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2014/12/ecommerce-in-django-world/image-0-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/12/ecommerce-in-django-world/image-0.png&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Within this interface you would provide the details for the products name, published status, date range for the product to be on the site, and product description in addition to any Variants or Images for the product. This is where the Mezzanine foundation of Cartridge start to show through, where this process mirrors the creating of a content page within Mezzanine, but has added these attributes of the product to the definition.&lt;/p&gt;
&lt;p&gt;In the same way, Product Variants, Discount Codes, and Sales can be created in much the same way within the admin interface. In this way, once you had a basic Cartridge setup in place on your server, within most use cases for setting up a simple web store, Cartridge would remove the need to do any further hacking of python code, and would allow any user familiar with a CMS workflow for creating and managing the objects within the environment to manage the store.&lt;/p&gt;
&lt;p&gt;In addition, Cartridge provides order management within the admin interface as well:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2014/12/ecommerce-in-django-world/image-1-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/12/ecommerce-in-django-world/image-1.png&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;From this interface you can update orders, create new orders, print the PDF invoices for the orders and track whether orders have been fulfilled and shipped to the customer.&lt;/p&gt;
&lt;h3 id=&#34;conclusions&#34;&gt;Conclusions&lt;/h3&gt;
&lt;p&gt;Like other Ecommerce applications built on top of a Content Management System, Cartridge and Mezzanine work well together to create a stack that allows for both ease of getting started with the system, and a massive degree of customization possible. Each component of Cartridge is defined within Django, and can be rewritten and customized to serve any number of use cases. The setup process for Cartridge was pretty straightforward, but one recommendation I can make is that once you have a working system up, you should place all of the files for Mezzanine and Cartridge into a source code management system like Git. Like other web applications of this type, the complexity of each change to the system can create a possible point of failure. Once you start making changes to the Mezzanine configuration files and the python for the Models and Views for Cartridge, controlling those changes becomes critical. In this way Mezzanine and Cartridge are spanning the normal continuum of software with the polar extremes of ease of use and customization by providing a good example to start with in the setup, and also exposing the full range of customization to the developer.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Drupal Commerce for Fun and Profit</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/05/drupal-commerce-for-fun-and-profit/"/>
      <id>https://www.endpointdev.com/blog/2014/05/drupal-commerce-for-fun-and-profit/</id>
      <published>2014-05-06T00:00:00+00:00</published>
      <author>
        <name>Kirk Harr</name>
      </author>
      <content type="html">
        &lt;p&gt;Drupal has for years been known as open source content management system (CMS) for creating easy content-driven websites for any topic. In addition to the CMS, there are a whole host of tools built upon this foundation to extend the features using modules. One module in particular I worked with recently for a client was Drupal Commerce, which helps to augment the CMS with an eCommerce platform.&lt;/p&gt;
&lt;p&gt;Within Drupal for normal content items, you would create distinct articles, which have a predefined model of fields that would reflect the different aspects of an article, author, title, tags, etc. While Drupal Commerce uses these same functions, it will also give you the capability to also define SKUs, product categories, and rules and procedures to be carried when adding a particular item to your cart. In much the same way as articles are published to the Drupal taxonomy to allow users to browse through the articles, product categories, and individual products are published, and allow the administrator to customize the layout of how those products would display.&lt;/p&gt;
&lt;h3 id=&#34;getting-started-with-drupal-commerce&#34;&gt;Getting Started with Drupal Commerce&lt;/h3&gt;
&lt;p&gt;One tool that is extremely helpful on getting a Drupal Commerce setup up for yourself is using the Drupal &lt;a href=&#34;https://drupal.org/project/commerce_kickstart&#34;&gt;Commerce Kickstart&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This tool, which is maintained by a group of Drupal consultants/enthusiasts who created an embedded install of Drupal with some modules installed and configured to get both a working Drupal install and a copy of the Commerce plugin as well. The installation is quite simple, you extract the install archive into your web server document root, and then you follow an install procedure to configure your database, the name of the site, and any other information needed.&lt;/p&gt;
&lt;p&gt;Once the install is complete, you will have a copy of Drupal Commerce installed, and will have a basic product display model on the front page with a few example products available. From here the next steps would be to define your own products and categories and input those model objects.&lt;/p&gt;
&lt;h3 id=&#34;creating-products&#34;&gt;Creating Products&lt;/h3&gt;
&lt;p&gt;Internally to Drupal, when referring to articles, there are distinct units for each article and category known as nodes, and a hierarchical taxonomy that is defined by the administrator for how these items fit together. Within Drupal Commerce there is a similar designation, but the items are known as ‘entities’ and could refer to a whole host of possible objects in Drupal Commerce. Here is a chart to show the relationships:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;/blog/2014/05/drupal-commerce-for-fun-and-profit/image-0.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;233&#34; src=&#34;/blog/2014/05/drupal-commerce-for-fun-and-profit/image-0.png&#34; width=&#34;320&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;These “entities” extend the original concept of a Drupal node to allow for different products, attributes of products and customers and their attributes. The product reference is a pointer object that allows a product to be added to a node article to be displayed on the front end of the site. This reference refers back to the products that were added, and if those product objects are updated, the content to display those products will stay static, but will be dynamically updated. In this way, the content templates for display products is abstracted from the actual product SKUs themselves.&lt;/p&gt;
&lt;p&gt;When defining each product there are a number of relationships that are created which relate to the possible categories that a product would live in, as well as fields and properties implied by that product. Here is a visual representation of these relationships:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;a href=&#34;/blog/2014/05/drupal-commerce-for-fun-and-profit/image-1.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;281&#34; src=&#34;/blog/2014/05/drupal-commerce-for-fun-and-profit/image-1.png&#34; width=&#34;320&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Every product entity would by definition be part of the “Product” bundle, and also would fall into a custom bundle for the “T-shirt” product category. In this way, you can create as complex or as simple of relationships between products and categories to reflect the actual state of the business using the tool, and will allow for the product definitions to remain in place, even if you were to redefine the layout for those products to display.&lt;/p&gt;
&lt;h3 id=&#34;conclusions&#34;&gt;Conclusions&lt;/h3&gt;
&lt;p&gt;Drupal Commerce is a hulking tool, with a number of learning curves and a bit of terminology to get straight, but once in place and working will give a user a very robust commerce platform. Any Drupal developer familiar with creating custom taxonomies for article display and categorization will be able to use the same skills to create a slick eCommerce site.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Ecommerce Solutions: What are the Options?</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/02/ecommerce-solutions-options/"/>
      <id>https://www.endpointdev.com/blog/2011/02/ecommerce-solutions-options/</id>
      <published>2011-02-28T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;Lately, I’ve been evaluating ecommerce options for use on &lt;a href=&#34;http://stephskardal.com/&#34;&gt;a side hobby/business&lt;/a&gt;. I’m obviously a developer, so in theory I could use one of End Point’s supported ecommerce frameworks or just write my own framework. But, my bottom line is that I don’t need the feature set offered by some of the ecommerce options out there and I don’t necessarily have the resources to develop a custom solution.&lt;/p&gt;
&lt;p&gt;In addition to personal interest, End Pointers constantly encounter potential clients who aim to get a better understanding of the cost of using open source, our preferred ecommerce solution. I put together two infographics on ecommerce options, ongoing cost, feature sets, and the ability to customize. Before anyone &lt;em&gt;flips out&lt;/em&gt; about the infographics, note that they represent &lt;strong&gt;my&lt;/strong&gt; broad generalizations regarding the ongoing cost, feature sets and ability to customize. I’m intimately familiar with some of these options and less familiar with a couple of them.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2011/02/ecommerce-solutions-options/image-0-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5579241277111917426&#34; src=&#34;/blog/2011/02/ecommerce-solutions-options/image-0.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 561px; height: 397px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Feature Set versus Ongoing Cost of Ecommerce Solutions&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2011/02/ecommerce-solutions-options/image-1-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5579241280123454690&#34; src=&#34;/blog/2011/02/ecommerce-solutions-options/image-1.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 665px; height: 454px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ability to Customize versus Ongoing Cost of Ecommerce Solutions&lt;/p&gt;
&lt;p&gt;Some notes on on the ecommerce solutions shown in the infographics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Online payment service (Paypal):&lt;/strong&gt; An online payment collection service like PayPal offers a minimal “ecommerce” feature set and might be suitable for someone looking to simply collect money. It also provides almost no ability to customize. The ongoing cost of PayPal is lower than many of the other options, where a percentage of each sale goes to PayPal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Online catalog service (Etsy, eBay):&lt;/strong&gt; An online catalog service such as Etsy or eBay offers very basic ecommerce listing features, little to no ability to customize, but has relatively low ongoing cost. For example, Etsy charges $0.20 to list a single item for four months and takes 3.5% of the sales fee.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hosted ecommerce (shopify, Big Cartel, Big Commerce, Yahoo Merchant):&lt;/strong&gt; A hosted ecommerce solution offers the ability to customize the appearance, typically in the form of a custom template language and has a basic ecommerce feature set. Ongoing costs would include the cost of the service, domain registration cost, and payment gateway (e.g. Authorize.NET) fees. Additional cost may apply if development services are required for building a custom template. Shopify’s basic solution costs $29/mo. with a max of 100 skus. Shopify offers several other higher-priced options which include more features and have less limitations. Most other hosted ecommerce solutions are similarly priced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open source ecommerce (Interchange, Spree, Magento, Zen Cart, osCommerce, prestaShop):&lt;/strong&gt; An open-source ecommerce solution tends to have generic ecommerce features, provides the opportunity for a large amount of customization, but is typically more expensive than hosted ecommerce solutions and online catalog services. The software itself is free, but ongoing costs of hosting (server, domain registration, SSL certificate), development (any piece of customization that does not fit into the generic mold), and payment gateway fees apply. One positive about open source ecommerce is that additional plugins or add-ons are produced by members of the community. If the business needs are satisfied by the community-available extensions, cost from additional development or customization may be eliminated or reduced. I’d also probably group in existing open source plugins or modules for open source CMS solutions like WordPress and Drupal into this category: the generic solution is free, but additional resources may be required for customization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enterprise ecommerce (ATG, Magento Enterprise):&lt;/strong&gt; From my experience, I’ve observed that enterprise ecommerce solutions tend to offer a large feature set and a similar ability to customize as open source frameworks. The ongoing cost is high: for example, Magento Enterprise starts at $12,990/year. In addition to the licensing cost, hosting, development, and payment gateway fees may apply.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom ecommerce (homegrown):&lt;/strong&gt; The cost of writing your own ecommerce framework depends on the functionality requirements. The ability to customize is unlimited since the entire solution is custom. The feature set is likely to be proportional to resources spent on the project. Ongoing costs here include hosting, development and payment gateway fees.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;which-one-do-you-choose&#34;&gt;Which one do you choose?&lt;/h3&gt;
&lt;p&gt;It has been my experience that most of End Point’s ecommerce clients need some type of customization: a custom appearance, discount functionality, shipping integration, payment gateway integration, social media integration, and other third-party integration. Choosing an option that allows easy customization tends to benefit our customers in the long run. We’re pretty biased at End Point towards open source ecommerce solutions, but my opinion recently is that with the advancement of web frameworks and web framework tools (e.g. gems in ruby), development of custom solutions can be done efficiently and may be a better option for a site that is outside the realm of standard ecommerce. For businesses not able to pay development or consulting costs, hosted ecommerce solutions are affordable and provide the essentials needed as the business grows.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Spree vs. Magento: Feature List Revisited</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2010/07/spree-vs-magento-feature-list-revisited/"/>
      <id>https://www.endpointdev.com/blog/2010/07/spree-vs-magento-feature-list-revisited/</id>
      <published>2010-07-13T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;A little over a month ago, I wrote an article on &lt;a href=&#34;/blog/2010/06/spree-vs-magento-feature-list/&#34;&gt;Spree vs. Magento Features&lt;/a&gt;. Recently, a client asked me to describe the features mentioned in that article. I thought this was another great opportunity to expand on my response to the client. So, here I am, revisiting ecommerce features in Spree and Magento. The &lt;a href=&#34;/blog/2010/06/spree-vs-magento-feature-list/&#34;&gt;original article&lt;/a&gt; can be referenced to compare availability of these features in Spree and Magento.&lt;/p&gt;
&lt;h3 id=&#34;features-on-a-single-product-or-group-of-product&#34;&gt;Features on a Single Product or Group of Product&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Product reviews and/or ratings&lt;/strong&gt;: functionality to allow customers to review and rate products. See &lt;a href=&#34;https://www.backcountry.com/the-north-face-denali-fleece-jacket-mens#reviews&#34;&gt;a Backcountry.com product page&lt;/a&gt; for an example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product QnA&lt;/strong&gt;: functionality allow customers to ask and answer questions on products. See &lt;a href=&#34;https://www.backcountry.com/the-north-face-denali-fleece-jacket-mens#qanda&#34;&gt;a Backcountry.com product page&lt;/a&gt; for an example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product SEO (URL, title, meta data control)&lt;/strong&gt;: functionality to allow site administrators to manage product URLs, product page titles, and product meta data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advanced/flexible taxonomy&lt;/strong&gt;: functionality to build a custom taxonomy / navigation structure for product browsing. For example, build multiple categories and subcategories with complex hierarchy. The taxonomy at &lt;a href=&#34;https://spreecommerce-demo.herokuapp.com/&#34;&gt;Spree’s demo&lt;/a&gt; includes two categories of brand and category and subcategories in each.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEO for taxonomy pages&lt;/strong&gt;: functionality to allow site administrators to manage taxonomy URLs, taxonomy page titles, and taxonomy meta data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configurable product search&lt;/strong&gt;: functionality to allow the developers and site administrators to adjust parameters used in search, such as products to show per page, and to show products with no on hand stock.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bundled products for discount&lt;/strong&gt;: functionality to allow site administrators to create bundles or group products together and then apply a discount to the entire bundle. For example, product X, Y and Z purchased together will yield a $10.00 discount.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recently viewed products&lt;/strong&gt;:  functionality to show customers products they recently visited. This can be displayed on other product pages or the navigation pages to aid in navigation back to those products if the user would like to revisit the products. See the image below for an example of this functionality in action at &lt;a href=&#34;https://www.papersource.com/&#34;&gt;Paper Source&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-0-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5493450761146669666&#34; src=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-0.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 333px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Soft product support/downloads&lt;/strong&gt;: functionality to sell soft products such as mp3 or pdf files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product comparison&lt;/strong&gt;: functionality to allow customers to compare multiple products, such as a comparison of price or technical features. See &lt;a href=&#34;http://www.backcountry.com/store/compare.html?chk_compare_TNF0115=1&amp;amp;chk_compare_TNF3203=1&amp;amp;chk_compare_TNF4034=1&#34;&gt;Backcountry.com&lt;/a&gt; for an example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Upsell&lt;/strong&gt;: functionality to encourage the customer to purchase a similar product from a higher price point, or to purchase an add on, may or may not include the functionality to allow site administrators to manage the upsell products.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross sell&lt;/strong&gt;: functionality to encourage the customer to purchase related items, may or may not include the functionality to allow site administrators to manage the cross sell products. See the image below for an example of this functionality at &lt;a href=&#34;http://www.backcountry.com/&#34;&gt;Backcountry.com&lt;/a&gt; after adding an item to the cart.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-1-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5493450630306818274&#34; src=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-1.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 323px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Related items&lt;/strong&gt;: functionality to display related items on product pages, may or may not include the functionality to allow site administrators to manage the related products.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSS feed of products&lt;/strong&gt;: functionality to produce a RSS feed with product detail releases. &lt;a href=&#34;https://www.steepandcheap.com/&#34;&gt;steepandcheap.com&lt;/a&gt; offers a RSS feed, however, they are unique in that they offer a one deal at a time business model, so the RSS feed contains a stream of products for sale. This may or may not include the functionality to allow site administrators to manage the RSS feed contents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple images per product&lt;/strong&gt;: functionality to allow site administrators to upload multiple products per image. See &lt;a href=&#34;https://spreecommerce-demo.herokuapp.com/products/ruby-on-rails-baseball-jersey&#34;&gt;a Spree demo product&lt;/a&gt; for an example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product option selection (variants)&lt;/strong&gt;: functionality to allow site administrators to create and manage variants for products to offer multiple variants per product, such as variants by size and color. See &lt;a href=&#34;https://spreecommerce-demo.herokuapp.com/products/ruby-on-rails-baseball-jersey&#34;&gt;a Spree demo product&lt;/a&gt; for an example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wish list&lt;/strong&gt;: functionality to allow customers to create product wish lists. See &lt;a href=&#34;https://www.amazon.com/wishlist&#34;&gt;Amazon.com&lt;/a&gt; for a description of their wish list functionality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Send product email to friend&lt;/strong&gt;: functionality to allow customers to send emails to their friend to visit a specific product.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product tagging / search by tagging&lt;/strong&gt;: functionality to allow site administrators to assign tags to products for navigation or searching. See &lt;a href=&#34;http://www.citypass.com/blog/&#34;&gt;CityPass’s blog&lt;/a&gt; for an example of tag use in a content management system; in ecommerce context, the tags would navigate to a set of products instead of a set of blog articles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Breadcrumbs&lt;/strong&gt;: functionality that renders the product navigation hierarchy on navigation and product pages to allow customers to navigate to previous pages visited. See &lt;a href=&#34;https://spreecommerce-demo.herokuapp.com/t/brand/rails&#34;&gt;a Spree demo navigation page&lt;/a&gt; for an example.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;cms-features&#34;&gt;CMS Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blogging functionality&lt;/strong&gt;: functionality to allow site administrators to create, manage and display blog articles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static page management&lt;/strong&gt;: functionality to allow site administrators to create, manage, and display static pages such as “About Us”, “Information”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Media management&lt;/strong&gt;: functionality to allow site administrators to create, manage, and display media such as images, video, audio. See the image below for an example of &lt;a href=&#34;https://www.wordpress.org&#34;&gt;WordPress’s&lt;/a&gt; dashboard for content management.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-2-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5493450626615883906&#34; src=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-2.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 341px; height: 400px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Contact us form&lt;/strong&gt;: functionality to allow customers to submit a request for contact. See &lt;a href=&#34;/contact/&#34;&gt;End Point’s contact page&lt;/a&gt; for an example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polls&lt;/strong&gt;: functionality to allow site administrators to create and manage basic polls, functionality to allow customers to submit answers to basic polls.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;checkout-support&#34;&gt;Checkout Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One page checkout&lt;/strong&gt;: functionality to allow customers to complete checkout on one page, rather than move forward through checkout through multiple address, payment pages. See &lt;a href=&#34;https://www.papersource.com/&#34;&gt;Paper Source&lt;/a&gt; or &lt;a href=&#34;https://www.backcountry.com/&#34;&gt;Backcountry.com’s&lt;/a&gt; checkout processes for examples.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guest checkout&lt;/strong&gt;: functionality to checkout without creating a user account. Checkout at the &lt;a href=&#34;https://spreecommerce-demo.herokuapp.com/&#34;&gt;Spree demo&lt;/a&gt; without being logged in for an example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSL support&lt;/strong&gt;: functionality to configure use of SSL during checkout. In Spree’s case, the site administrator may turn SSL off during development and on during production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discounts&lt;/strong&gt;: functionality to allow customers to apply discount coupons to orders for a percentage or dollar amount reduction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gift certificates&lt;/strong&gt;: functionality to allow customers to purchase and use gift certificates as credit for purchases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saved shopping cart&lt;/strong&gt;: the functionality to save the products in a customers shopping cart so their shopping cart will be pre-populated on their next visit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saved addresses&lt;/strong&gt;: functionality to allow customers to create and manage addresses to be selected during checkout for billing or shipping rather than requiring the customer to re-enter their address. See the image below for an example of using saved addresses during &lt;a href=&#34;https://www.backcountry.com&#34;&gt;Backcountry.com’s&lt;/a&gt; checkout.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-3-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5493450765449179810&#34; src=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-3.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 184px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;shipping-support&#34;&gt;Shipping Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real time rate lookup (UPS, USPS, FedEx)&lt;/strong&gt;: the functionality to request rates from UPS, USPS, or FedEx during checkout for more accurate rate pricing rather than using a flat shipping rate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Order tracking&lt;/strong&gt;: functionality to allow the site administrators to enter order tracking information and allow the user to review that tracking information, may or may not include sharing this information in a “Your order has been shipped” email.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple shipments per order&lt;/strong&gt;: functionality to allow site administrators to split orders into multiple packages if specific products can not be shipped at the same time or can not be shipped together. See the image below for an example of &lt;a href=&#34;https://www.spreecommerce.org/&#34;&gt;Spree’s&lt;/a&gt; backend shipping interface.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-4-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5493450647395677138&#34; src=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-4.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 78px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complex rate lookup&lt;/strong&gt;: functionality to calculate ship rates based on weight or price.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Free shipping&lt;/strong&gt;: functionality to offer free shipping.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;payment-support&#34;&gt;Payment Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multiple payment gateways&lt;/strong&gt;: integration of multiple payment gateways such as Authorize.NET, Beanstream, Paypal, SagePay, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorize.Net&lt;/strong&gt;: integration of the Authorize.Net payment gateway; may or may include the use of profiles (Authorize.Net CIM).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorize and capture versus authorize only&lt;/strong&gt;: functionality to allow site administrators to configure whether or not credit cards should be authorized only during checkout completion or authorized and captured. If the credit card is authorized only, site administrators may finalize an order by capturing on the backend interface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Checkout&lt;/strong&gt;: integration of &lt;a href=&#34;https://googlecheckout.blogspot.com/&#34;&gt;Google Checkout&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paypal Express&lt;/strong&gt;: integration of &lt;a href=&#34;https://www.paypal.com/us/webapps/mpp/paypal-checkout&#34;&gt;Paypal Express&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;admin-features&#34;&gt;Admin Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sales reporting&lt;/strong&gt;: the functionality to display sales statistics, such as profits on sales or year-over-year sales.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sales management tools&lt;/strong&gt;: functionality to allow site administrators to create and manage product sales. For example, the site administrator might create a 50% off sale to cover 25% of the products to begin in a week and end in two weeks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inventory management&lt;/strong&gt;: functionality to allow the site administrator to manage individual inventory units and their current state (on_hand, shipped, backordered) and order assignment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Purchase order management&lt;/strong&gt;: functionality to allow the site administrator to create and manage purchase orders. See the image below for an example of a potential backend interface for purchase orders in Spree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-5-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5493450657470955906&#34; src=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-5.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 71px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multi-tier pricing for quantity discounts&lt;/strong&gt;: functionality to allow customers to buy large quantities of products at a discount, the functionality to allow the site administrator to manage the large quantity product discounts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Landing page tool&lt;/strong&gt;: functionality to create custom landing pages that may include targeted content or products, typically used for advertising or marketing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch import and export of products&lt;/strong&gt;: functionality to allow the site administrator to import and export products via admin interface or script rather than entering each product individually.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple sales reports&lt;/strong&gt;: See “Sales reporting” above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Order fulfillment&lt;/strong&gt;: functionality to allow site administrators to manage fulfillment (inventory selection, shipping) of orders.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tax Rate Management&lt;/strong&gt;: functionality to manage tax rates per zone, where zones are defined by states and/or countries. Note that in Spree, zones can only be defined by a combination of states and countries and tax rates can be tied to one or more zones.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;user-account-features&#34;&gt;User Account Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;User addresses&lt;/strong&gt;: See “Saved addresses” above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature rich user preferences&lt;/strong&gt;: integration of various user account tools, such as address management, profile management, order review, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Order tracking history&lt;/strong&gt;: functionality to allow a customer to lookup their order history, may or may not include order tracking information.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;site-wide-features&#34;&gt;Site Wide Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;: functionality to extend the ecommerce core with modular components.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Appearance Theming&lt;/strong&gt;: functionality to change the appearance of the site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ability to customize appearance at category or browsing level&lt;/strong&gt;: functionality to create and manage custom and varied appearances for product browsing pages. For example, the categories &amp;ldquo;Jackets&amp;rdquo; and &amp;ldquo;Pants&amp;rdquo; may have different appearances, motivated by marketing or advertising.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Localization&lt;/strong&gt;: the functionality to translate the ecommerce site to a different language. See the image below for a small example of localization in action in Spree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-6-big.png&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5493450639007292738&#34; src=&#34;/blog/2010/07/spree-vs-magento-feature-list-revisited/image-6.png&#34; style=&#34;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 170px; height: 45px;&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multi-store, single admin support&lt;/strong&gt;: functionality to manage multiple stores from a single administrative location. An example of this might include &lt;a href=&#34;http://store1.endpoint.com/&#34;&gt;http://store1.endpoint.com/&lt;/a&gt;, and &lt;a href=&#34;http://store2.endpoint.com/&#34;&gt;http://store2.endpoint.com/&lt;/a&gt;, where both can be managed at &lt;a href=&#34;http://admin.endpoint.com/&#34;&gt;http://admin.endpoint.com/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support for multiple currencies&lt;/strong&gt;: functionality to translate product prices between currencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web service API&lt;/strong&gt;: functionality to retrieve data from the ecommerce application for third party use. See &lt;a href=&#34;https://guides.spreecommerce.org/api/&#34;&gt;Spree’s documentation&lt;/a&gt; on the Spree API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System wide SEO&lt;/strong&gt;: general site-wide SEO functionality including features such as sitemap, googlebase integration, URL management, page title management.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Analytics&lt;/strong&gt;: functionality to allow site administrators to create and manage Google Analytics Ids, functionality to track traffic and conversion on the frontend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Active community&lt;/strong&gt;: an active developer community with frequent core and extension contributions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the features described above are well known to ecommerce developers, but this list might also serve as a good checklist to review with a potential client during the estimate process to make sure expectations of an ecommerce platform are managed, especially with a young platform such as Spree where some features are not yet included in the core.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Spree vs Magento: A Feature List Comparison</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2010/06/spree-vs-magento-feature-list/"/>
      <id>https://www.endpointdev.com/blog/2010/06/spree-vs-magento-feature-list/</id>
      <published>2010-06-07T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;Note: This article was written in June of 2010. Since then, there have been several updates to Spree. Check out the current &lt;a href=&#34;https://web.archive.org/web/20111027123306/http://www.spreecommerce.com/extensions?is_official=true&#34;&gt;Official Spree Extensions&lt;/a&gt; or review a list of all the &lt;a href=&#34;https://web.archive.org/web/20111010012147/http://spreecommerce.com/extensions&#34;&gt;Spree Extensions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This week, a client asked me for a list of Spree features both in the core and in available extensions. I decided that this might be a good time to look through Spree and provide a comprehensive look at features included in Spree core and extensions and use Magento as a basis for comparison. I’ve divided these features into meaningful broader groups that will hopefully ease the pain of comprehending an extremely long list :) Note that the Magento feature list is based on their documentation. Also note that the Spree features listed here are based on recent 0.10.* releases of Spree.&lt;/p&gt;
&lt;h3 id=&#34;features-on-a-single-product-or-group-of-product&#34;&gt;Features on a Single Product or Group of Product&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Feature&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Product reviews and/or ratings&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Product qna&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Product seo (url, title, meta data control)&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Advanced/flexible taxonomy&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Seo for taxonomy pages&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Configurable product search&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bundled products for discount&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Recently viewed products&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Soft product support/downloads&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y, I think so&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Product comparison&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Upsell&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Cross sell&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Related items&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;RSS feed of products&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Multiple images per product&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Product option selection (variants)&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Wishlist&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Send product email to friend&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Product tagging / search by tagging&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Breadcrumbs&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;cms-features&#34;&gt;CMS Features&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Features&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Blogging functionality&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y *extension&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Static page management&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Media management&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Contact us form&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Polls&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;checkout-support&#34;&gt;Checkout Support&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Feature&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;One page checkout&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Guest checkout&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SSL Support&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Discounts&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gift Certificates&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Saved Shopping Cart&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Saved Addresses&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;shipping-support&#34;&gt;Shipping Support&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Feature&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Real time rate lookup (UPS, USPS, Fedex)&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Order tracking&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Multiple shipments per order&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Complex rate lookup&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Free shipping&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;payment-support&#34;&gt;Payment Support&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Feature&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Multiple Payment Gateways&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Authorize.net&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Authorize and capture versus authorize only&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Google Checkout&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Paypal Express&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;admin-features&#34;&gt;Admin Features&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Feature&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sales reporting&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Sales Management Tools&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Inventory management&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Purchase order management&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Multi-tier pricing for quantity discounts&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Landing page tool&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Batch import and export of products&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Multiple Sales reports&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Order fulfillment&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Tax Rate Management&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;user-account-features&#34;&gt;User Account Features&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Feature&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;User addresses&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Feature rich user preferences&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Order tracking history&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;system-wide-features&#34;&gt;System Wide Features&lt;/h3&gt;
&lt;table cellpadding=&#34;2&#34; cellspacing=&#34;0&#34; width=&#34;100%&#34;&gt;&lt;tbody&gt;
&lt;tr class=&#34;alt header&#34;&gt;&lt;td width=&#34;60%&#34;&gt;Feature&lt;/td&gt;&lt;td width=&#34;20%&#34;&gt;Spree&lt;/td&gt;&lt;td&gt;Magento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Extensibility&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Appearance Theming&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Ability to customize appearance at category or browsing level&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Localization&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Multi-store, single admin support&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Support for multiple currencies&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Web Service API&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;SEO System wide: sitemap, google base, etc&lt;/td&gt;&lt;td&gt;Y, extension&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Google Analytics&lt;/td&gt;&lt;td&gt;Y, core&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;
&lt;tr class=&#34;alt&#34;&gt;&lt;td&gt;Active community&lt;/td&gt;&lt;td&gt;Y, N/A&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The configurability and complexity of each feature listed above varies. Just because a feature is provided within a platform does not guarantee that it will meet the desired business needs. Magento serves as a more comprehensive ecommerce platform out of the box, but the disadvantage may be that adding custom functionality may require more resources (read: more expensive). Spree serves as a simpler base that may encourage quicker (read: cheaper) customization development simply because it’s in Rails and because the dynamic nature of Ruby allows for elegant extensibility in Spree, but a disadvantage to Spree could be that a site with a large amount of customization may not be able to take advantage of community-available extensions because they may not all play nice together.&lt;/p&gt;
&lt;p&gt;Rather than focus on the platform features, the success of the development depends on the developer and his/her skillset. Most developers will say that &lt;strong&gt;any&lt;/strong&gt; of the features listed above are doable in Magento, Spree, or Interchange (a Perl-based ecommerce platform that End Point supports) with an unlimited budget, but a developer needs to have an understanding of the platform to design a solution that is easily understood and well organized (to encourage readability and understandability by other developers), develop with standard principles like DRY and MVC-style separation of concerns, and elegantly abstract from the ecommerce core to encourage maintainability. And of course, be able to understand the business needs and priorities to guide a project to success within the given budget. Inevitably, another developer will come along and need to understand the code and inevitably, the business will often use an ecommerce platform longer than planned so maintainability is important.&lt;/p&gt;
&lt;p&gt;Please feel free to comment on any errors in the feature list. I’ll be happy to correct any mistakes. Now, off to rest before RailsConf!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>New End Point site launched: Rails, jQuery, Flot, blog feed</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2009/10/new-end-point-site-rails-jquery-flot/"/>
      <id>https://www.endpointdev.com/blog/2009/10/new-end-point-site-rails-jquery-flot/</id>
      <published>2009-10-07T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;This week we launched a new website for End Point. Not only did the site get a facelift, but the backend content management system was entirely redesigned.&lt;/p&gt;
&lt;p&gt;Goodbye Old Site:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/oldsite.png&#34; alt=&#34;Old site&#34;&gt;&lt;/p&gt;
&lt;p&gt;Hello New Site:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/newsite.png&#34; alt=&#34;New site&#34;&gt;&lt;/p&gt;
&lt;p&gt;Our old site was a Rails app with a Postgres database running on Apache and Passenger. It used a custom CMS to manage dynamic content for the bio, articles, and service pages. The old site was essentially JavaScript-less, with the exception of Google Analytics.&lt;/p&gt;
&lt;p&gt;Although the new site is still a Rails application, it no longer uses the Postgres database. As developers, we found that it is more efficient to use Git as our &amp;ldquo;CMS&amp;rdquo; rather than developing and maintaining a custom CMS to meet our ever-changing needs. We also trimmed down the content significantly, which further justified the design; the entire site and content is now comprised of Rails views and partial views. Also included in the new site is cross browser functioning &lt;a href=&#34;http://jquery.com/&#34;&gt;jQuery&lt;/a&gt; and &lt;a href=&#34;http://code.google.com/p/flot/&#34;&gt;flot&lt;/a&gt;. Some of the interesting implementation challenges are discussed below.&lt;/p&gt;
&lt;h3 id=&#34;jquery-flot-integration&#34;&gt;jQuery Flot Integration&lt;/h3&gt;
&lt;p&gt;The first interesting JavaScript component I worked on was using flot to improve interactivity to the site and to decrease the excessive text that End Pointers are known for (for example, this article). Flot is a jQuery data plotting tool that contains functionality for plot zooming, data interactivity, and various configuration display settings (see more &lt;a href=&#34;http://people.iola.dk/olau/flot/examples/&#34;&gt;flot examples&lt;/a&gt;). I&amp;rsquo;ve used flot before in several experiments but had yet to use it on a live site. For the implementation, we chose to plot our consultant locations over a map of the US to present our locations in an interactive and fun to use way. The tedious part of this implementation was actually creating the datapoints to align with cities. Check out the images below for examples.&lt;/p&gt;
&lt;p&gt;Flot has built in functionality for on hover events. When a point on the plot is hovered over, correlating employees are highlighted using jQuery and their information is presented to the right of the map.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/hover1.png&#34; alt=&#34;End Point team page, hovering on New York&#34;&gt;&lt;/p&gt;
&lt;p&gt;When a bio picture is hovered over, the correlating location is highlighted using jQuery and flot data point highlighting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/hover2.png&#34; alt=&#34;End Point team page, hovering on Rick’s portrait in New York&#34;&gt;&lt;/p&gt;
&lt;p&gt;We also implemented a timeline using flot to map End Point&amp;rsquo;s history. Check out the images below.&lt;/p&gt;
&lt;p&gt;When a point on the plot is hovered over, the history details are revealed in the section below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/hover3.png&#34; alt=&#34;History plot, hovering on 1963 (Founders Ben and Rick meet each other on a playground)&#34;&gt;&lt;/p&gt;
&lt;p&gt;The triangle image CSS position is adjusted when a point on the plot is activated.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/hover4.png&#34; alt=&#34;History plot, hovered over 2008-2009&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;dynamic-rails-partial-generation&#34;&gt;Dynamic Rails Partial Generation&lt;/h3&gt;
&lt;p&gt;One component of the old site that was generated dynamically sans-CMS was blog article integration into the site. A cron job ran daily to import new blog article title, link, and content snippets into the Postgres database.  We opted for removing dependency on a database with the new site, so we investigated creative ways to include the dynamic blog content. We developed a rake task that is run via cron job to dynamically generate partial Rails views containing blog content. Below is an example and explanation of how the blog RSS feed is retrieved and a partial is generated:&lt;/p&gt;
&lt;p&gt;Open URI and REXML are used to retrieve and parse the XML feed.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;open-uri&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;rexml/document&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The feed is retrieved and a REXML object created from the feed in the rake task:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data = &lt;span style=&#34;color:#038&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://www.endpointdev.com/blog/feed.xml&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Ruby-Wget&amp;#39;&lt;/span&gt;).read
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;doc = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;REXML&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Document&lt;/span&gt;.new(data)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The REXML object is iterated through. An array containing the blog links and titles is created.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;results = []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;doc.root.each_element(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;//item&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt; |item|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  author = item.elements[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;author&amp;#39;&lt;/span&gt;].text.match(&lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;/\(.+/&lt;/span&gt;).to_s.gsub(&lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;/\.|\(|\)/&lt;/span&gt;,&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  results &amp;lt;&amp;lt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;lt;a href=&amp;#34;&amp;#39;&lt;/span&gt; + item.elements[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;].text + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#34;&amp;gt;&amp;#39;&lt;/span&gt; + item.elements[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;].text + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, a Rails dynamic partial is written containing the contents of the results array:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;File&lt;/span&gt;.open(&lt;span style=&#34;color:#888&#34;&gt;#{RAILS_ROOT}/app/views/blog/_index.rhtml&amp;#34;, &amp;#39;w&amp;#39;) { |f| f.write(results.inject(&amp;#39;&amp;#39;) { |s, v| s = s + &amp;#39;&amp;lt;p&amp;gt;&amp;#39; + v  + &amp;#39;&amp;lt;/p&amp;gt;&amp;#39;}) }&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A similar process was applied for bio and tag dynamic partials. The partials are included on pages such as the End Point service pages, End Point bio pages, and End Point home page.&lt;/p&gt;
&lt;h3 id=&#34;jquery-carousel-functionality&#34;&gt;jQuery Carousel Functionality&lt;/h3&gt;
&lt;p&gt;Another interesting JavaScript component I worked on for the new site was the carousel functionality for the home page and client page. Carousels are a common &amp;ldquo;web 2.0&amp;rdquo; JavaScript component where visible items slide one direction out of view and new items slide into view from the other direction. I initially planned on implementing a simple carousel with a jQuery plugin, such as &lt;a href=&#34;http://sorgalla.com/jcarousel/&#34;&gt;jCarousel&lt;/a&gt;. Other JavaScript frameworks also include carousel functionality such as the &lt;a href=&#34;http://developer.yahoo.com/yui/carousel/&#34;&gt;YUI Carousel Control&lt;/a&gt; or the &lt;a href=&#34;http://www.prototype-ui.com/&#34;&gt;Prototype UI&lt;/a&gt;. I went along planning to implement the existing jQuery carousel functionality, but then was asked, &amp;ldquo;Can you make it a circular carousel where the left and right buttons are always clickable?&amp;rdquo; In many of the existing carousel plugins and widgets, the carousel is not circular, so this request required custom jQuery. After much cross-browser debugging, I implemented the following (shown in images for a better explanation):&lt;/p&gt;
&lt;p&gt;Step 1: The page loads with visible bios surrounded by empty divs with preset width. The visibility of the bios is determined by CSS use of the overflow, position, and left attributes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/image-6.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Step 2: Upon right carousel button click, new bios populate the right div via jQuery.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/image-7.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Step 3: To produce the carousel or slider effect, the left div uses jQuery animation functionality and shrinks to a width of 0px.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/image-8.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Step 4: Upon completion of the animation, the empty left div is removed, and a new empty div is created to the right of the new visible bios.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/image-9.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Step 5: Finally, the left div&amp;rsquo;s contents are emptied and the carousel is in its default position ready for action!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2009/10/new-end-point-site-rails-jquery-flot/image-10.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Another request for functionality came from &lt;a href=&#34;/team/jon-jensen/&#34;&gt;Jon&lt;/a&gt;. He asked that we create and use &amp;ldquo;web 2.0&amp;rdquo; URLs to load specific content on page load for the dynamic content throughout our site, such as &lt;a href=&#34;https://www.endpoint.com/clients#citypass&#34;&gt;www.endpoint.com/clients#citypass&lt;/a&gt;, &lt;a href=&#34;https://www.endpoint.com/clients#backcountry&#34;&gt;www.endpoint.com/clients#backcountry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Upon page load, JavaScript is used to detect if a relative link exists:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt;(document.location.href.match(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#.+&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    var &lt;span style=&#34;color:#038&#34;&gt;id&lt;/span&gt; = document.location.href.match(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#.*&amp;#39;&lt;/span&gt;).toString().replace(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The id retrieved from the code snippet above is used to populate the dynamic page content. Then, JavaScript is used during dynamic page functionality, such as carousel navigation, to update the relative link:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;document.location.href = document.location.href.split(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#&amp;#39;&lt;/span&gt;)[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;] + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#&amp;#39;&lt;/span&gt; + anchor;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;twitter-integration&#34;&gt;Twitter Integration&lt;/h3&gt;
&lt;p&gt;Another change in the new site was importing existing functionality previously written in Python to update &lt;a href=&#34;https://twitter.com/endpoint&#34;&gt;End Point&amp;rsquo;s Twitter&lt;/a&gt; feed automagically. The rake task uses the &lt;a href=&#34;http://twitter4r.rubyforge.org/&#34;&gt;Twitter4R&lt;/a&gt; gem to update the Twitter feed and is run via cron job every 30 minutes. See the explanation below:&lt;/p&gt;
&lt;p&gt;The public twitter feed is retrieved using Open URI and REXML.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data = &lt;span style=&#34;color:#038&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://twitter.com/statuses/user_timeline/endpoint.xml&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Ruby-Wget&amp;#39;&lt;/span&gt;).read
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    doc = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;REXML&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Document&lt;/span&gt;.new(data)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;An array containing all the titles of all tweets is created.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    doc.each_element(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;statuses/status/text&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt; |item|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      twitter &amp;lt;&amp;lt; item.text.gsub(&lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;/ http:\/\/j\.mp.*/&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The blog RSS feed is retrieved and parsed. An array of hashes is created to track the un-tweeted blog articles.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data = &lt;span style=&#34;color:#038&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://www.endpointdev.com/blog/feed.xml&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Ruby-Wget&amp;#39;&lt;/span&gt;).read
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    doc = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;REXML&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Document&lt;/span&gt;.new(data)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    found_recent = &lt;span style=&#34;color:#080&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    doc.root.each_element(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;//item&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt; |item|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      found_recent = &lt;span style=&#34;color:#080&#34;&gt;true&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; twitter.include?(item.elements[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;].text)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      blog &amp;lt;&amp;lt; { &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt; =&amp;gt; item.elements[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;].text, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt; =&amp;gt; item.elements[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;].text } &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; !found_recent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using the j.mp api, a short url is generated. A Twitter message is created from the short URL.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      data = &lt;span style=&#34;color:#038&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;http://api.j.mp/shorten?version=2.0.1&amp;amp;longUrl=&amp;#39;&lt;/span&gt; + blog.last[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;] + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;amp;login=**&amp;amp;apiKey=*****&amp;amp;format=xml&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      twitter_msg = blog.last[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;] + &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; + short_url&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The twitter4r gem is used to login and update the Twitter status message.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      client = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Twitter&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Client&lt;/span&gt;.new(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:login&lt;/span&gt; =&amp;gt; **, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:password&lt;/span&gt; =&amp;gt; *****)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;begin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        status = client.status(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:post&lt;/span&gt;, twitter_msg)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;rescue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;google-event-tracking&#34;&gt;Google Event Tracking&lt;/h3&gt;
&lt;p&gt;Finally, since we implemented dynamic content throughout the site, we decided to use Google Event Tracking to track user interactivity. We followed the standard Google Analytics event tracking to add events for events such as the slider carousel user involvement, the team page bio and history hover user involvement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//pageTracker._trackEvent(category, action, optional_label, optional_value);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;pageTracker._trackEvent(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Team Page Interaction&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Map Hover&amp;#39;&lt;/span&gt;, bio);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We are happy with the new site and we hope that it presents our skillz well!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Spree at RailsConf</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2009/05/spree-at-railsconf/"/>
      <id>https://www.endpointdev.com/blog/2009/05/spree-at-railsconf/</id>
      <published>2009-05-11T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;Last week at &lt;a href=&#34;https://conferences.oreilly.com/rails2009&#34;&gt;RailsConf 2009&lt;/a&gt;, the Spree folks from End Point conducted a &lt;a href=&#34;https://conferences.oreilly.com/rails2009/public/schedule/stype/Bof&#34;&gt;Birds of a Feather&lt;/a&gt; session to discuss Spree, an End Point sponsored open source rails ecommerce platform. Below is some of the dialog from the discussion (paraphrased).&lt;/p&gt;
&lt;p&gt;Crowd: “How difficult is it to get Spree up and running from start to finish?”&lt;/p&gt;
&lt;p&gt;Spree Crew: “This depends on the level of customization. If a customer simple needs to reskin the site, this shouldn’t take more than a week (hopefully much less than a full week). If the customer needs specific functionality that is not included in core functionality or extensions, you may need to spend some time developing an &lt;a href=&#34;https://web.archive.org/web/20090513101129/http://wiki.github.com/schof/spree/extensions&#34;&gt;extension&lt;/a&gt;.”&lt;/p&gt;
&lt;p&gt;Crowd: “How difficult is it to develop extensions in Spree?”&lt;/p&gt;
&lt;p&gt;Spree Crew: “Spree extension work is based on the work of the &lt;a href=&#34;http://radiantcms.org/&#34;&gt;Radiant&lt;/a&gt; community. Extensions are mini-applications: they allow you to drop a pre-built application into spree to override or insert new functionality. Documentation for extensions is available at the &lt;a href=&#34;https://web.archive.org/web/20090513101129/http://wiki.github.com/schof/spree/extensions&#34;&gt;spree github wiki&lt;/a&gt;. We also plan to release more extensive Spree Guides documentation based on &lt;a href=&#34;http://guides.rubyonrails.org/&#34;&gt;Rails Guides&lt;/a&gt; soon.”&lt;/p&gt;
&lt;p&gt;Spree Crew: “How did you hear about Spree?”&lt;/p&gt;
&lt;p&gt;Crowd: “My client and I found it via search engines. My client thought that Spree looked like a good choice.”&lt;/p&gt;
&lt;p&gt;Spree Crew: “What other platforms did you consider before you found spree?”&lt;/p&gt;
&lt;p&gt;Crowd: “Magento”, “Substruct”, “My client considered Magento, but I know several people that have developed with Magento and have found it difficult to override core functionality.”&lt;/p&gt;
&lt;p&gt;Spree Crew: “What types of functionality were missing from Spree that you’d like to see developed in the future?”&lt;/p&gt;
&lt;p&gt;Crowd: “My client wanted checkout split into multiple steps instead of the new single page checkout. I was able to implement this by overriding the Spree checkout library and checkout views.”, “My client needed complex inventory management.”, “My client needed split shipping functionality.”&lt;/p&gt;
&lt;p&gt;Crowd: “What is the plan for Spree with regards to CMS development?”&lt;/p&gt;
&lt;p&gt;Spree Crew: “There has been some discussion on the &lt;a href=&#34;https://web.archive.org/web/20121016021052/http://groups.google.com/group/spree-user/search?q=cms&#34;&gt;integration of a CMS into Spree&lt;/a&gt;. No one in the Spree community appears to be currently working on this. Contributions in this area are welcome. Also, Yehuda Katz is giving a talk on &lt;a href=&#34;https://conferences.oreilly.com/rails2009/public/schedule/detail/7785&#34;&gt;mountable apps&lt;/a&gt;—​the Spree community would like to investigate the implications this has for Spree.”&lt;/p&gt;
&lt;p&gt;Crowd: “What are the next steps for localization, especially multilingual product descriptions?”&lt;/p&gt;
&lt;p&gt;Spree Crew: “This is on the radar for future Spree development. It is not currently in development, and again, contributions in this area are welcome.”&lt;/p&gt;
&lt;p&gt;From the discussion, I took away that some of the desired features for Spree are inventory management, split shipping functionality, cms integration, and improved localization. I hope that the application of Spree continues to contribute to it’s progress. The Spree Crew also hopes to showcase some of the sites referenced above at the spree site.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>SEO Ecommerce</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2009/04/ecommerce-and-seo/"/>
      <id>https://www.endpointdev.com/blog/2009/04/ecommerce-and-seo/</id>
      <published>2009-04-20T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;I recently read an article that discusses &lt;a href=&#34;https://web.archive.org/web/20090311033516/http://yoast.com/magento-seo/&#34;&gt;Magento SEO&lt;/a&gt; problems and solutions. This got me to think about common search engine optimization issues that I’ve seen in e-commerce. Below are some highlighted e-commerce search engine optimization issues. The &lt;a href=&#34;https://github.com/spree/demo&#34;&gt;Spree Demo&lt;/a&gt;, &lt;a href=&#34;http://demo.icdevgroup.org/i/demo1&#34;&gt;Interchange Demo&lt;/a&gt;, and &lt;a href=&#34;https://web.archive.org/web/20090716030438/http://demo.magentocommerce.com/&#34;&gt;Magento Demo&lt;/a&gt; are used as references.&lt;/p&gt;
&lt;h3 id=&#34;duplicate-home-pages-www-non-www-indexhtml&#34;&gt;Duplicate Home Pages (www, non-www, index.html)&lt;/h3&gt;
&lt;p&gt;Duplicate home pages can come in the form of a homepage with www and without www, a homepage in the form of http://www.domain.com/ and a homepage with some variation of “index” appended to the url, or a combination of the two. In the Interchange demo, &lt;a href=&#34;http://demo.icdevgroup.org/i/demo1&#34;&gt;http://demo.icdevgroup.org/i/demo1&lt;/a&gt; and &lt;a href=&#34;http://demo.icdevgroup.org/i/demo1/index.html&#34;&gt;http://demo.icdevgroup.org/i/demo1/index.html&lt;/a&gt; are duplicate, http://demo.spreecommerce.com/ and http://demo.spreecommerce.com/products/ in the Spree demo, and finally http://demo.magentocommerce.com/ and http://demo.magentocommerce.com/index.php in the Magento demo.&lt;/p&gt;
&lt;p&gt;External links positively influence search engine performance more if they are pointing to one index page rather than being divided between two or three home pages. Since the homepage most likely receives the most external links, this issue can be more problematic than other generated duplicate content. I’ve also seen this happen in several content management systems.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://web.archive.org/web/20090311033516/http://yoast.com/magento-seo/&#34;&gt;This article&lt;/a&gt; provides directions on mod_rewrite use to apply a 301 redirect from the www.domain.com/index.php homepage to www.domain.com. This solution or other redirect solutions can be applied to Spree, Interchange, and other ecommerce platforms.&lt;/p&gt;
&lt;h3 id=&#34;irrelevant-product-urls&#34;&gt;Irrelevant Product URLs&lt;/h3&gt;
&lt;p&gt;A search engine optimization best practice is to provide relevant and indicative text in the product urls. In the Interchange demo, the default catalog uses the product sku in the product url (http://demo.icdevgroup.org/i/demo1/os28073.html). In Magento and Spree, product permalinks with relevant text are used in the product url. In wordpress, the author has the ability to set permalinks for articles. I am unsure if Magento gives you the ability to customize product urls. Spree does not currently give you the ability to manage custom product permalinks. However, for all of these ecommerce platforms, these fixes may all be in the works since it is important for ecommerce platforms to implement search engine optimization best practices.&lt;/p&gt;
&lt;h3 id=&#34;duplicate-product-content&#34;&gt;Duplicate product content&lt;/h3&gt;
&lt;p&gt;I’ve observed several situations where products divided into multiple taxonomies results in duplicate content creation via different user navigation paths. For example, in the Spree demo, the “Ruby Baseball Jersey” can be reached through the Ruby brand page, the Clothing page, or the homepage. The three generated duplicate content urls are http://demo.spreecommerce.com/products/ruby-on-rails-ringer-t-shirt, http://demo.spreecommerce.com/t/brands/ruby/p/ruby-baseball-jersey, and http://demo.spreecommerce.com/t/categories/clothing/shirts/p/ruby-baseball-jersey.&lt;/p&gt;
&lt;p&gt;Another example of this can be found in the Interchange demo. The left navigation taxonomy tree provides links to any product url with “?open=X,Y,Z” appended to the url. The “open” query string indicates how the DHTML tree should be displayed. For example, the “Digger Hand Trencher” has a base url of http://demo.icdevgroup.org/i/demo1/os28076.html. Depending on which tree nodes are exploded, the product can be reached at http://demo.icdevgroup.org/i/demo1/os28076.html?open=0,11,13,19, http://demo.icdevgroup.org/i/demo1/os28076.html?open=0,11,13, etc. This standard demo functionality yields a lot of duplicate content.&lt;/p&gt;
&lt;p&gt;In Magento, products are the in the form of www.domain.com/product-name, although the article I mentioned above mentions that www.domain.com/category/product.html product urls were generated. Perhaps this was a recent fix, or perhaps the demo is configured to avoid generating this type of duplicate content.&lt;/p&gt;
&lt;p&gt;Duplicate product page content is often used to indicate which breadcrumb should display or to track user click-through behavior (for example, did a user click on a “featured product”? a “best seller”? a specific “product advertisement”?). In Interchange, session ids are appended to urls which is another source of duplicate content. Instead of using the url to track user navigation or behavior, several other solutions such as using cookies, using a ‘#’ (hash), or using session data can be used to avoid duplicate content generation.&lt;/p&gt;
&lt;h3 id=&#34;performance&#34;&gt;Performance&lt;/h3&gt;
&lt;p&gt;Performance should not be overlooked in ecommerce for search engine optimization. In March of 2008, Google wrote about &lt;a href=&#34;https://adwords.googleblog.com/2008/03/landing-page-load-time-will-soon-be.html&#34;&gt;how landing page load time will be incorporated into the Quality Score for Google Adwords&lt;/a&gt;—​which is also believed to apply to regular search results. And github recently released some data on &lt;a href=&#34;https://github.com/blog/368-keeping-googlebot-happy&#34;&gt;how performance improvements influenced http://www.github.com/ Googlebot visits&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keeping a high content to text ratio, consolidation, minification, and gzipping CSS and JavaScript, and minimizing the use of JavaScript-based suckerfish can all improve search engine performance.&lt;/p&gt;
&lt;p&gt;The Interchange default catalog has a simple template with minimal css and javascript includes, so the developer is responsible for sticking to best performance practices. The Magento demo appears to have decent content to text ratio, but still requires 5 css files that should be consolidated and minified if they are included on every page. Finally, Spree has undergone some changes in the last month and is moving in the direction of including one consolidated javascript file plus any javascript required for extensions on every page, and the &lt;a href=&#34;https://groups.google.com/forum/#!topic/spree-user/gB3hZ3sgLpw&#34;&gt;upcoming release of Spree 0.8.0&lt;/a&gt; will have considerable frontend view improvements.&lt;/p&gt;
&lt;p&gt;Ecommerce platforms should have decent performance—​&lt;a href=&#34;http://yslow.org/&#34;&gt;yslow&lt;/a&gt; or &lt;a href=&#34;http://shop.oreilly.com/product/9780596529307.do&#34;&gt;this book on high performance website essentials&lt;/a&gt; are good resources.&lt;/p&gt;
&lt;h3 id=&#34;lacking-basic-cms-management&#34;&gt;Lacking basic CMS management&lt;/h3&gt;
&lt;p&gt;Basic CMS management such as the ability to manage and update page titles and page meta data is something that has been overlooked by ecommerce platforms in the past, but appears to have been given more attention recently. An ecommerce solution should also have functionality to create and manage static pages.&lt;/p&gt;
&lt;p&gt;The Interchange demo does not have meta description and keyword functionality, however, page titles are equal to product names which is an acceptable default. It’s also very simple to add a static content page (as a developer) and would require just a bit more effort to have this content managed by a database in Interchange. The Spree core is missing some basic CMS management such as page title and meta data management, but this functionality is currently in development. One Spree contributer developed &lt;a href=&#34;https://web.archive.org/web/20090619060215/http://github.com/PeterBerkenbosch/spree-static-content/tree/master&#34;&gt;a Spree extension that provides management of simple static pages using a WYSIWYG editor&lt;/a&gt;. At the moment, Magento appears to have the most traditional content management system functionality out of the box.&lt;/p&gt;
&lt;p&gt;Another area to improve CMS within Ecommerce is to determine a solution to integrate a blog. A quick Google search of “magento add blog” revealed &lt;a href=&#34;https://web.archive.org/web/20090508093030/http://chasesagum.com/setup-a-blog-inside-your-magento-store&#34;&gt;how to set up a wordpress blog in Magento with an extension&lt;/a&gt;. One of End Point’s clients, &lt;a href=&#34;https://www.ccibeauty.com/&#34;&gt;CCI Beauty&lt;/a&gt;, also has &lt;a href=&#34;https://www.ccibeauty.com/blog/&#34;&gt;wordpress integrated into their Interchange setup&lt;/a&gt;. Finally, there has been discussion about the development of “Spradiant”, or &lt;a href=&#34;https://groups.google.com/forum/#!searchin/spree-user/radiant%7Csort:date&#34;&gt;mixing spree and radiant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another missed opportunity in ecommerce platforms is finding a solution to elegantly blend content and product listings to target specific keywords. A “landing page” can have a page title, meta data, and content targeted towards a specific terms. http://www.backcountry.com/store/gear/arcteryx-vests.html and http://www.backcountry.com/store/gear/cargo-pant.html are examples of targeted terms with corresponding products. Going one step farther, search pages themselves can have managed content to attract keywords, such as a page title, and meta data for specific high traffic keywords with the related products. For example, http://www.domain.com/s/ruby_shirt could be a search page for “Ruby Shirt” which contains meaningful content and relevant products.&lt;/p&gt;
&lt;h3 id=&#34;mishandled-product-pagination&#34;&gt;Mishandled Product Pagination&lt;/h3&gt;
&lt;p&gt;Finding a search engine optimization solution for pagination can be a difficult problem in ecommerce. When there are less than 100 products for a site, this shouldn’t be an issue because a simple taxonomy can appropriately group the products with low crawl depth. A website with 10,000 products must balance between keeping a low taxonomy depth to minimize crawl depth and ensure that all products are listed and indexable.&lt;/p&gt;
&lt;p&gt;For example, products may be divided and fit into three levels of navigation: category, subcategory, and group. If there are 10,000 products, divided into 10 categories, 10 subcategories per category, and 10 product groups per category, 10 products can be shown on each group per page with no pagination. However, product taxonomy is not always so ideal. In some groups there may be 2 products and in others there may be 30. Pagination, or pages with an offset of product listings are generated to accommodate these product listings (for example, http://www.backcountry.com/store/group/61/Sun-Hats-Rain-Hats-Safari-Hats.html, http://www.backcountry.com/store/group/61/Sun-Hats-Rain-Hats-Safari-Hats-p1.html).&lt;/p&gt;
&lt;p&gt;A few problems can arise from the pagination solution. First, by web 2.0 standards, the content should be generated via ajax. An SEO friendly ajax solution must be implemented—​where the onclick event refreshes the content, but the links are still crawlable via search engine bots. Second, page 1 with no product offset will have 1 level less of crawl depth, therefore it will receive the most link juice from it’s parent page (subcategory). As a result, there must be thoughtful analysis of which products to present on that page: should high traffic pages get the traffic? should popular items be listed on the first page? should low traffic products be listed to try to bump the traffic on those pages? should products with the most “user interaction” (reviews, qna, ratings) be shown on that page? Another problem that comes up is that the page meta data and title will most likely be very similar since the content is a list of similar products. These two pages can essentially be competing for traffic and may be counted as duplicate content if the page titles and meta data are equal.&lt;/p&gt;
&lt;p&gt;Interchange uses the &lt;a href=&#34;http://docs.icdevgroup.org/cgi-bin/online/tags/more_list.html&#34;&gt;more list&lt;/a&gt; to handle pagination, but this functionality is not search engine friendly as it generates urls such as http://demo.icdevgroup.org/i/demo1/scan/MM=3ffffa066192cba677e1428d7461ddc9:10:19:10.html?mv_more_ip=1&amp;amp;mv_nextpage=results&amp;amp;mv_arg=, http://demo.icdevgroup.org/i/demo1/scan/MM=3ffffa066192cba677e1428d7461ddc9:20:27:10.html?mv_more_ip=1&amp;amp;mv_nextpage=results&amp;amp;mv_arg=, etc. The Spree demo had some pagination implementation, but upon recent frontend changes, it is no longer included in the demo. The Magento demo was carefully arranged so that product group pages have no more than 9 products to avoid showing any pagination functionality. However, when modifying the number of products displayed per group or using the “Sort By” mechanism, ?limit=Y and &amp;amp;order=X&amp;amp;dir=asc is appended to the url—​which can produce a large volume of duplicate content (try filters on &lt;a href=&#34;https://web.archive.org/web/20090227041133/http://demo.magentocommerce.com/apparel/shirts&#34;&gt;this page&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;It is difficult to determine which of the above problems is the most problematic. From personal experience, I have been involved in tackling all duplicate content issues, and then moving on to “optimization” opportunities such as enhancing the content management system. At the very least, developers and users of any ecommerce platform should be aware of common search engine optimization issues.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Varnish, Radiant, etc.</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2008/10/varnish-radiant-etc_26/"/>
      <id>https://www.endpointdev.com/blog/2008/10/varnish-radiant-etc_26/</id>
      <published>2008-10-26T00:00:00+00:00</published>
      <author>
        <name>Ethan Rowe</name>
      </author>
      <content type="html">
        &lt;p&gt;As my colleague &lt;a href=&#34;/blog/2008/10/walden-university-presidential-youth/&#34;&gt;Jon mentioned&lt;/a&gt;, the Presidential Youth Debates launched its &lt;a href=&#34;http://www.youthdebate2008.org/&#34;&gt;full debate content this week&lt;/a&gt;. And, as Jon also mentioned, the mix of tools involved was fairly interesting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://radiantcms.org/&#34;&gt;Radiant&lt;/a&gt;, a simple Rails-based CMS&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://varnish.projects.linpro.no/&#34;&gt;Varnish&lt;/a&gt;, a high-performance reverse proxy server&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/s3/&#34;&gt;Amazon Simple Storage Service&lt;/a&gt;, a scalable storage layer&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.postgresql.org/&#34;&gt;PostgreSQL&lt;/a&gt;, the truly marvelous open-source relational database management system that End Point loves so well&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our use of Postgres for this project was not particularly special, and is simply a reflection of our using Postgres by default. So I won’t discuss the Postgres usage further (though it pains me to ignore my favorite piece of the software stack).&lt;/p&gt;
&lt;h3 id=&#34;radiant&#34;&gt;Radiant&lt;/h3&gt;
&lt;p&gt;Dan Collis-Puro, who has done a fair amount of CMS-focused work throughout his career, was the initial engineer on this project and chose Radiant as the backbone of the site. He organized the content within Radiant, configured the &lt;a href=&#34;https://github.com/radiant/radiant-page-attachments-extension/tree/master&#34;&gt;Page Attachments extension&lt;/a&gt; for use with Amazon’s S3 (Simple Storage Service), and designed the organization of videos and thumbnails for easy administration through the standard Radiant admin interface. Furthermore, prior to the release of the debate videos, Dan built a question submission and moderation facility as a Radiant extension, through which users could submit questions that might ultimately get passed along to the candidates for the debate.&lt;/p&gt;
&lt;p&gt;In the last few days prior to launch, it fell to me to get the new debate materials into production, and we had to reorganize the way we wanted to lay out the campaign videos and associated content. Because the initial implementation relied purely on conventions in how page parts and page attachments are used, accomplishing the reorganization was straightforward and easily achieved; it was not the sort of thing that required code tweaks and the like, managed purely through the CMS. It ended up being quite—​dare I say it?—​an agile solution. (Agility! Baked right in! Because it’s opinionated software! Where’s my Mac? It just works! &lt;a href=&#34;https://www.flickr.com/photos/twylo/173895378/&#34;&gt;Think Same.&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;For managing small, simple, straightforward sites, Radiant has much to recommend it. For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the hierarchical management of content/pages is quite effective and intuitive&lt;/li&gt;
&lt;li&gt;a pretty rich set of extensions (such as page attachments)&lt;/li&gt;
&lt;li&gt;the “filter” option on content is quite handy (switch between straight text, fckeditor, etc.) and helpful&lt;/li&gt;
&lt;li&gt;the Radiant tag set for basic templating/logic is easy to use and understand&lt;/li&gt;
&lt;li&gt;the general resources available for organizing content (pages, layouts, snippets) enables and readily encourages effective reuse of content and/or presentation logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said, there are a number of things for which one quickly longs within Radiant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In-place editing user interface: an adminstrative mode of viewing the site in which editing tools would show in-place for the different components on a given page. This is not an uncommon approach to content management. The fact that you can view the site in one window/tab and the admin in another mitigates the pain of not having this feature to a healthy extent, but the ease of use undoubtedly suffers nevertheless.&lt;/li&gt;
&lt;li&gt;Radiant offers different publishing “states” for any given page (“draft”, “published”, “hidden”, etc.), and only publicly displays pages in the “published” state in production. This is certainly helpful, but it is ultimately insufficient. This is no substitute for versioning of resources; there is no way to have a staging version of a given page, in which the staging version is exposed to administrative users only at the same URL as the published version. To get around this, one needs to make an entirely different page that will replace the published page when you’re ready. While it’s possible to work around the problem in this manner, it clutters up the set of resources in the CMS admin UI, and doesn’t fit well with the hierarchical nature of the system; the staging version of a page can’t have the same children as the published version of the page, so any staging involving more than one level of edits is problematic and awkward. That leaves quite a lot to be desired: any engineer who has ever done all development on a production site (no development sites) and moved to version-controlled systems knows full well that working purely against a live system is extremely painful. Content management is no different.&lt;/li&gt;
&lt;li&gt;The page attachments extension, while quite handy in general, has configuration information (such as file size limits and the attachment_fu storage backend to use) hard-coded into its PageAttachment model class definition, rather than abstracting that configuration information into YAML files. Furthermore, it’s all or nothing: you can only use one storage backend, apparently, rather than having the flexibility of choosing different storage backends by the content type of the file attached, or choosing manually when uploading the file, etc. The result in our case is that all page attachments go to Amazon S3, even though videos were the only thing we really wanted to have in S3 (bandwidth on our server is not a concern for simple images and the like).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The in-place editing UI features could presumably be added to Radiant given a reasonable degree of patience. The page attachment criticisms also seem achievable. The versioning, however, is a more fundamental issue. Many CMSes attempt to solve this problem many different ways, and ultimately things tend to get unpleasant. I tend to think that CMSes would do well to learn from version control systems like &lt;a href=&#34;https://git-scm.com/&#34;&gt;Git&lt;/a&gt; in their design; beyond that, &lt;strong&gt;integrate&lt;/strong&gt; with Git: dump the content down to some intelligent serialized format and integrate with git branching, checkin, checkout, pushing, etc. That dandy, glorious future is not easily realized.&lt;/p&gt;
&lt;p&gt;To be clear: Radiant is a very useful, effective, straightforward tool; I would be remiss not to emphasize that the things it does well are more important than the areas that need improvement. As is the case with most software, it could be better. I’d happily use/recommend it for most content management cases I’ve encountered.&lt;/p&gt;
&lt;h3 id=&#34;amazon-s3&#34;&gt;Amazon S3&lt;/h3&gt;
&lt;p&gt;I knew it was only a matter of time before I got to play with Amazon S3. Having read about it, I felt like I pretty much knew what to expect. And the expectations were largely correct: it’s been mostly reliable, fairly straightforward, and its cost-effectiveness will have to be determined over time. A few things did take me by surprise, though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The documentation on certain aspects, particularly the logging is, fairly uninspiring. It could be a lot worse. It could also be a lot better. Given that people pay for this service, I would expect it to be documented extremely well. Of course, given the kind of documentation Microsoft routinely spits out, this expectation clearly lacks any grounding in reality.&lt;/li&gt;
&lt;li&gt;Given that the storage must be distributed under the hood, making usage information aggregation somewhat complicated, it’s nevertheless disappointing that Amazon doesn’t give any interface for capping usage for a given bucket. It’s easy to appreciate that Amazon wouldn’t want to be on the hook over usage caps when the usage data comes in from multiple geographically-scattered servers, presumably without any guarantee of serialization in time. Nevertheless, it’s a totally lame problem. I have reason to believe that Amazon plans to address this soon, for which I can only applaud them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, yeah, Amazon S3 has worked fine and been fine and generally not offended me overmuch.&lt;/p&gt;
&lt;h3 id=&#34;varnish&#34;&gt;Varnish&lt;/h3&gt;
&lt;p&gt;The Presidential Youth Debate project had a number of high-profile sponsors potentially capable of generating significant usage spikes. Given the simplicity of the public-facing portion of the site (read-only content, no forms to submit), scaling out with a caching reverse proxy server was a great option. Fortunately, &lt;a href=&#34;https://varnish-cache.org/&#34;&gt;Varnish&lt;/a&gt; makes it pretty easy; basic Varnish configuration is simple, and putting it in place took relatively little time.&lt;/p&gt;
&lt;p&gt;Why go with Varnish? It’s designed from the ground up to be fast and scalable (check out the &lt;a href=&#34;https://www.varnish-cache.org/trac/wiki/ArchitectNotes&#34;&gt;architecture notes&lt;/a&gt; for an interesting technical read). The time-based caching of resources is a nice approach in this case; we can have the cached representations live for a couple of minutes, which effectively takes the load off of Apache/Rails (we’re running Rails with &lt;a href=&#34;https://www.phusionpassenger.com/&#34;&gt;Phusion Passenger&lt;/a&gt;) while refreshing frequently enough for little CMS-driven tweaks to percolate up in a timely fashion. Furthermore, it’s not a custom caching design, instead relying upon the &lt;a href=&#34;https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13&#34;&gt;fundamentals of caching in HTTP itself&lt;/a&gt;. Varnish, with its Varnish Configuration Language (&lt;a href=&#34;https://www.varnish-cache.org/trac/wiki/VCLExamples&#34;&gt;VCL&lt;/a&gt;), is extremely flexible and configurable, allowing us to easily do things like ignore cookies, normalize domain names (though I ultimately did this in Apache), normalize the annoying Accept-Encoding header values, etc. Furthermore, if the cache interval is too long for a particular change, Varnish gives you a straightforward, expressive way of &lt;a href=&#34;https://www.varnish-cache.org/trac/wiki/VCLExamplePurging&#34;&gt;purging cached representations&lt;/a&gt;, which came in handy on a number of occasions close to launch time.&lt;/p&gt;
&lt;p&gt;A number of us at End Point have been interested in Varnish for some time. We’ve made some core patches: JT Justman tracked down a caching bug when using &lt;a href=&#34;https://en.wikipedia.org/wiki/Edge_Side_Includes&#34;&gt;Edge-Side Includes&lt;/a&gt; (ESI), and Charles Curley and JT have done some work to add native gzip/deflate support in Varnish, though that remains to be released upstream. We’ve also prototyped a system relying on ESI and message-driven cache purging for an up-to-date, high-speed, extremely scalable architecture. (That particular project hasn’t gone into production yet due to the degree of effort required to refactor much of the underlying app to fit the design, though it may still come to pass next year—​I hope!)&lt;/p&gt;
&lt;p&gt;Getting Varnish to play nice with Radiant was a non-issue, because the relative simplicity of the site feature set and content did not require specialized handling of any particular resource: one cache interval was good for all pages. Consequently, rather than fretting about having Radiant issue Cache-Control headers on a per-page basis (which may have been fairly unpleasant, though I didn’t look into it deeply; eventually I’ll need to, though, having gotten modestly hooked on Radiant and less-modestly hooked on Varnish), the setup was refreshingly simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The public site’s domain disallows all access to the Radiant admin, meaning it’s effectively a read-only site.&lt;/li&gt;
&lt;li&gt;The public domain’s Apache container always issues a couple of cache-related headers:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Header always set Cache-Control &amp;#34;public; max-age=120&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Header always set Vary &amp;#34;Accept-Encoding&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Cache-Control header tells clients (Varnish in this case) that it’s acceptable to cache representations for 120 seconds, and that all representations are valid for all users (“public”). We can, if we want, use VCL to clean this out of the representation Varnish passes along to clients (i.e. browsers) so that browsers don’t cache automatically, instead relying on conditional GET. The Vary header tells clients that cache (again, primarily concerned with Varnish here) to consider the “Accept-Encoding” header value of a request when keying cached representations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An entirely separate domain exists that is not fronted by Varnish and allows access to the Radiant admin. We could have it fronted by Varnish with caching deactivated, but the configuration we used keeps things clean and simple.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We use some simple VCL to tell Varnish to ignore cookies (in case of Rails sessions on the public site), to normalize the Accept-Encoding header value to one of “gzip” or “deflate” (or none at all) to avoid caching different versions of the same representation due to inconsistent header values submitted by competing browsers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting all that sorted was, as stated, refreshingly easy. It was a little less easy, surprisingly, to deal with logging. The main Varnish daemon (varnishd) logs to a shared memory block. The logs just sit there (and presumably eventually get overwritten) unless consumed by another process. A varnishlog utility, which can be run as a one-off or as a daemon, reads in the logs and outputs them in various ways. Furthermore, a varnishncsa utility outputs logging information in an Apache/NCSA-inspired “combined log” format (though it includes full URLs in the request string rather than just the path portion, presumably due to the possibility of Varnish fronting many different domains). Neither one of these is particularly complicated, though the varnishlog output is reportedly quite verbose and may need frequent rotation, and when run in daemon mode, both will re-open the log file to which they write upon receiving SIGHUP, meaning they’ll play nice with log rotation routines. I found myself repeatedly wishing, however, that they both interfaced with syslog.&lt;/p&gt;
&lt;p&gt;So, I’m very happy with Varnish at this point. Being a jerk, I must nevertheless publicly pick a few nits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why no syslog support in the logging utilities? Is there a compelling argument against it (I haven’t encountered one, but admittedly I haven’t looked very hard), or is it simply a case of not having been handled yet?&lt;/li&gt;
&lt;li&gt;The VCL snippet we used for normalizing the Accept-Encoding header came right off the Varnish FAQ, and seems to be a pretty common case. I wonder if it would make more sense for this to be part of the default VCL configuration requiring explicit deactivation if not desired. It’s not a big deal either way, but it seems like the vast majority of deployments are likely to use this strategy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s all I have to whine about, so either I’m insufficiently observant or the software effectively solves the problem it set out to address. These options are not mutually exclusive.&lt;/p&gt;
&lt;p&gt;I’m definitely looking forward to further work with Varnish. This project didn’t get into ESI support at all, but the native ESI support, combined with the high-performance caching, seems like a real win, potentially allowing for simplification of resource design in the application server, since documents can be constructed by the edge server (Varnish in this case) from multiple components. That sort of approach to design calls into question many of the standard practices seen in popular (and unpopular) application servers (namely, high-level templating with “pages” fitting into an overall “layout”) but could help engineers keep maintain component encapsulation, think through more effectively the URL space, resource privacy and scoping considerations (whether or not a resource varies per user, by context, etc.), etc. But I digress. Shocking.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
