
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>python on nblock&#39;s ~</title>
  <link href="https://nblock.org/tags/python/index.xml" rel="self"/>
  <link href="https://nblock.org/tags/python/"/>
  <updated>2021-04-09T00:00:00+00:00</updated>
  <id>https://nblock.org/tags/python/</id>
  <author>
    <name>Florian Preinstorfer</name>
  </author>
  <generator>Hugo</generator>
  <entry>
    <title type="html"><![CDATA[Sync date and time on an offline Fronius Datalogger]]></title>
    <link href="https://nblock.org/2021/04/09/sync-date-and-time-on-an-offline-fronius-datalogger/"/>
    <id>https://nblock.org/2021/04/09/sync-date-and-time-on-an-offline-fronius-datalogger/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2021-04-09T00:00:00+00:00</published>
    <updated>2021-04-09T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<h2 id="background-and-introduction">Background and introduction</h2>
<p>I&rsquo;m currently working on extracting metrics from multiple Fronius Symo
inverters. There are essentially two approaches to solve this problem:</p>
<ul>
<li>Connect the inverter with the Internet and upload everything to
SolarWeb, a proprietary metrics platform hosted by Fronius. Use the
Fronius SolarWeb mobile app to view the data.</li>
<li>Operate the device offline and collect the metrics yourself. Fronius
offers a JSON based
<a href="https://www.fronius.com/en/solar-energy/installers-partners/technical-data/all-products/system-monitoring/open-interfaces/fronius-solar-api-json-">API</a>
to query realtime and archive data from an inverter. Furthermore, the
device also offers a push service, where the inverter can upload its
metrics continuously to a FTP server or send it to a HTTP endpoint.
Both methods can be used without in Internet connection.</li>
</ul>
<p>As you might have guessed, I implemented the second approach where each
inverter pushes its metrics continuously to a server on the local
network. From there, it is picked up and imported into InfluxDB. Grafana
is used to visualize the metrics. Please contact me, if you are
interested in how to get collection, transfer to InfluxDB and
visualization up- and running for Fronius inverters.</p>
<h2 id="lost-date-and-time">Lost date and time</h2>
<p>After a few days of metrics collection, I noticed that one of the
inverters regularly loses its local date and time. Getting timeseries
data with a timestamp of 2000-01-01T05:02:15 is not very helpful.</p>
<p>The inverter&rsquo;s webinterface offers to set the system time and also has a
checkbox for &ldquo;Set time automatically&rdquo;.</p>
<p><img loading="lazy" src="/2021/04/09/sync-date-and-time-on-an-offline-fronius-datalogger/fronius-datalogger-general-settings.png" type="" alt="Configure date and time."  title="Configure date and time."  /></p>
<p>Great, simply enable automatic time synchronization and allow outgoing
NTP traffic (and DNS). As it turns out, the inverter does not use NTP to
synchronize its system time. It <em>requires</em> the user to enable the
metrics upload to SolarWeb in order to synchronize its system time!</p>
<p>I did not want to re-implement parts of the proprietary, UDP based
protocol (PCAP dumps are available upon request), so I decided to check
the
<a href="https://www.fronius.com/en/solar-energy/installers-partners/technical-data/all-products/system-monitoring/open-interfaces/fronius-solar-api-json-">API</a>
docs for endpoints related to date and time settings. Unfortunately, I
could not find such an endpoint.</p>
<p>The next approach was to inspect the requests of the web browser and
rebuild the necessary requests in curl. Put those requests in a script
and invoke it regularly. The steps are easy:</p>
<ul>
<li>Authenticate with the device (only HTTP Digest auth is supported)</li>
<li>Set date and time</li>
</ul>
<p>As it turns out, the HTTP Digest implementation does not conform to the
relevant <a href="https://tools.ietf.org/html/rfc7235">RFC 7235</a>. From section
4.1:</p>
<blockquote>
<p>A server generating a 401 (Unauthorized) response MUST send a
WWW-Authenticate header field containing at least one challenge. A
server MAY generate a WWW-Authenticate header field in other response
messages to indicate that supplying credentials (or different
credentials) might affect the response.</p></blockquote>
<p>The datalogger does not respond with a <code>WWW-Authenticate</code> header, but
instead sends a <code>X-WWW-Authenticate</code> header, which breaks the
authentication workflow in curl. As there is no way to override the
expected HTTP header in curl, I decided to implement (the broken)
Fronius HTTP Digest authentication myself in Python. The workaround is
easy, subclass the <code>HTTPDigestAuth</code> class from
<a href="https://github.com/psf/requests/blob/v2.25.1/requests/auth.py#L108">Requests</a>
and fixup the header name before Requests reads the header value.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">requests.auth</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">HTTPDigestAuth</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">class</span> <span style="color:#cb4b16">HTTPDigestAuthFronius</span>(<span style="color:#268bd2">HTTPDigestAuth</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#859900">def</span> <span style="color:#268bd2">handle_401</span>(<span style="color:#cb4b16">self</span>, <span style="color:#268bd2">r</span>, **<span style="color:#268bd2">kwargs</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#93a1a1;font-style:italic"># Replace www-authenticate unconditionally</span>
</span></span><span style="display:flex;"><span>        <span style="color:#268bd2">r</span>.<span style="color:#268bd2">headers</span>[<span style="color:#2aa198">&#34;www-authenticate&#34;</span>] = <span style="color:#268bd2">r</span>.<span style="color:#268bd2">headers</span>.<span style="color:#268bd2">get</span>(<span style="color:#2aa198">&#34;x-www-authenticate&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#859900">return</span> <span style="color:#cb4b16">super</span>().<span style="color:#268bd2">handle_401</span>(<span style="color:#268bd2">r</span>, **<span style="color:#268bd2">kwargs</span>)
</span></span></code></pre></div><p>The entire script is available <a href="fronius_datalogger_set_time.py">here</a> (tested on Python 3.9 with Requests 2.25).</p>
<h2 id="conclusion">Conclusion</h2>
<p>Please use open standards and established protocols for common tasks
such as NTP for keeping the system time in sync. Furthermore, don&rsquo;t mess
with the standards, just implement/use them as-is and test them with
standard tools such as curl.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[Tryton GTK client inside a virtualenv]]></title>
    <link href="https://nblock.org/2018/09/27/tryton-gtk-client-inside-a-virtualenv/"/>
    <id>https://nblock.org/2018/09/27/tryton-gtk-client-inside-a-virtualenv/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2018-09-27T00:00:00+00:00</published>
    <updated>2018-09-27T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>I&rsquo;m currently working on a <a href="https://tryton.org">Tryton</a> upgrade from 4.2
to 5.0 (via intermediate releases 4.4, 4.6 and 4.8).
<a href="https://tryton.org">Tryton</a> 5.0 is the first long term support release
with support for 5 years. A useful property for an ERP system. In order
to test each of the intermediate versions, I decided to quickly spawn a
virtualenv and install the <a href="https://tryton.org">Tryton</a> GTK client in
there.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ virtualenv -p python2 venv-tryton-4.4
</span></span><span style="display:flex;"><span>$ . venv-tryton-4.4/bin/activate
</span></span><span style="display:flex;"><span>$ pip install tryton~=4.4
</span></span><span style="display:flex;"><span>$ tryton
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>  File <span style="color:#2aa198">&#34;~/venv-tryton-4.4/bin/tryton&#34;</span>, line 48, in &lt;module&gt;
</span></span><span style="display:flex;"><span>    from tryton import client
</span></span><span style="display:flex;"><span>  File <span style="color:#2aa198">&#34;~/venv-tryton-4.4/local/lib/python2.7/site-packages/tryton/client.py&#34;</span>, line 17, in &lt;module&gt;
</span></span><span style="display:flex;"><span>    import pygtk
</span></span><span style="display:flex;"><span>ImportError: No module named pygtk
</span></span></code></pre></div><p>Installing <code>pygtk</code> and its dependencies in a virtualenv is not without
its problems. But, there is a rather quick (and hackish) solution to
this problem. This is only suitable for quick tests and experiments. Use
proper Debian packages on production systems.</p>
<h2 id="python-27">Python 2.7</h2>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ virtualenv -p python2 venv-tryton-4.4
</span></span><span style="display:flex;"><span>$ . venv-tryton-4.4/bin/activate
</span></span><span style="display:flex;"><span>$ pip install tryton~=4.4
</span></span><span style="display:flex;"><span>$ pip install PyGObject
</span></span><span style="display:flex;"><span>$ ln -s /usr/lib/python2.7/dist-packages/pygtk.py <span style="color:#268bd2">$VIRTUAL_ENV</span>/lib/python2.7/site-packages
</span></span><span style="display:flex;"><span>$ ln -s /usr/lib/python2.7/dist-packages/gtk-2.0 <span style="color:#268bd2">$VIRTUAL_ENV</span>/lib/python2.7/site-packages
</span></span><span style="display:flex;"><span>$ ln -s /usr/lib/python2.7/dist-packages/gobject <span style="color:#268bd2">$VIRTUAL_ENV</span>/lib/python2.7/site-packages
</span></span><span style="display:flex;"><span>$ ln -s /usr/lib/python2.7/dist-packages/glib <span style="color:#268bd2">$VIRTUAL_ENV</span>/lib/python2.7/site-packages
</span></span><span style="display:flex;"><span>$ ln -s /usr/lib/python2.7/dist-packages/gi <span style="color:#268bd2">$VIRTUAL_ENV</span>/lib/python2.7/site-packages
</span></span><span style="display:flex;"><span>$ ln -s /usr/lib/python2.7/dist-packages/pygtkcompat <span style="color:#268bd2">$VIRTUAL_ENV</span>/lib/python2.7/site-packages
</span></span><span style="display:flex;"><span>$ tryton
</span></span></code></pre></div><p>Obviously, GTK 2 and the respective Python bindings must be installed.</p>
<h2 id="python-3x">Python 3.x</h2>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ virtualenv -p python3 venv-tryton-4.8
</span></span><span style="display:flex;"><span>$ . venv-tryton-4.8/bin/activate
</span></span><span style="display:flex;"><span>$ pip install tryton~=4.8
</span></span><span style="display:flex;"><span>$ pip install PyGObject
</span></span><span style="display:flex;"><span>$ tryton
</span></span></code></pre></div><p>By the way: <a href="https://tryton.org">Tryton</a> 5.0 and later versions will
only support Python 3.x and GTK 3.</p>
<p>Tested on Debian Testing with Python 2.7.15, Python 3.6.6 and
<a href="https://tryton.org">Tryton</a> 4.4, 4.6, 4.8 and 5.0.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[SQLAlchemy automap and custom types]]></title>
    <link href="https://nblock.org/2018/04/03/sqlalchemy-automap-and-custom-types/"/>
    <id>https://nblock.org/2018/04/03/sqlalchemy-automap-and-custom-types/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2018-04-03T00:00:00+00:00</published>
    <updated>2018-04-03T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>An API is an important part of an application when it comes to
automation. For those web applications that do not offer an API, I
typically write some small application on top of
<a href="https://scrapy.org/">Scrapy</a> or
<a href="http://docs.python-requests.org/en/master/">Requests</a> to solve the
problem.</p>
<p>The application in question is an old PHP application. There is no API
available and adding an API to it is out of scope. MySQL is used as the
database and most of the 79 tables are stored with MyISAM. Recently
added tables are stored with InnoDB. There are just a few unique
constraints on the database side and no foreign key constraints (due to
MyISAM). The entire logic is coded into the PHP application.</p>
<p>The web application&rsquo;s database can be accessed directly, so there is no
need for scraping. The following SQL listing provides a minimal working
example for the purpose of this blog post.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic">-- create tables
</span></span></span><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic"></span><span style="color:#859900">CREATE</span> <span style="color:#859900">TABLE</span> <span style="color:#859900">IF</span> <span style="color:#859900">NOT</span> <span style="color:#859900">EXISTS</span> `<span style="color:#268bd2">hosts</span>` (
</span></span><span style="display:flex;"><span>  `<span style="color:#268bd2">id</span>` <span style="color:#cb4b16">int</span>(<span style="color:#2aa198;font-weight:bold">11</span>) <span style="color:#859900">NOT</span> <span style="color:#859900">NULL</span> <span style="color:#268bd2">AUTO_INCREMENT</span>,
</span></span><span style="display:flex;"><span>  `<span style="color:#268bd2">name</span>` <span style="color:#cb4b16">varchar</span>(<span style="color:#2aa198;font-weight:bold">255</span>) <span style="color:#859900">NOT</span> <span style="color:#859900">NULL</span>,
</span></span><span style="display:flex;"><span>  `<span style="color:#859900">state</span>` <span style="color:#cb4b16">int</span>(<span style="color:#2aa198;font-weight:bold">1</span>) <span style="color:#859900">NOT</span> <span style="color:#859900">NULL</span> <span style="color:#859900">DEFAULT</span> <span style="color:#2aa198">&#39;1&#39;</span>,
</span></span><span style="display:flex;"><span>  `<span style="color:#268bd2">os_id</span>` <span style="color:#cb4b16">int</span>(<span style="color:#2aa198;font-weight:bold">11</span>) <span style="color:#859900">NOT</span> <span style="color:#859900">NULL</span> <span style="color:#859900">DEFAULT</span> <span style="color:#2aa198">&#39;1&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#859900">PRIMARY</span> <span style="color:#859900">KEY</span> (`<span style="color:#268bd2">id</span>`),
</span></span><span style="display:flex;"><span>  <span style="color:#859900">UNIQUE</span> <span style="color:#859900">KEY</span> `<span style="color:#268bd2">name</span>` (`<span style="color:#268bd2">name</span>`)
</span></span><span style="display:flex;"><span>) <span style="color:#268bd2">ENGINE</span>=<span style="color:#268bd2">MyISAM</span>  <span style="color:#859900">DEFAULT</span> <span style="color:#268bd2">CHARSET</span>=<span style="color:#268bd2">latin1</span> <span style="color:#268bd2">AUTO_INCREMENT</span>=<span style="color:#2aa198;font-weight:bold">8</span> ;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">CREATE</span> <span style="color:#859900">TABLE</span> <span style="color:#859900">IF</span> <span style="color:#859900">NOT</span> <span style="color:#859900">EXISTS</span> `<span style="color:#268bd2">os_types</span>` (
</span></span><span style="display:flex;"><span>  `<span style="color:#268bd2">id</span>` <span style="color:#cb4b16">int</span>(<span style="color:#2aa198;font-weight:bold">11</span>) <span style="color:#859900">NOT</span> <span style="color:#859900">NULL</span> <span style="color:#268bd2">AUTO_INCREMENT</span>,
</span></span><span style="display:flex;"><span>  `<span style="color:#268bd2">name</span>` <span style="color:#cb4b16">varchar</span>(<span style="color:#2aa198;font-weight:bold">255</span>) <span style="color:#859900">NOT</span> <span style="color:#859900">NULL</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#859900">PRIMARY</span> <span style="color:#859900">KEY</span> (`<span style="color:#268bd2">id</span>`),
</span></span><span style="display:flex;"><span>  <span style="color:#859900">UNIQUE</span> <span style="color:#859900">KEY</span> `<span style="color:#268bd2">name</span>` (`<span style="color:#268bd2">name</span>`)
</span></span><span style="display:flex;"><span>) <span style="color:#268bd2">ENGINE</span>=<span style="color:#268bd2">MyISAM</span>  <span style="color:#859900">DEFAULT</span> <span style="color:#268bd2">CHARSET</span>=<span style="color:#268bd2">latin1</span> <span style="color:#268bd2">AUTO_INCREMENT</span>=<span style="color:#2aa198;font-weight:bold">4</span> ;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic">-- stuff some sample data into the tables
</span></span></span><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic"></span><span style="color:#859900">INSERT</span> <span style="color:#859900">INTO</span> `<span style="color:#268bd2">hosts</span>` (`<span style="color:#268bd2">id</span>`, `<span style="color:#268bd2">name</span>`, `<span style="color:#859900">state</span>`, `<span style="color:#268bd2">os_id</span>`) <span style="color:#859900">VALUES</span>
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">1</span>, <span style="color:#2aa198">&#39;astoria&#39;</span>, <span style="color:#2aa198;font-weight:bold">1</span>, <span style="color:#2aa198;font-weight:bold">3</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">2</span>, <span style="color:#2aa198">&#39;fiddle&#39;</span>, <span style="color:#2aa198;font-weight:bold">2</span>, <span style="color:#2aa198;font-weight:bold">2</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">3</span>, <span style="color:#2aa198">&#39;freeman&#39;</span>, <span style="color:#2aa198;font-weight:bold">4</span>, <span style="color:#2aa198;font-weight:bold">3</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">4</span>, <span style="color:#2aa198">&#39;liard&#39;</span>, <span style="color:#2aa198;font-weight:bold">4</span>, <span style="color:#2aa198;font-weight:bold">3</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">5</span>, <span style="color:#2aa198">&#39;leand&#39;</span>, <span style="color:#2aa198;font-weight:bold">1</span>, <span style="color:#2aa198;font-weight:bold">1</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">6</span>, <span style="color:#2aa198">&#39;algar&#39;</span>, <span style="color:#2aa198;font-weight:bold">1</span>, <span style="color:#2aa198;font-weight:bold">2</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">7</span>, <span style="color:#2aa198">&#39;ells&#39;</span>, <span style="color:#2aa198;font-weight:bold">3</span>, <span style="color:#2aa198;font-weight:bold">2</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">INSERT</span> <span style="color:#859900">INTO</span> `<span style="color:#268bd2">os_types</span>` (`<span style="color:#268bd2">id</span>`, `<span style="color:#268bd2">name</span>`) <span style="color:#859900">VALUES</span>
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">1</span>, <span style="color:#2aa198">&#39;Debian Wheezy&#39;</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">2</span>, <span style="color:#2aa198">&#39;Debian Jessie&#39;</span>),
</span></span><span style="display:flex;"><span>(<span style="color:#2aa198;font-weight:bold">3</span>, <span style="color:#2aa198">&#39;Debian Stretch&#39;</span>);
</span></span></code></pre></div><p>There are two tables, <code>hosts</code> and <code>os_types</code>. The rows of the <code>os_types</code>
table are referenced via <code>hosts.os_id</code> inside the PHP application. There
is no immediate connection on the database level. The <code>hosts</code> table
contains a <code>state</code> column with the following magic numbers:</p>
<ul>
<li>1: active</li>
<li>2: disabled</li>
<li>3: unknown</li>
<li>4: deleted</li>
</ul>
<p>The task is simple: Connect to the database and print the name, state
and the name of the OS.</p>
<h2 id="try-1">Try #1</h2>
<p>I don&rsquo;t want to write SQL by hand and I certainly don&rsquo;t want to remember
all the magic numbers. So, I decided to give
<a href="http://www.sqlalchemy.org/">SQLAlchemy</a>, a popular Object Relational
Mapper for Python, another try. The typical usage is to define your
model in plain Python and let <a href="http://www.sqlalchemy.org/">SQLAlchemy</a>
manage the database side for you. This is convenient for new projects or
if the database has 5 tables in total. The database of this application
manages 79 tables and some of them contain a lot of columns (e.g. 26
columns for a single table). That&rsquo;s too much typing. Fortunately,
<a href="http://www.sqlalchemy.org/">SQLAlchemy</a> offers a feature called
<a href="http://docs.sqlalchemy.org/en/latest/orm/extensions/automap.html">Automap</a>
where it connects to a database, inspects the tables and tries to figure
out the models for you. OK, now some code:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">create_engine</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.ext.automap</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">automap_base</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.orm</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Session</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic"># The declarative base used for the SQLAlchemy reflection.</span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2">Base</span> = <span style="color:#268bd2">automap_base</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">def</span> <span style="color:#268bd2">main</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">engine</span> = <span style="color:#268bd2">create_engine</span>(<span style="color:#2aa198">&#39;mysql://USER:PASS@HOST:PORT/DATABASE&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Perform automap and create a session</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">Base</span>.<span style="color:#268bd2">prepare</span>(<span style="color:#268bd2">engine</span>, <span style="color:#268bd2">reflect</span>=<span style="color:#859900;font-weight:bold">True</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">session</span> = <span style="color:#268bd2">Session</span>(<span style="color:#268bd2">engine</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Use the session</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">Hosts</span> = <span style="color:#268bd2">Base</span>.<span style="color:#268bd2">classes</span>.<span style="color:#268bd2">hosts</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">OSTypes</span> = <span style="color:#268bd2">Base</span>.<span style="color:#268bd2">classes</span>.<span style="color:#268bd2">os_types</span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">for</span> <span style="color:#268bd2">host</span> <span style="color:#859900">in</span> <span style="color:#268bd2">session</span>.<span style="color:#268bd2">query</span>(<span style="color:#268bd2">Hosts</span>).<span style="color:#268bd2">filter_by</span>(<span style="color:#268bd2">state</span>=<span style="color:#2aa198;font-weight:bold">1</span>).<span style="color:#268bd2">all</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#268bd2">os_type</span> = <span style="color:#268bd2">session</span>.<span style="color:#268bd2">query</span>(<span style="color:#268bd2">OSTypes</span>).<span style="color:#268bd2">get</span>(<span style="color:#268bd2">host</span>.<span style="color:#268bd2">os_id</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#cb4b16">print</span>(<span style="color:#268bd2">host</span>.<span style="color:#268bd2">name</span>, <span style="color:#268bd2">host</span>.<span style="color:#268bd2">state</span>, <span style="color:#268bd2">os_type</span>.<span style="color:#268bd2">name</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">if</span> <span style="color:#268bd2">__name__</span> == <span style="color:#2aa198">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">main</span>()
</span></span></code></pre></div><p>Run it:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ python sqlalchemy1.py
</span></span><span style="display:flex;"><span>astoria <span style="color:#2aa198;font-weight:bold">1</span> Debian Stretch
</span></span><span style="display:flex;"><span>leand <span style="color:#2aa198;font-weight:bold">1</span> Debian Wheezy
</span></span><span style="display:flex;"><span>algar <span style="color:#2aa198;font-weight:bold">1</span> Debian Jessie
</span></span></code></pre></div><p>It works but there are some obvious limitations.
<a href="http://www.sqlalchemy.org/">SQLAlchemy</a> was not able to figure out the
relationship between <code>hosts.os_id</code> and <code>os_types.id</code>. So the programmer
has to manage the relationship by hand (this happens in the PHP
application). This is not only cumbersome but also error prone as one
could just set <code>os_id</code> to an unreferenced value. Let&rsquo;s try to fix that.</p>
<h2 id="try-2">Try #2</h2>
<p>One can provide some hints for <a href="http://www.sqlalchemy.org/">SQLAlchemy</a>
in order to build up a relationship. Take a look at the following
version:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Column</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">ForeignKey</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Integer</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">create_engine</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.ext.automap</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">automap_base</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.orm</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Session</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.orm</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">relationship</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic"># The declarative base used for the SQLAlchemy reflection.</span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2">Base</span> = <span style="color:#268bd2">automap_base</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">class</span> <span style="color:#cb4b16">Hosts</span>(<span style="color:#268bd2">Base</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">__tablename__</span> = <span style="color:#2aa198">&#39;hosts&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># custom types</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">os_id</span> = <span style="color:#268bd2">Column</span>(<span style="color:#268bd2">Integer</span>, <span style="color:#268bd2">ForeignKey</span>(<span style="color:#2aa198">&#39;os_types.id&#39;</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># relationships</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">os</span> = <span style="color:#268bd2">relationship</span>(<span style="color:#2aa198">&#39;os_types&#39;</span>, <span style="color:#268bd2">backref</span>=<span style="color:#2aa198">&#39;hosts&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">def</span> <span style="color:#268bd2">main</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">engine</span> = <span style="color:#268bd2">create_engine</span>(<span style="color:#2aa198">&#39;mysql://USER:PASS@HOST:PORT/DATABASE&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Perform automap and create a session</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">Base</span>.<span style="color:#268bd2">prepare</span>(<span style="color:#268bd2">engine</span>, <span style="color:#268bd2">reflect</span>=<span style="color:#859900;font-weight:bold">True</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">session</span> = <span style="color:#268bd2">Session</span>(<span style="color:#268bd2">engine</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Use the session</span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">for</span> <span style="color:#268bd2">host</span> <span style="color:#859900">in</span> <span style="color:#268bd2">session</span>.<span style="color:#268bd2">query</span>(<span style="color:#268bd2">Hosts</span>).<span style="color:#268bd2">filter_by</span>(<span style="color:#268bd2">state</span>=<span style="color:#2aa198;font-weight:bold">1</span>).<span style="color:#268bd2">all</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#cb4b16">print</span>(<span style="color:#268bd2">host</span>.<span style="color:#268bd2">name</span>, <span style="color:#268bd2">host</span>.<span style="color:#268bd2">state</span>, <span style="color:#268bd2">host</span>.<span style="color:#268bd2">os</span>.<span style="color:#268bd2">name</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">if</span> <span style="color:#268bd2">__name__</span> == <span style="color:#2aa198">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">main</span>()
</span></span></code></pre></div><p>Run it:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ python sqlalchemy2.py
</span></span><span style="display:flex;"><span>astoria <span style="color:#2aa198;font-weight:bold">1</span> Debian Stretch
</span></span><span style="display:flex;"><span>leand <span style="color:#2aa198;font-weight:bold">1</span> Debian Wheezy
</span></span><span style="display:flex;"><span>algar <span style="color:#2aa198;font-weight:bold">1</span> Debian Jessie
</span></span></code></pre></div><p>I just added a <code>Hosts</code> class that maps to the <code>hosts</code> table. It adds a
<code>ForeignKey</code> to the <code>os_id</code> column and a relationship named <code>os</code>. Usage
is much simpler now: it boils down to a single query and there is no
need to look up the name of the operating system by hand. But still,
there is a magic number in use (<code>state=1</code>).</p>
<h2 id="try-3">Try #3</h2>
<p>As noted above, the <code>state</code> column has 4 known values. This pretty much
looks like an enum. <a href="http://www.sqlalchemy.org/">SQLAlchemy</a> has another
nice feature for this particular use case: <a href="http://docs.sqlalchemy.org/en/latest/core/custom_types.html">custom
types</a>. The
following version replaces the magic numbers with a custom type
implemented as Python enum:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">enum</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Column</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">ForeignKey</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Integer</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">TypeDecorator</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">create_engine</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.ext.automap</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">automap_base</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.orm</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Session</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">sqlalchemy.orm</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">relationship</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic"># The declarative base used for the SQLAlchemy reflection.</span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2">Base</span> = <span style="color:#268bd2">automap_base</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2">@enum.unique</span>
</span></span><span style="display:flex;"><span><span style="color:#859900">class</span> <span style="color:#cb4b16">HostState</span>(<span style="color:#268bd2">enum</span>.<span style="color:#268bd2">IntEnum</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># This host is active and currently in use.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">ACTIVE</span> = <span style="color:#2aa198;font-weight:bold">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># It is no longer in use, it is turned off.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">DISABLED</span> = <span style="color:#2aa198;font-weight:bold">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Unknown, literally</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">UNKNOWN</span> = <span style="color:#2aa198;font-weight:bold">3</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># This host is gone, away forever.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">DELETED</span> = <span style="color:#2aa198;font-weight:bold">4</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">class</span> <span style="color:#cb4b16">HostStateTypeDecorator</span>(<span style="color:#268bd2">TypeDecorator</span>):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">impl</span> = <span style="color:#268bd2">Integer</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">def</span> <span style="color:#268bd2">process_bind_param</span>(<span style="color:#cb4b16">self</span>, <span style="color:#268bd2">value</span>, <span style="color:#268bd2">dialect</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#859900">if</span> <span style="color:#cb4b16">isinstance</span>(<span style="color:#268bd2">value</span>, <span style="color:#268bd2">HostState</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#268bd2">value</span> = <span style="color:#268bd2">value</span>.<span style="color:#268bd2">value</span>
</span></span><span style="display:flex;"><span>        <span style="color:#859900">return</span> <span style="color:#268bd2">value</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">def</span> <span style="color:#268bd2">process_result_value</span>(<span style="color:#cb4b16">self</span>, <span style="color:#268bd2">value</span>, <span style="color:#268bd2">dialect</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#859900">if</span> <span style="color:#268bd2">value</span> <span style="color:#859900">is</span> <span style="color:#859900">not</span> <span style="color:#859900;font-weight:bold">None</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#268bd2">value</span> = <span style="color:#268bd2">HostState</span>(<span style="color:#268bd2">value</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#859900">return</span> <span style="color:#268bd2">value</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">class</span> <span style="color:#cb4b16">Hosts</span>(<span style="color:#268bd2">Base</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">__tablename__</span> = <span style="color:#2aa198">&#39;hosts&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># custom types</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">state</span> = <span style="color:#268bd2">Column</span>(<span style="color:#268bd2">HostStateTypeDecorator</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">os_id</span> = <span style="color:#268bd2">Column</span>(<span style="color:#268bd2">Integer</span>, <span style="color:#268bd2">ForeignKey</span>(<span style="color:#2aa198">&#39;os_types.id&#39;</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># relationships</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">os</span> = <span style="color:#268bd2">relationship</span>(<span style="color:#2aa198">&#39;os_types&#39;</span>, <span style="color:#268bd2">backref</span>=<span style="color:#2aa198">&#39;hosts&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">def</span> <span style="color:#268bd2">main</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">engine</span> = <span style="color:#268bd2">create_engine</span>(<span style="color:#2aa198">&#39;mysql://USER:PASS@HOST:PORT/DATABASE&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Perform automap and create a session</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">Base</span>.<span style="color:#268bd2">prepare</span>(<span style="color:#268bd2">engine</span>, <span style="color:#268bd2">reflect</span>=<span style="color:#859900;font-weight:bold">True</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">session</span> = <span style="color:#268bd2">Session</span>(<span style="color:#268bd2">engine</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Use the session</span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">for</span> <span style="color:#268bd2">host</span> <span style="color:#859900">in</span> <span style="color:#268bd2">session</span>.<span style="color:#268bd2">query</span>(<span style="color:#268bd2">Hosts</span>).<span style="color:#268bd2">filter_by</span>(<span style="color:#268bd2">state</span>=<span style="color:#268bd2">HostState</span>.<span style="color:#268bd2">ACTIVE</span>).<span style="color:#268bd2">all</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#cb4b16">print</span>(<span style="color:#268bd2">host</span>.<span style="color:#268bd2">name</span>, <span style="color:#268bd2">host</span>.<span style="color:#268bd2">state</span>, <span style="color:#268bd2">host</span>.<span style="color:#268bd2">os</span>.<span style="color:#268bd2">name</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">if</span> <span style="color:#268bd2">__name__</span> == <span style="color:#2aa198">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">main</span>()
</span></span></code></pre></div><p>Run it:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ python sqlalchemy3.py
</span></span><span style="display:flex;"><span>astoria HostState.ACTIVE Debian Stretch
</span></span><span style="display:flex;"><span>leand HostState.ACTIVE Debian Wheezy
</span></span><span style="display:flex;"><span>algar HostState.ACTIVE Debian Jessie
</span></span></code></pre></div><p>Sweet, the magic numbers are now gone and one can build a query using
the custom type: <code>state=HostState.ACTIVE</code>. Magic numbers are converted
in both directions via <code>process_result_value()</code> and
<code>process_bind_param()</code>.</p>
<p>Tested with Python 3.6.4 and <a href="http://www.sqlalchemy.org/">SQLAlchemy</a>
1.2.6.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[Tryton keyboard shortcuts]]></title>
    <link href="https://nblock.org/2017/04/10/keyboard-shortcuts-in-tryton/"/>
    <id>https://nblock.org/2017/04/10/keyboard-shortcuts-in-tryton/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2017-04-10T00:00:00+00:00</published>
    <updated>2017-04-10T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>The Tryton GTK client provides numerous keyboard shortcuts to speed up
data entry in widgets. This post aims to document them along with their
pitfalls.</p>
<h2 id="date-datetime-and-time-widgets">Date, datetime, and time widgets</h2>
<p>The <a href="http://doc.tryton.org/4.2/tryton/doc/usage.html#date-datetime-time-widgets">client
documentation</a>
covers keyboard shortcuts for those widgets, too. The rules are:</p>
<ul>
<li>Lower case letters increase the value by one unit.</li>
<li>Upper case letters decrease the value by one unit.</li>
</ul>
<p>The following keyboard shortcuts are defined:</p>
<table>
  <thead>
      <tr>
          <th><strong>Increase</strong></th>
          <th><strong>Decrease</strong></th>
          <th><strong>Operation</strong></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>s</code></td>
          <td><code>S</code></td>
          <td>± 1 second</td>
      </tr>
      <tr>
          <td><code>i</code></td>
          <td><code>I</code></td>
          <td>± 1 minute</td>
      </tr>
      <tr>
          <td><code>h</code></td>
          <td><code>H</code></td>
          <td>± 1 hour</td>
      </tr>
      <tr>
          <td><code>d</code></td>
          <td><code>D</code></td>
          <td>± 1 day</td>
      </tr>
      <tr>
          <td><code>w</code></td>
          <td><code>W</code></td>
          <td>± 1 week</td>
      </tr>
      <tr>
          <td><code>m</code></td>
          <td><code>M</code></td>
          <td>± 1 month</td>
      </tr>
      <tr>
          <td><code>y</code></td>
          <td><code>Y</code></td>
          <td>± 1 year</td>
      </tr>
  </tbody>
</table>
<h2 id="timedelta-widgets">Timedelta widgets</h2>
<p>Some fields, such as lead time on supplier use timedelta widgets. They
also support shortcuts, but they are different depending on the user
interface language.</p>
<p>The following keyboard shortcuts are defined for English and German:</p>
<table>
  <thead>
      <tr>
          <th><strong>EN</strong></th>
          <th><strong>DE</strong></th>
          <th><strong>Operation</strong></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>s</code></td>
          <td><code>s</code></td>
          <td>1 second</td>
      </tr>
      <tr>
          <td><code>m</code></td>
          <td><code>m</code></td>
          <td>1 minute</td>
      </tr>
      <tr>
          <td><code>h</code></td>
          <td><code>h</code></td>
          <td>1 hour</td>
      </tr>
      <tr>
          <td><code>d</code></td>
          <td><code>t</code></td>
          <td>1 day</td>
      </tr>
      <tr>
          <td><code>w</code></td>
          <td><code>W</code></td>
          <td>1 week (= 7 days)</td>
      </tr>
      <tr>
          <td><code>M</code></td>
          <td><code>M</code></td>
          <td>1 month (= 30 days)</td>
      </tr>
      <tr>
          <td><code>Y</code></td>
          <td><code>J</code></td>
          <td>1 year (=365 days)</td>
      </tr>
  </tbody>
</table>
<p>Here are a few usage examples for English:</p>
<ul>
<li><code>2Y</code>: 2 years</li>
<li><code>1M</code>: 1 months</li>
<li><code>2w</code>: 2 weeks</li>
<li><code>3d</code>: 3 days</li>
<li><code>5h</code> or <code>05:00</code>: 5 hours</li>
<li><code>45m</code> or <code>00:45</code>: 45 minutes</li>
<li><code>1d 3h 10m</code>: 1 day, 3 hours and 10 minutes</li>
</ul>
<p>See the implementation for <a href="http://hg.tryton.org/tryton/file/4.2/tryton/common/timedelta.py#l13">Tryton
4.2</a>
along with the <a href="http://hg.tryton.org/tryton/file/4.2/tryton/data/locale/de/LC_MESSAGES/tryton.po#l329">german
translation</a>.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[Notes on upgrading from Tryton 4.0 to Tryton 4.2]]></title>
    <link href="https://nblock.org/2016/12/15/notes-on-upgrading-from-tryton-4.0-to-tryton-4.2/"/>
    <id>https://nblock.org/2016/12/15/notes-on-upgrading-from-tryton-4.0-to-tryton-4.2/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2016-12-15T00:00:00+00:00</published>
    <updated>2016-12-15T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>The Tryton developers released a <a href="http://www.tryton.org/posts/new-tryton-release-42.html">new version of Tryton a few days
ago</a>. We try to
keep up with the current stable version, so an upgrade is required. This
posts lists the subtle changes that caused problems with our
installation.</p>
<h2 id="before-the-upgrade">Before the upgrade</h2>
<h3 id="custom-and-third-party-modules">Custom and third-party modules</h3>
<ul>
<li>Update dependencies to Tryton 4.2.</li>
<li>Tryton 4.2 reworked the translation system which requires translation
files to be renamed. For all languages, rename the <code>.po</code> files as
follows (See <a href="https://bugs.tryton.org/issue5443">issue5443</a>):
<ul>
<li><code>en_US.po</code> → <code>en.po</code></li>
<li><code>de_DE.po</code> → <code>de.po</code></li>
<li>…</li>
</ul>
</li>
</ul>
<h3 id="reports">Reports</h3>
<ul>
<li>Reports use English as fallback language in case no other language is
defined. Due to the translation updates one needs to use <code>en</code> as
fallback instead of <code>en_US</code> (See:
<a href="https://bugs.tryton.org/issue5443">issue5443</a>).</li>
</ul>
<h3 id="module-party">Module: Party</h3>
<ul>
<li>
<p>The field <code>vat_code</code> was renamed to <code>tax_identifier</code>. Adapt to this
change in case you are using this value in a report.</p>
</li>
<li>
<p>The full address of a party now includes the party name. In our case
the party name is duplicated on reports and we decided to go with this
minimal patch for the party module to restore the old behaviour.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#d33682">--- a/address.py  2016-12-13 10:16:26.720162514 +0100
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span><span style="color:#d33682">+++ b/address.py  2016-12-13 10:16:34.872140410 +0100
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span><span style="color:#d33682">@@ -143,8 +143,6 @@
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span>             }
</span></span><span style="display:flex;"><span>         if context.get(&#39;address_from_country&#39;) == self.country:
</span></span><span style="display:flex;"><span>             substitutions[&#39;country&#39;] = &#39;&#39;
</span></span><span style="display:flex;"><span><span style="color:#d33682">-        if context.get(&#39;address_with_party&#39;, False):
</span></span></span><span style="display:flex;"><span><span style="color:#d33682">-            substitutions[&#39;party_name&#39;] = self.party.full_name
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span>         for key, value in substitutions.items():
</span></span><span style="display:flex;"><span>             substitutions[key.upper()] = value.upper()
</span></span><span style="display:flex;"><span>         return substitutions
</span></span></code></pre></div></li>
<li>
<p>The report generation is broken for parties when certain countries are
used in the address record. The following patch fixes the issue for
Tryton 4.2 (See: <a href="https://bugs.tryton.org/issue6111">issue6111</a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#d33682">diff -r 706751992f88 address.py
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span><span style="color:#d33682">--- a/address.py   Mon Nov 28 16:19:18 2016 +0100
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span><span style="color:#d33682">+++ b/address.py   Thu Dec 15 13:08:35 2016 +0100
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span><span style="color:#d33682">@@ -138,6 +138,11 @@
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span>             &#39;country&#39;: self.country.name if self.country else &#39;&#39;,
</span></span><span style="display:flex;"><span>             &#39;country_code&#39;: self.country.code if self.country else &#39;&#39;,
</span></span><span style="display:flex;"><span>             }
</span></span><span style="display:flex;"><span><span style="color:#d33682">+
</span></span></span><span style="display:flex;"><span><span style="color:#d33682">+        # Map invalid substitutions district* to subdivision* on 4.2.
</span></span></span><span style="display:flex;"><span><span style="color:#d33682">+        substitutions[&#39;district&#39;] = substitutions[&#39;subdivision&#39;]
</span></span></span><span style="display:flex;"><span><span style="color:#d33682">+        substitutions[&#39;district_code&#39;] = substitutions[&#39;subdivision_code&#39;]
</span></span></span><span style="display:flex;"><span><span style="color:#d33682">+
</span></span></span><span style="display:flex;"><span><span style="color:#d33682"></span>         if context.get(&#39;address_from_country&#39;) == self.country:
</span></span><span style="display:flex;"><span>             substitutions[&#39;country&#39;] = &#39;&#39;
</span></span><span style="display:flex;"><span>         if context.get(&#39;address_with_party&#39;, False):
</span></span></code></pre></div></li>
</ul>
<h3 id="module-stock">Module: Stock</h3>
<ul>
<li>Previous versions used
<code>&lt;product_name(move.product.id, shipment.delivery_address.party.lang and shipment.delivery_address.party.lang.code or 'en_US')&gt;</code>
in the report. This was changed to <code>move.product.rec_name</code> in the
upstream delivery note report. Adapt accordingly in case you are using
a custom delivery note report.</li>
</ul>
<h2 id="the-upgrade">The upgrade</h2>
<p>To get started, clone the running instance and test the upgrade with the
clone. Our Tryton instance is installed in a <a href="http://docs.python-guide.org/en/latest/dev/virtualenvs/">virtual
environment</a>,
so I started out by updating the version information in our Ansible role
from 4.0 to 4.2. After that, Ansible can create the new virtual
environment. Beware to upgrade all custom modules <strong>before</strong> this step
as they need to depend on the new Tryton version.</p>
<p>Run <code>pip freeze | grep &quot;4.0&quot;</code> in the virtual environment to make sure no
modules from 4.0 are lingering around.</p>
<h3 id="fix-translations">Fix translations</h3>
<p>In order to keep custom translations, one needs to convert them <em>before</em>
performing the database upgrade. Connect to the database and update all
translations:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#859900">UPDATE</span> <span style="color:#268bd2">ir_translation</span> <span style="color:#859900">SET</span> <span style="color:#268bd2">lang</span> = <span style="color:#2aa198">&#39;en&#39;</span> <span style="color:#859900">WHERE</span> <span style="color:#268bd2">lang</span> = <span style="color:#2aa198">&#39;en_US&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#859900">UPDATE</span> <span style="color:#268bd2">ir_translation</span> <span style="color:#859900">SET</span> <span style="color:#268bd2">lang</span> = <span style="color:#2aa198">&#39;de&#39;</span> <span style="color:#859900">WHERE</span> <span style="color:#268bd2">lang</span> = <span style="color:#2aa198">&#39;de_DE&#39;</span>;
</span></span></code></pre></div><h3 id="upgrading-the-database">Upgrading the database</h3>
<p>After the translation updates we are ready to upgrade the database:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ trytond-admin --verbose --config trytond.conf --database &lt;dbname&gt; --all
</span></span></code></pre></div><p>The database upgrade completed successfully.</p>
<h2 id="the-aftermath">The aftermath</h2>
<p>A few problems popped up after the upgrade:</p>
<ul>
<li>Duplicating a product requires &ldquo;administration&rdquo; permission (See:
<a href="https://bugs.tryton.org/issue6115">issue6115</a>)</li>
<li>Menus are always displayed in English in the Windows client (See:
<a href="https://bugs.tryton.org/issue6116">issue6116</a>)</li>
<li>The URL parameter does not open the referenced record (See:
<a href="https://bugs.tryton.org/issue6119">issue6119</a>)</li>
</ul>
<h2 id="additional-information">Additional information</h2>
<ul>
<li><a href="https://discuss.tryton.org/t/migration-from-4-0-to-4-2/161">https://discuss.tryton.org/t/migration-from-4-0-to-4-2/161</a></li>
</ul>
<p>Until next time.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[Notes on upgrading from Tryton 3.8 to Tryton 4.0]]></title>
    <link href="https://nblock.org/2016/08/19/notes-on-upgrading-from-tryton-3.8-to-tryton-4.0/"/>
    <id>https://nblock.org/2016/08/19/notes-on-upgrading-from-tryton-3.8-to-tryton-4.0/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2016-08-19T00:00:00+00:00</published>
    <updated>2016-08-19T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>At work, we wanted to upgrade the Tryton installation from version 3.8
to <a href="http://www.tryton.org/posts/new-tryton-release-40.html">version
4.0</a>. There is
very little information available on how to conduct such upgrades and
how to deal with the various errors that pop up during the process. The
post starts with notes on some of the changes followed by a description
of problems as they emerged during the upgrade and concludes with some
required changes after the upgrade.</p>
<h2 id="before-the-upgrade">Before the upgrade</h2>
<h3 id="split-of-the-trytond-binary">Split of the trytond binary</h3>
<p>The <code>trytond</code> executable has been split into multiple distinct
executables: <code>trytond-admin</code>, <code>trytond-cron</code>. There is no backwards
compatibility layer in the new <code>trytond</code>, so you will quickly notice
which scripts need to change as some parameters are no longer accepted
by <code>trytond</code>:</p>
<ul>
<li>All administrative tasks such as database upgrades are now handled by
<code>trytond-admin</code>.</li>
<li>All background tasks are now handled by <code>trytond-cron</code> and the new
<code>trytond</code> no longer accepts the <code>--cron</code> parameter. As such, a new
service file is needed to handle background tasks.</li>
</ul>
<h3 id="configuration-changes">Configuration changes</h3>
<p>The configuration for <code>trytond</code> needs to be adjusted:</p>
<ul>
<li>The <code>[jsonrpc]</code> and <code>[xmlrpc]</code> sections have been merged into
<code>[web]</code>.</li>
<li>The path to the Tryton web interface (SAO) now uses the configuration
key <code>root</code> instead of <code>data</code>. The configuration key is part of the
<code>[web]</code> section.</li>
<li>All connections are now handled by a single port. The separation
between JSON-RPC and XML-RPC is gone. This obviously triggers some
changes in the nginx configuration as well.</li>
</ul>
<h3 id="custom-modules">Custom modules</h3>
<p>We use several modules that provide custom reports which require some
updates:</p>
<ul>
<li>Update dependency to Tryton 4.0.</li>
<li>Rebuild translations as the translation format changed. I actually
deleted the translations of the modules exported them again from the
test instance.</li>
</ul>
<h3 id="tools-and-scripts">Tools and scripts</h3>
<p>The tools that leverage Proteus to communicate with Tryton require some
changes as well:</p>
<ul>
<li>Update Proteus to match Tryton 4.0.</li>
<li>The XML-RPC connection string must end with a <strong>/</strong>, e.g.
<code>https://user:pass@hostname:port/database/</code></li>
</ul>
<h2 id="the-upgrade">The upgrade</h2>
<h3 id="update-trytond-and-its-modules">Update Trytond and its modules</h3>
<p>To get started, clone the running instance and test the upgrade with the
clone. Our Tryton instance is installed in a <a href="http://docs.python-guide.org/en/latest/dev/virtualenvs/">virtual
environment</a>,
so I started out by updating the version information in our Ansible role
from 3.8 to 4.0 and completed the list of required modules from the
<a href="http://doc.tryton.org/4.0/trytond/doc/topics/install.html">official
documentation</a>.
After that Ansible can create the new virtual environment. Beware to
upgrade all custom modules <strong>before</strong> this step as they need to depend
on the new Tryton version.</p>
<p>Run <code>pip freeze | grep &quot;3.8&quot;</code> in the virtual environment to make sure no
modules from 3.8 are lingering around.</p>
<h3 id="upgrading-the-database">Upgrading the database</h3>
<p>OK, the virtual environment is ready. Let&rsquo;s upgrade the database:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ trytond-admin --verbose --config trytond.conf --database &lt;dbname&gt; --all
</span></span></code></pre></div><p>Running the above commands yields:</p>
<pre tabindex="0"><code class="language-noe" data-lang="noe">Traceback (most recent call last):
  File &#34;/path/to/tryton/venv/bin/trytond-admin&#34;, line 21, in &lt;module&gt;
    admin.run(options)
  File &#34;/path/to/tryton/venv/local/lib/python2.7/site-packages/trytond/admin.py&#34;, line 48, in run
    Pool(db_name).init(update=options.update, lang=lang)
  File &#34;/path/to/tryton/venv/local/lib/python2.7/site-packages/trytond/pool.py&#34;, line 155, in init
    lang=lang)
  File &#34;/path/to/tryton/venv/local/lib/python2.7/site-packages/trytond/modules/__init__.py&#34;, \
      line 429, in load_modules
    _load_modules()
  File &#34;/path/to/tryton/venv/local/lib/python2.7/site-packages/trytond/modules/__init__.py&#34;, \
      line 396, in _load_modules
    graph = create_graph(module_list)[0]
  File &#34;/path/to/tryton/venv/local/lib/python2.7/site-packages/trytond/modules/__init__.py&#34;,
       line 191, in create_graph
    - set((p[0] for p in packages))))
Exception: Missing dependencies: [u&#39;purchase_request&#39;]
</code></pre><p>This error occurs even if the module <code>trytond-purchase-request</code> is
installed. The
<a href="https://groups.google.com/forum/#!msg/tryton/TLHQtdSEw1I/MolB7TFfAgAJ">solution</a>
is to run the following command instead (with this exact argument
order):</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ trytond-admin --verbose --config trytond.conf --database &lt;dbname&gt; -u purchase_request --all
</span></span></code></pre></div><p>Subsequent invocations do not require the <code>-u purchase_request</code>
parameter.</p>
<p>Now, the upgrade completes but spits out the following warning several
times:</p>
<pre tabindex="0"><code class="language-none" data-lang="none">WARNING trytond.modules.product.product The column &#34;category&#34; on table \
  &#34;product_template&#34; must be dropped manually
</code></pre><p>The <a href="#product-categories">product categories</a> got overhauled with Tryton
4.0. Tryton already migrated the respective data in the database and now
warns us that we should better get rid of the old category column. This
warning may be fixed by connecting to the database and do what Tryton
suggests:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#859900">ALTER</span> <span style="color:#859900">TABLE</span> <span style="color:#268bd2">product_template</span> <span style="color:#859900">DROP</span> <span style="color:#859900">COLUMN</span> <span style="color:#268bd2">category</span>;
</span></span></code></pre></div><p>The database upgrade is now complete. Subsequent invocations do not
yield any errors or warnings.</p>
<h3 id="remove-the-webdav-module">Remove the webdav module</h3>
<p>The webdav module is no longer bundled as core module for Tryton 4.0. It
is now available as separate module. We never used it, so it is best to
remove it altogether. Connect to the database and remove it from the
<code>ir_module</code> table (<a href="https://www.tryton.org/~irclog/2016-08-18.log.html#t2016-08-18%2009:00">thx @cedk for the
hint</a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#859900">DELETE</span> <span style="color:#859900">FROM</span> <span style="color:#268bd2">ir_module</span> <span style="color:#859900">WHERE</span> <span style="color:#268bd2">name</span>=<span style="color:#2aa198">&#39;webdav&#39;</span>;
</span></span></code></pre></div><h2 id="after-the-upgrade">After the upgrade</h2>
<h3 id="usability-improvements">Usability improvements</h3>
<p>Tryton 4.0 allows to set default values for several fields. This is
really useful as it saves the user a few clicks for each created product
and improves data consistency.</p>
<h3 id="product-categories">Product categories</h3>
<p>The product category was replaced by a categories field. This allows
multiple categories per product. A single category is used for
accounting.</p>
<p>For our use case we had to update the product categories and the
accounting category for all products. I wrote a small migration script
that leverages Proteus to update all products
(<a href="migrate-categories-for-tryton-4.0.py">download</a>).</p>
<h2 id="additional-information">Additional information</h2>
<ul>
<li><a href="https://discuss.tryton.org/t/migration-from-3-8-to-4-0/96">https://discuss.tryton.org/t/migration-from-3-8-to-4-0/96</a></li>
</ul>
<p>Until next time.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[Access multiple translations of a field in Tryton]]></title>
    <link href="https://nblock.org/2016/06/03/access-multiple-translations-in-tryton/"/>
    <id>https://nblock.org/2016/06/03/access-multiple-translations-in-tryton/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2016-06-03T00:00:00+00:00</published>
    <updated>2016-06-03T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>At work, we are currently migrating away from a proprietary ERP software
package to <a href="https://tryton.org">Tryton</a>. There are several reasons why
we decided to switch and one them is that Tryton provides an API and
sane data access. Being a free software project, there are libraries and
tools available to get access to the data. This is a good foundation to
build your own tools on top.</p>
<p>Tryton provides a client library called
<a href="https://pypi.python.org/pypi/proteus">Proteus</a> for programmatic data
access. The first tool that I built on top of
<a href="https://pypi.python.org/pypi/proteus">Proteus</a> is called <strong>pedantic
bot</strong>. It nags about inconsistencies and errors in the database. For
example, the <strong>pedantic bot</strong> checks for the following issues:</p>
<ul>
<li>Leading/trailing whitespace in fields</li>
<li>Control characters in fields</li>
<li>Inconsistent formatting of fields (phone numbers, …)</li>
<li>Incomplete data records (e.g. contact information is missing)</li>
<li>Missing descriptions</li>
<li>…</li>
</ul>
<p>Tryton allows to translate some of the fields where it makes sense to
have them available in multiple languages (product name, a description,
payment terms, …). The <strong>pedantic bot</strong> should check all the
translations alike. It took me a while to figure out how to get access
to a field in a particular translation, so here is a short demo on how
to accomplish it in Tryton 3.8. It should work on other versions too:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic">#!/usr/bin/python2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">proteus</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">config</span>
</span></span><span style="display:flex;"><span><span style="color:#dc322f;font-weight:bold">from</span> <span style="color:#268bd2">proteus</span> <span style="color:#dc322f;font-weight:bold">import</span> <span style="color:#268bd2">Model</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">def</span> <span style="color:#268bd2">main</span>(<span style="color:#268bd2">username</span>, <span style="color:#268bd2">password</span>, <span style="color:#268bd2">host</span>, <span style="color:#268bd2">port</span>, <span style="color:#268bd2">db</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Connect to Tryton.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">current_config</span> = <span style="color:#268bd2">config</span>.<span style="color:#268bd2">set_xmlrpc</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#2aa198">&#39;https://</span><span style="color:#2aa198">{}</span><span style="color:#2aa198">:</span><span style="color:#2aa198">{}</span><span style="color:#2aa198">@</span><span style="color:#2aa198">{}</span><span style="color:#2aa198">:</span><span style="color:#2aa198">{}</span><span style="color:#2aa198">/</span><span style="color:#2aa198">{}</span><span style="color:#2aa198">&#39;</span>.<span style="color:#268bd2">format</span>(<span style="color:#268bd2">username</span>, <span style="color:#268bd2">password</span>, <span style="color:#268bd2">host</span>, <span style="color:#268bd2">port</span>, <span style="color:#268bd2">db</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Get the product model.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">Product</span> = <span style="color:#268bd2">Model</span>.<span style="color:#268bd2">get</span>(<span style="color:#2aa198">&#39;product.product&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Print the product information in de_DE.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">with</span> <span style="color:#268bd2">current_config</span>.<span style="color:#268bd2">set_context</span>({<span style="color:#2aa198">&#39;language&#39;</span>: <span style="color:#2aa198">&#39;de_DE&#39;</span>}):
</span></span><span style="display:flex;"><span>        <span style="color:#859900">for</span> <span style="color:#268bd2">record</span> <span style="color:#859900">in</span> <span style="color:#268bd2">Product</span>.<span style="color:#268bd2">find</span>():
</span></span><span style="display:flex;"><span>            <span style="color:#cb4b16">print</span>(<span style="color:#2aa198">u</span><span style="color:#2aa198">&#39;Name: </span><span style="color:#2aa198">{}</span><span style="color:#2aa198">&#39;</span>.<span style="color:#268bd2">format</span>(<span style="color:#268bd2">record</span>.<span style="color:#268bd2">rec_name</span>))
</span></span><span style="display:flex;"><span>            <span style="color:#cb4b16">print</span>(<span style="color:#2aa198">u</span><span style="color:#2aa198">&#39;Description: </span><span style="color:#2aa198">{}</span><span style="color:#2aa198">&#39;</span>.<span style="color:#268bd2">format</span>(<span style="color:#268bd2">record</span>.<span style="color:#268bd2">description</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Print the product information in en_US.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">with</span> <span style="color:#268bd2">current_config</span>.<span style="color:#268bd2">set_context</span>({<span style="color:#2aa198">&#39;language&#39;</span>: <span style="color:#2aa198">&#39;en_US&#39;</span>}):
</span></span><span style="display:flex;"><span>        <span style="color:#859900">for</span> <span style="color:#268bd2">record</span> <span style="color:#859900">in</span> <span style="color:#268bd2">Product</span>.<span style="color:#268bd2">find</span>():
</span></span><span style="display:flex;"><span>            <span style="color:#cb4b16">print</span>(<span style="color:#2aa198">u</span><span style="color:#2aa198">&#39;Name: </span><span style="color:#2aa198">{}</span><span style="color:#2aa198">&#39;</span>.<span style="color:#268bd2">format</span>(<span style="color:#268bd2">record</span>.<span style="color:#268bd2">rec_name</span>))
</span></span><span style="display:flex;"><span>            <span style="color:#cb4b16">print</span>(<span style="color:#2aa198">u</span><span style="color:#2aa198">&#39;Description: </span><span style="color:#2aa198">{}</span><span style="color:#2aa198">&#39;</span>.<span style="color:#268bd2">format</span>(<span style="color:#268bd2">record</span>.<span style="color:#268bd2">description</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># Print the product information in the language that is configured for the</span>
</span></span><span style="display:flex;"><span>    <span style="color:#93a1a1;font-style:italic"># connected user.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#859900">for</span> <span style="color:#268bd2">record</span> <span style="color:#859900">in</span> <span style="color:#268bd2">Product</span>.<span style="color:#268bd2">find</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#cb4b16">print</span>(<span style="color:#2aa198">u</span><span style="color:#2aa198">&#39;Name: </span><span style="color:#2aa198">{}</span><span style="color:#2aa198">&#39;</span>.<span style="color:#268bd2">format</span>(<span style="color:#268bd2">record</span>.<span style="color:#268bd2">rec_name</span>))
</span></span><span style="display:flex;"><span>        <span style="color:#cb4b16">print</span>(<span style="color:#2aa198">u</span><span style="color:#2aa198">&#39;Description: </span><span style="color:#2aa198">{}</span><span style="color:#2aa198">&#39;</span>.<span style="color:#268bd2">format</span>(<span style="color:#268bd2">record</span>.<span style="color:#268bd2">description</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859900">if</span> <span style="color:#268bd2">__name__</span> == <span style="color:#2aa198">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2">main</span>(<span style="color:#2aa198">&#39;username&#39;</span>, <span style="color:#2aa198">&#39;password&#39;</span>, <span style="color:#2aa198">&#39;hostname&#39;</span>, <span style="color:#2aa198">&#39;9000&#39;</span>, <span style="color:#2aa198">&#39;db&#39;</span>)
</span></span></code></pre></div><p>Save this script as <code>example.py</code>, install
<a href="https://pypi.python.org/pypi/proteus">Proteus</a> and run:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ python example.py
</span></span></code></pre></div><p>The solution is to set the desired language in the context:
<code>with current_config.set_context({'language': 'en_US'})</code>. Have fun.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[Migrate from Perforce to Git]]></title>
    <link href="https://nblock.org/2015/12/12/migrate-from-perforce-to-git/"/>
    <id>https://nblock.org/2015/12/12/migrate-from-perforce-to-git/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2015-12-12T00:00:00+00:00</published>
    <updated>2015-12-12T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>One of our customers is currently using Perforce as their version
control system. Some parts of their software is already in Git, but two
huge repositories are still maintained in Perforce. Years ago, they
decided that they want to move to Git and weed out Perforce altogether.
Since then their Perforce server is basically unmaintained and the
maintenance contract with Perforce has expired years ago. It&rsquo;s in very
bad shape and the limitations of Perforce are daunting.</p>
<p>Now, after several years of basically leaving everything as is, the
customer decided to tackle this issue again and finally get rid of
Perforce. A simplified version of the transition plan looks as follows:</p>
<ul>
<li>Bring the internal git server (<a href="https://about.gitlab.com">Gitlab</a>)
up-to-date.</li>
<li>Create a one-way bridge between Perforce and Git to periodically sync
new changes from Perforce to Git.</li>
<li>Move the build system and infrastructure to Git.</li>
<li>Educate developers to use Git (for those that don&rsquo;t know it).</li>
<li>Shutdown the Perforce server permanently.</li>
</ul>
<p>This blog post is about the second step, the one-way bridge between
Perforce and Git.</p>
<h2 id="requirements">Requirements</h2>
<p>Here is an informal list of requirements:</p>
<ul>
<li>Keep the entire history of interesting branches. These include: the
current master branch, a few long running development branches and
many release branches.</li>
<li>Preserve branch points, i.e. the point in time where two branches
diverged.</li>
<li>During the transition period, all new changes committed in Perforce
should be periodically synced to Git (incremental updates).</li>
<li>Make incremental updates fast.</li>
</ul>
<h2 id="the-one-way-bridge">The one-way bridge</h2>
<p>Here is how one can migrate a repository from Perforce to Git with
support for multiple branches, an unimpaired history and support for
incremental updates.</p>
<h3 id="create-the-git-repository">Create the Git repository</h3>
<p>Create a new local Git repository and setup a Git remote. For the
initial import, the remote is empty and no data can be fetched from it.
On subsequent runs, most of the data is already in Git and fetching the
data directly from Git is way faster than extracting the commits from
Perforce.</p>
<h3 id="sync-perforce-branches-into-git">Sync Perforce branches into Git</h3>
<p>Each Perforce branch of interest needs to be checked for updates.
<a href="https://git-scm.com/docs/git-p4">Git-p4</a> provides the sync subcommand
which may be used for this purpose. If a copy of the Perforce branch is
not yet in Git, import the entire history of it into a dedicated Git
branch. If it is already in Git, just update the local Git branch and
import all new changes since the last run. I used the prefix <code>p4/</code> for
Git branches that track a Perforce branch, e.g. <code>p4/main</code> tracks the
Perforce main branch.</p>
<h3 id="find-all-branches-with-updates">Find all branches with updates</h3>
<p>Find all Git branches that were updated since they need further
processing. On the first run, all branches need to be updated. On all
subsequent runs, the number of branches to update should be fairly
small, if any.</p>
<h3 id="rewrite-history-to-restore-branch-points">Rewrite history to restore branch points</h3>
<p>After the import, the branches have no relationship with each other and
the repository might look as follows:</p>
<pre tabindex="0"><code>J---K---L---M  p4/dev

G---H---I  p4/release

A---B---C---D---E---F  p4/main
</code></pre><p>In reality, the branches do relate to each other and the repository
should look like this:</p>
<pre tabindex="0"><code>G---H---I  p4/release
/
A---B---C---D---E---F  p4/main
          \
           J---K---L---M  p4/dev
</code></pre><p>Unfortunately, the commit-parent relationship got lost during the
import. One can use
<a href="https://git.wiki.kernel.org/index.php/GraftPoint">grafts</a> to restore
the relationship between commits:</p>
<blockquote>
<p>Graft points or grafts enable two otherwise different lines of development to
be joined together. It works by letting users record fake ancestry information
for commits. This way you can make git pretend the set of parents a commit has
is different from what was recorded when the commit was created.</p></blockquote>
<p>Since the commit volume on the Perforce repository is relatively low,
obtaining the graft points is straight forward in my case:</p>
<ul>
<li>Extract the current graft points from the Git repository.</li>
<li>Find the SHA1 of the first commit on each branch and add the SHA1 of
the commit that happened right before as a second parent. If no commit
is available, do nothing.</li>
<li>Store the modified grafts file under <code>.git/info/grafts</code>.</li>
<li>Rewrite history using <code>git filter-branch</code> to permanently apply the
grafts.</li>
<li>Delete the grafts file.</li>
</ul>
<h3 id="from-the-perforce-branch-to-the-final-git-branch">From the Perforce branch to the final Git branch</h3>
<p>Since rewriting history permanently alters the repository, it is a very
bad idea to do it on public branches. Furthermore,
<a href="https://git-scm.com/docs/git-p4">Git-p4</a> is not amused if one messes
with the history of the <code>p4/</code> branches. After some experiments, I have
decided for the following workflow to migrate a Perforce branch to a
final Git branch:</p>
<pre tabindex="0"><code>Perforce depot        main

                        +
                        |
                        |
                        | clone
----------------        | sync                +-----+
                        |                     |     | rewrite
                        |                     |     | history
                        v                     v     +
Git repository
(local)               p4/main  +-----------&gt;  tmp/main  +-----------&gt;  main
                                  branch                   branch
                        ^                                  merge         ^
                        |                                                |
                        |                                                |
                        | push                                           | push
----------------        | pull                                           | pull
                        |                                                |
                        |                                                |
                        v                                                v
Git repository
(remote)              p4/main                                           main
</code></pre><p>The above diagram illustrates the workflow for a single branch, <code>main</code>:</p>
<ul>
<li>Clone or sync the Perforce branch into the local Git branch.</li>
<li>Create a temporary branch for each branch that got updated. Those
branches have the prefix <code>tmp/</code>, e.g. <code>tmp/main</code> is the temporary
branch for <code>p4/main</code>.</li>
<li>Create the grafts file and rewrite history for all temporary branches.</li>
<li>Create the final branch from the temporary branch, e.g <code>main</code> is the
final branch for the Perforce <code>main</code> branch and is branched from
<code>tmp/main</code>. On the first run, the final branch does not exist, so
simply create it from the temporary branch. If it does exist, perform
a <strong>fast-forward only</strong> merge to get new commits from the temporary
branch into the final branch.</li>
</ul>
<h3 id="cleanup-and-publish">Cleanup and publish</h3>
<p>Cleanup the local Git repository and remove all temporary branches since
they should not be pushed to the remote. After cleanup, publish all
branches under <code>p4/</code> and all final branches to the remote.</p>
<h3 id="implementation">Implementation</h3>
<p>I wrote a small one-way bridge in Python that implements the above
steps. Unfortunately, it is written in Python 2 due to a fairly ancient
server infrastructure where Python 3 is not available. You can download
the script and a sample configuration from <a href="p4-to-git.tar.gz">here</a>.</p>
<h3 id="credits">Credits</h3>
<ul>
<li><a href="https://www.notinventedhere.org">Lukas</a> for reviewing this blog post
and his valuable input on this topic.</li>
</ul>
<p>Until next time.</p>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[Python Debugger Cheatsheet]]></title>
    <link href="https://nblock.org/2011/11/15/pdb-cheatsheet/"/>
    <id>https://nblock.org/2011/11/15/pdb-cheatsheet/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2011-11-15T00:00:00+00:00</published>
    <updated>2011-11-15T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>I&rsquo;ve been using the Python Debugger a lot lately and decided to write a
cheatsheet that covers most of the basic stuff. The cheatsheet is
suitable for pdb and ipbd (just replace <code>pdb</code> with <code>ipbd</code>).</p>
<p>The cheatsheet is available as PNG and PDF file on the <a href="https://github.com/nblock/pdb-cheatsheet/releases">GitHub
release</a> page.</p>
<p><img loading="lazy" src="/2011/11/15/pdb-cheatsheet/pdb-cheatsheet.png" type="" alt="PDB Cheatsheet"  title="PDB Cheatsheet"  /></p>
<h2 id="credits-and-references">Credits and References</h2>
<ul>
<li><a href="https://anilattech.wordpress.com/2011/06/29/python-debugger-pdb-cheatsheet">https://anilattech.wordpress.com/2011/06/29/python-debugger-pdb-cheatsheet</a></li>
<li><a href="https://pythonconquerstheuniverse.wordpress.com/2009/09/10/debugging-in-python">https://pythonconquerstheuniverse.wordpress.com/2009/09/10/debugging-in-python</a></li>
<li><a href="http://docs.python.org/library/pdb.html">http://docs.python.org/library/pdb.html</a></li>
</ul>
]]></content>
    
  </entry>
  <entry>
    <title type="html"><![CDATA[About using the static blogging engine rstblog]]></title>
    <link href="https://nblock.org/2011/08/31/1st-blogpost/"/>
    <id>https://nblock.org/2011/08/31/1st-blogpost/</id>
    <author>
      <name>Florian Preinstorfer</name>
    </author>
    <published>2011-08-31T00:00:00+00:00</published>
    <updated>2011-08-31T00:00:00+00:00</updated>
    
    <content type="html"><![CDATA[<p>Hi, and welcome on my site. First off, I want to tell you something
about the static blogging engine I use. It&rsquo;s called
<a href="https://github.com/mitsuhiko/rstblog">rstblog</a> and it has been written
by <a href="http://lucumr.pocoo.org/about/">Armin Ronacher</a>. I discovered this
fine piece of software when I stumbled over Armins&rsquo;s blog entry <a href="http://lucumr.pocoo.org/2011/7/9/python-and-pola">Python
and the Principle of Least
Astonishment</a>.</p>
<p>These are my key points for choosing
<a href="https://github.com/mitsuhiko/rstblog">rstblog</a>:</p>
<ul>
<li>Free software (BSD)</li>
<li>Simple</li>
<li>Produces static output</li>
<li>Written in <a href="http://python.org">Python</a></li>
<li>Write blog entries with <a href="http://www.vim.org">vim</a></li>
<li>Manage blog entries with <a href="http://git-scm.com">git</a></li>
<li>Does the trick for me</li>
</ul>
<h2 id="installation">Installation</h2>
<p>You can either checkout and build
<a href="https://github.com/mitsuhiko/rstblog">rstblog</a> from source or use <a href="https://aur.archlinux.org/packages.php?ID=51195">my
PKGBUILD file</a> in case
you are an <a href="https://archlinux.org">Arch Linux</a> user.</p>
<h2 id="getting-started">Getting started</h2>
<p><a href="https://github.com/mitsuhiko/rstblog">rstblog</a> requires a certain
directory structure to produce your blog. Here is the layout of
<em>example.org</em>, a simple blog:</p>
<pre tabindex="0"><code>example.org
├── 2011
│   ├── 08
│   │   └── 31
│   │       └── 1st-blogpost.rst
│   └── 09
│       └── 15
│           ├── nice.rst
│           └── story.rst
├── about.rst
├── config.yml
└── _templates
    └── layout.html
</code></pre><p>To get started, just <a href="example.org.tar.gz">download</a> the example above.</p>
<p>First off, adjust the config file (<em>config.yml</em>) to suit your needs.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#268bd2">---</span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2;font-weight:bold">active_modules</span>: [<span style="color:#2aa198">pygments, tags, blog, latex]</span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2;font-weight:bold">author</span>: <span style="color:#2aa198">your name</span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2;font-weight:bold">canonical_url</span>: <span style="color:#2aa198">http://example.org</span>
</span></span><span style="display:flex;"><span><span style="color:#268bd2;font-weight:bold">modules</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#268bd2;font-weight:bold">pygments</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#268bd2;font-weight:bold">style</span>: <span style="color:#2aa198">friendly</span>
</span></span></code></pre></div><p>The special folder <em>_templates</em> contains the
<a href="http://jinja.pocoo.org">Jinja2</a> template(s) that
<a href="https://github.com/mitsuhiko/rstblog">rstblog</a> uses to create your
blog. Just create the file <em>layout.html</em>, which is the most important
building block for producing your blog.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#93a1a1;font-style:italic">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#268bd2;font-weight:bold">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#268bd2;font-weight:bold">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#268bd2;font-weight:bold">meta</span> <span style="color:#268bd2">charset</span>=<span style="color:#2aa198">utf-8</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#268bd2;font-weight:bold">title</span>&gt;{% block title %}{% endblock %}&lt;/<span style="color:#268bd2;font-weight:bold">title</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#268bd2;font-weight:bold">link</span> <span style="color:#268bd2">href</span>=<span style="color:#2aa198">&#34;/feed.atom&#34;</span> <span style="color:#268bd2">rel</span>=<span style="color:#2aa198">&#34;alternate&#34;</span> <span style="color:#268bd2">title</span>=<span style="color:#2aa198">&#34;Feed&#34;</span> <span style="color:#268bd2">type</span>=<span style="color:#2aa198">&#34;application/atom+xml&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#268bd2;font-weight:bold">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#268bd2;font-weight:bold">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#268bd2;font-weight:bold">ul</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#268bd2;font-weight:bold">li</span>&gt;&lt;<span style="color:#268bd2;font-weight:bold">a</span> <span style="color:#268bd2">href</span>=<span style="color:#2aa198">&#34;/&#34;</span>&gt;home&lt;/<span style="color:#268bd2;font-weight:bold">a</span>&gt;&lt;/<span style="color:#268bd2;font-weight:bold">li</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#268bd2;font-weight:bold">li</span>&gt;&lt;<span style="color:#268bd2;font-weight:bold">a</span> <span style="color:#268bd2">href</span>=<span style="color:#2aa198">&#34;/archive/&#34;</span>&gt;archive&lt;/<span style="color:#268bd2;font-weight:bold">a</span>&gt;&lt;/<span style="color:#268bd2;font-weight:bold">li</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#268bd2;font-weight:bold">li</span>&gt;&lt;<span style="color:#268bd2;font-weight:bold">a</span> <span style="color:#268bd2">href</span>=<span style="color:#2aa198">&#34;/tags/&#34;</span>&gt;tags&lt;/<span style="color:#268bd2;font-weight:bold">a</span>&gt;&lt;/<span style="color:#268bd2;font-weight:bold">li</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#268bd2;font-weight:bold">ul</span>&gt;
</span></span><span style="display:flex;"><span>    {% block body %}{% endblock %}
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#268bd2;font-weight:bold">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#268bd2;font-weight:bold">html</span>&gt;
</span></span></code></pre></div><h2 id="blog-entries">Blog entries</h2>
<p><a href="https://github.com/mitsuhiko/rstblog">rstblog</a> distinguishes (sort of)
between two different blog entries:</p>
<ul>
<li>Regular blog entries</li>
<li>Static blog entries</li>
</ul>
<h3 id="regular-blog-entries">Regular blog entries</h3>
<p>Regular blog entries should be put in a directory structure that looks
like this: <em>year/month/day</em>. Create such a structure and put your first
blog entry (<code>my-first-blog-entry.rst</code>) in there.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rst" data-lang="rst"><span style="display:flex;"><span>public: yes
</span></span><span style="display:flex;"><span>tags: [rstblog, firsttry]
</span></span><span style="display:flex;"><span>summary: This is my first blog entry
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#d33682">My first blog entry</span>
</span></span><span style="display:flex;"><span><span style="color:#d33682">===================</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Hello World!
</span></span></code></pre></div><h3 id="static-blog-entries">Static blog entries</h3>
<p><a href="https://github.com/mitsuhiko/rstblog">rstblog</a> also allows you to
create <em>static</em> blog entries such as an about page. Simply put a rst
formatted file in the root directory of your blog.</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rst" data-lang="rst"><span style="display:flex;"><span>public: yes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#d33682">About me</span>
</span></span><span style="display:flex;"><span><span style="color:#d33682">========</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>This is me.
</span></span></code></pre></div><h2 id="building-the-blog">Building the blog</h2>
<p>Finally, build and view the result:</p>
<div class="highlight"><pre tabindex="0" style="color:#586e75;background-color:#eee8d5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ run-rstblog build
</span></span><span style="display:flex;"><span>$ run-rstblog serve
</span></span><span style="display:flex;"><span>Serving on http://127.0.0.1:5000/
</span></span></code></pre></div><p>Now point your browser to <code>http://127.0.0.1:5000</code> to view the results of
your work. The about entry should work as well:
<code>http://127.0.0.1:5000/about</code>.</p>
<h2 id="additional-stuff">Additional stuff</h2>
<h3 id="static-content">Static content</h3>
<p>Static content (css files, js files, …) should be placed in the
directory <em>static</em>. During the build process the content of this folder
will be copied to the directory <em>_build/static</em>. You can easily link to
them from your blog entries using <code>`target &lt;/static/target&gt;`_</code>.</p>
<h3 id="design">Design</h3>
<p>Your <em>layout.html</em> file contains <a href="http://jinja.pocoo.org">Jinja2</a>
templating code. What&rsquo;s missing is a css file that nicely formats your
content. Just create one and put it into the directory <em>static</em>. Don&rsquo;t
forget to link to it in your <em>layout.html</em>!</p>
<h3 id="tagging">Tagging</h3>
<p>Tagging is a nice feature that helps classifying blog entries. The first
blog entry (<a href="#regular-blog-entries">Regular blog entries</a>) already uses
two tags, <code>rstblog</code> and <code>firsttry</code>. These tags will be used to create a
tag overview page, viewable at <code>http://127.0.0.1:5000/tags</code>. Use as many
tags as you like to classify your content.</p>
<h3 id="customize-blog-generation">Customize blog generation</h3>
<p>In the default setting <a href="https://github.com/mitsuhiko/rstblog">rstblog</a>
will build archive and tag pages for you. This is of course also
template based. If you wish to modify some of the templates, copy the
affected files from <em>rstblog/rstblog/templates</em> to your <em>_templates</em>
folder and adjust them accordingly.</p>
<h2 id="publish-your-blog">Publish your blog</h2>
<p>This is really simple. Just copy your <em>_build</em> folder to your public
html folder and you are done.</p>
<h2 id="further-information">Further information</h2>
<p><a href="http://github.com/ralsina/rst-cheatsheet/raw/master/rst-cheatsheet.pdf">This</a>
cheat sheet might be interesting if you need some hints for writing
content in reStructuredText.</p>
<p><a href="http://sbhr.dk/2010/11/30/using_rstblog">This</a> might also help you to
setup your own <a href="https://github.com/mitsuhiko/rstblog">rstblog</a> site.</p>
]]></content>
    
  </entry>
</feed>
