<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alexander A. E.</title><link href="https://alexanderae.com/" rel="alternate"/><link href="https://alexanderae.com/feeds/all.atom.xml" rel="self"/><id>https://alexanderae.com/</id><updated>2024-06-22T00:00:00-05:00</updated><entry><title>Script en flask para registrar cada request (para pruebas)</title><link href="https://alexanderae.com/flask-script-to-log-every-request.html" rel="alternate"/><published>2024-06-22T00:00:00-05:00</published><updated>2024-06-22T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2024-06-22:/flask-script-to-log-every-request.html</id><summary type="html">&lt;p&gt;Hoy me aventuré por un camino inusual. Mi día comenzó con el objetivo de guardar mis tweets marcados y mis likes en una base de datos. Para lograrlo, necesitaba acceder al API de Twitter usando tweepy. Sin embargo, me encontré con un obstáculo persistente: Twitter requiere una URL de autorización para probar su API, lo cual complicaba mis intentos de configurar ngrok con un pequeño servidor Python debido a problemas de CSRF.&lt;/p&gt;
&lt;p&gt;Después de un tiempo de frustración, tuve una …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hoy me aventuré por un camino inusual. Mi día comenzó con el objetivo de guardar mis tweets marcados y mis likes en una base de datos. Para lograrlo, necesitaba acceder al API de Twitter usando tweepy. Sin embargo, me encontré con un obstáculo persistente: Twitter requiere una URL de autorización para probar su API, lo cual complicaba mis intentos de configurar ngrok con un pequeño servidor Python debido a problemas de CSRF.&lt;/p&gt;
&lt;p&gt;Después de un tiempo de frustración, tuve una idea: "Tengo mi propio dominio, ¿por qué no añadir un subdominio y utilizarlo como URL de autorización?"&lt;/p&gt;
&lt;h2&gt;Requerimientos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Disponer de un dominio que pueda recibir peticiones&lt;/li&gt;
&lt;li&gt;Mostrar todos los requests y sus parámetros en una URL específica (/dashboard)&lt;/li&gt;
&lt;li&gt;Mantener la información persistente utilizando SQLite&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Script&lt;/h2&gt;
&lt;p&gt;Con la ayuda de Skynet, obtuve esto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template_string&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DATABASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;requests_log.db&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;MAX_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="n"&gt;DELETE_BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;  &lt;span class="c1"&gt;# Number of records to delete when exceeding MAX_REQUESTS&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init_db&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Initialize the SQLite database with requests table.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;CREATE TABLE IF NOT EXISTS requests (&lt;/span&gt;
&lt;span class="s1"&gt;                            id INTEGER PRIMARY KEY AUTOINCREMENT,&lt;/span&gt;
&lt;span class="s1"&gt;                            method TEXT,&lt;/span&gt;
&lt;span class="s1"&gt;                            path TEXT,&lt;/span&gt;
&lt;span class="s1"&gt;                            params TEXT,&lt;/span&gt;
&lt;span class="s1"&gt;                            headers TEXT,&lt;/span&gt;
&lt;span class="s1"&gt;                            body TEXT&lt;/span&gt;
&lt;span class="s1"&gt;                        )&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;log_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Log request details into SQLite database, excluding &amp;#39;/dashboard&amp;#39;.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dashboard&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Exclude logging for &amp;#39;/dashboard&amp;#39; endpoint&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;INSERT INTO requests (method, path, params, headers, body) VALUES (?, ?, ?, ?, ?)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;# Check the number of requests and delete oldest if exceeds MAX_REQUESTS&lt;/span&gt;
            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SELECT COUNT(*) FROM requests&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_REQUESTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DELETE FROM requests WHERE id IN (SELECT id FROM requests ORDER BY id ASC LIMIT ?)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DELETE_BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
                &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="c1"&gt;# Initialize the database when the app starts&lt;/span&gt;
&lt;span class="n"&gt;init_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;PUT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DELETE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;lt;path:path&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;PUT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DELETE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;catch_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Route to catch all requests and log them.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;log_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Request logged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dashboard&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Route to display request logs in a dashboard.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SELECT * FROM requests&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;requests_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;method&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;params&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;headers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;body&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;
            &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# HTML template to render the dashboard view&lt;/span&gt;
    &lt;span class="n"&gt;table_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1, shrink-to-fit=no&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;title&amp;gt;Request Log Dashboard&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            table {&lt;/span&gt;
&lt;span class="s2"&gt;                width: 100%;&lt;/span&gt;
&lt;span class="s2"&gt;                border-collapse: collapse;&lt;/span&gt;
&lt;span class="s2"&gt;            }&lt;/span&gt;
&lt;span class="s2"&gt;            table, th, td {&lt;/span&gt;
&lt;span class="s2"&gt;                border: 1px solid black;&lt;/span&gt;
&lt;span class="s2"&gt;            }&lt;/span&gt;
&lt;span class="s2"&gt;            th, td {&lt;/span&gt;
&lt;span class="s2"&gt;                padding: 8px;&lt;/span&gt;
&lt;span class="s2"&gt;                text-align: left;&lt;/span&gt;
&lt;span class="s2"&gt;            }&lt;/span&gt;
&lt;span class="s2"&gt;            th {&lt;/span&gt;
&lt;span class="s2"&gt;                background-color: #f2f2f2;&lt;/span&gt;
&lt;span class="s2"&gt;            }&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;h1&amp;gt;Request Log Dashboard&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;table&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;          &amp;lt;tr&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;th&amp;gt;Method&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;th&amp;gt;Path&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;th&amp;gt;Params&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;th&amp;gt;Headers&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;th&amp;gt;Body&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;          &amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;          {&lt;/span&gt;&lt;span class="si"&gt;% f&lt;/span&gt;&lt;span class="s2"&gt;or request in requests %}&lt;/span&gt;
&lt;span class="s2"&gt;          &amp;lt;tr&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;td&amp;gt;{{ request.id }}&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;td&amp;gt;{{ request.method }}&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;td&amp;gt;{{ request.path }}&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;td&amp;gt;{{ request.params }}&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;td&amp;gt;{{ request.headers }}&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;td&amp;gt;{{ request.body }}&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;          &amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;          {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndfor %}&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_template_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;requests_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En principio la lógica es muy simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Todo request (que nos sea el /dashboard) lo registra en una base de datos sqlite. Teniendo en cuenta que si se 
   excede cierta cantidad de 1000 requests estoy eliminando los 500 más antiguos.&lt;/li&gt;
&lt;li&gt;Al consultar el dashboard, me lista los requests con un formato html mínimamente cómodo.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Claro, para correrlo en un servidor tuve que utilizar supervisord, uwsgi y nginx, aunque quizá se pudo realizar con 
menos.&lt;/p&gt;
&lt;p&gt;Mi archivo uwsgi.ini terminó así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[uwsgi]&lt;/span&gt;
&lt;span class="na"&gt;webapps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/home/alexander/webapps&lt;/span&gt;
&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;%(webapps)/params.alexanderae.com&lt;/span&gt;
&lt;span class="na"&gt;venv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/home/alexander/.pyenv/versions/flask&lt;/span&gt;

&lt;span class="na"&gt;chdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;%(app)&lt;/span&gt;
&lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;server:app&lt;/span&gt;
&lt;span class="na"&gt;virtualenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;%(venv)&lt;/span&gt;

&lt;span class="na"&gt;uwsgi-socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;%(app)/uwsgi.sock&lt;/span&gt;
&lt;span class="na"&gt;master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;processes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;threads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;thread-stacksize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;128&lt;/span&gt;
&lt;span class="na"&gt;reload-on-rss&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;
&lt;span class="na"&gt;harakiri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;
&lt;span class="na"&gt;vacuum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;logto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/tmp/uwsgi_params.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y mi script de supervisor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[program:server_app]&lt;/span&gt;
&lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/alexander/.pyenv/versions/flask/bin/uwsgi /home/alexander/webapps/params.alexanderae.com/uwsgi.ini&lt;/span&gt;
&lt;span class="na"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;stderr_logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/flask_app.err.log&lt;/span&gt;
&lt;span class="na"&gt;stdout_logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/var/log/flask_app.out.log&lt;/span&gt;
&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;alexander&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y con ello parece que pasaré al paso 2, el cual es intentar conectarme a twitter, esperemos y funcione, ya lo iré 
contando (o tal vez no).&lt;/p&gt;</content><category term="blog"/><category term="flask"/></entry><entry><title>Crear sitemaps de más de 50000 urls en Django</title><link href="https://alexanderae.com/sitemaps-mas-de-50000-urls-en-django.html" rel="alternate"/><published>2023-04-21T00:00:00-05:00</published><updated>2023-04-21T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2023-04-21:/sitemaps-mas-de-50000-urls-en-django.html</id><summary type="html">&lt;p&gt;Descubre cómo crear sitemaps para sitios web en Django con gran cantidad de urls. Mejora la navegación y relevancia de tu sitio para SEO. Ejemplo de código incluido.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Un sitemap es un archivo XML que contiene una lista de URLs de un sitio web. Este archivo ayuda a los motores de
búsqueda a indexar y rastrear todas las páginas de un sitio web. Pero si tu sitio web tiene más de 50,000 URLs, es posible
que necesites crear múltiples sitemaps para ayudar a los motores de búsqueda a rastrear e indexar todas tus páginas.&lt;/p&gt;
&lt;p&gt;Aquí te mostramos cómo puedes organizar tus sitemaps de más de 50,000 URLs para tu sitio web en Django.&lt;/p&gt;
&lt;p&gt;Algunos pasos a seguir antes de paginar el sitemap, que nos pueden facilitar el trabajo son:&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;1. Divide tu sitio web en secciones:&lt;/h2&gt;
&lt;p&gt;Organiza tu sitio web en secciones lógicas. Por ejemplo, si fuera un website de noticias, podríamos tener:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;noticias de última hora&lt;/li&gt;
&lt;li&gt;noticias locales&lt;/li&gt;
&lt;li&gt;noticias deportivas&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esto te ayudará a crear sitemaps secundarios más pequeños.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;2. Crea un archivo sitemap.xml principal que agrupe a estas secciones ya definidas:&lt;/h2&gt;
&lt;p&gt;Agrega las URLs de los sitemaps secundarios que vas a crear. Este archivo será el punto de entrada para los motores de
búsqueda.&lt;/p&gt;
&lt;p&gt;Antes hemos mostrado ejemplos de cómo realizar
esto: &lt;a href="https://alexanderae.com/django-implementar-sitemap.html"&gt;Cómo implementar sitemaps en Django&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sitemapindex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.sitemaps.org/schemas/sitemap/0.9&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;sitemap&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;http://www.ejemplo.com/noticias_locales.xml.gz&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/sitemap&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;sitemap&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;http://www.ejemplo.com/noticias_deportivas.xml.gz&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/sitemap&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;sitemap&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;http://www.ejemplo.com/noticias_de_ultima_hora.xml.gz&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/sitemap&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sitemapindex&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;3. Crea sitemaps secundarios:&lt;/h2&gt;
&lt;p&gt;Crea los sitemaps secundarios para cada sección de tu sitio web. Cada sitemap secundario debe contener hasta 50,000
URLs.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;4. Comprime los sitemaps secundarios (recomendado, opcional):&lt;/h2&gt;
&lt;p&gt;Comprime los sitemaps secundarios utilizando &lt;em&gt;gzip&lt;/em&gt; para reducir su tamaño y aumentar la velocidad de descarga.
Esto lo podemos realizar con la configuración del servidor web (Apache o Nginx).&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;5. Agrega una caché a los sitemaps (recomendado, opcional)&lt;/h2&gt;
&lt;p&gt;Esto para no realizar el cálculo después de cada visita, ya que no es tan frecuente que se agreguen nuevas páginas
   a la web (evaluable según cada caso). &lt;/p&gt;
&lt;p&gt;Este punto también lo pueden realizar desde &lt;em&gt;apache&lt;/em&gt; / &lt;em&gt;nginx&lt;/em&gt; para mayor eficiencia.
Noten que &lt;em&gt;Django&lt;/em&gt; también es capaz de realizar esta tarea.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;¿Qué sucede si uno de mis sitemaps, aún así excede las 50000 urls?&lt;/h2&gt;
&lt;p&gt;Django cuenta con paginación automática para estos casos.
Las url's generadas serían del tipo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http://127.0.0.1:8001/books/sitemap.xml&lt;/li&gt;
&lt;li&gt;http://127.0.0.1:8001/books/sitemap.xml?p=2&lt;/li&gt;
&lt;li&gt;http://127.0.0.1:8001/books/sitemap.xml?p=3&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hay que notar que django utiliza el parámetro GET "p" para identificar a cada página y que no crea un índice que
las agrupe, por lo que esta tarea quedaría para nosotros.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Ejemplo de código&lt;/h2&gt;
&lt;p&gt;Aquí adjunto un proyecto en el que se puede revisar el comportamiento de un website con más de un millón de libros en su
sitemap: &lt;a href="https://gitlab.com/__alexander__/django-books-demo" target="_blank"&gt;Django books demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/dev/ref/contrib/sitemaps/"&gt;Sitemaps en Django (documentación oficial)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="django"/><category term="seo"/></entry><entry><title>Mejora el rendimiento de tu sitio web con Prefetch, Preconnect, Async y Defer</title><link href="https://alexanderae.com/mejora-rendimiento-prefetch-preconnect-async-defer.html" rel="alternate"/><published>2023-04-11T00:00:00-05:00</published><updated>2023-04-11T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2023-04-11:/mejora-rendimiento-prefetch-preconnect-async-defer.html</id><summary type="html">&lt;p&gt;Cuando se trata de optimizar el rendimiento de tu sitio web, hay varias técnicas que puedes utilizar para reducir los tiempos de carga y mejorar la experiencia del usuario.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cuando se trata de optimizar el rendimiento de tu sitio web, hay varias técnicas que puedes utilizar para reducir los
tiempos de carga y mejorar la experiencia del usuario. Entre estas técnicas se encuentran el uso de prefetch,
preconnect, async y defer.&lt;/p&gt;
&lt;h2&gt;Prefetch&lt;/h2&gt;
&lt;p&gt;La técnica de &lt;code&gt;prefetch&lt;/code&gt; se utiliza para cargar recursos que se necesitarán en el futuro, antes de que el usuario los solicite. Por ejemplo, si tienes una página que muestra una imagen grande después de hacer clic en un botón, puedes utilizar &lt;code&gt;prefetch&lt;/code&gt; para cargar la imagen antes de que el usuario haga clic en el botón. De esta manera, cuando el usuario haga clic en el botón, la imagen ya estará cargada y se mostrará inmediatamente.&lt;/p&gt;
&lt;p&gt;Para utilizar &lt;code&gt;prefetch&lt;/code&gt;, simplemente agrega la siguiente etiqueta &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; a la sección &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; de tu página:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &amp;lt;link rel=&amp;quot;prefetch&amp;quot; href=&amp;quot;ruta-del-recurso-a-cargar&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Preconnect&lt;/h2&gt;
&lt;p&gt;La técnica de preconnect se utiliza para establecer conexiones de red tempranas con recursos externos, como servidores de terceros o CDN, antes de que el navegador los necesite. Esto puede ayudar a reducir los tiempos de respuesta del servidor y mejorar el rendimiento general de la página.&lt;/p&gt;
&lt;p&gt;Para utilizar preconnect, simplemente agrega la siguiente etiqueta  &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; a la sección &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; de tu página:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &amp;lt;link rel=&amp;quot;preconnect&amp;quot; href=&amp;quot;dominio-del-recurso-a-conectar&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Async&lt;/h2&gt;
&lt;p&gt;La técnica de async se utiliza para cargar y ejecutar scripts de manera asíncrona, lo que significa que el script se cargará en segundo plano mientras la página se sigue cargando y no bloqueará la carga de la página. Esto puede ayudar a mejorar el tiempo de carga de la página.&lt;/p&gt;
&lt;p&gt;Para utilizar async, simplemente agrega la siguiente etiqueta &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; a la sección &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; o &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; de tu página:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ruta-del-script-a-cargar&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Defer&lt;/h2&gt;
&lt;p&gt;La técnica de defer se utiliza para cargar y ejecutar scripts después de que la página haya terminado de cargarse. Esto puede ayudar a mejorar el tiempo de carga de la página, ya que los scripts no bloquearán la carga de la página.&lt;/p&gt;
&lt;p&gt;Para utilizar defer, simplemente agrega la siguiente etiqueta &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; a la sección &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; o &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; de tu página:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ruta-del-script-a-cargar&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Conclusión&lt;/h2&gt;
&lt;p&gt;El uso de prefetch, preconnect, async y defer son técnicas útiles que pueden ayudarte a mejorar el rendimiento de tu sitio web. Al utilizar estas técnicas, puedes reducir los tiempos de carga, mejorar la experiencia del usuario y mejorar la eficiencia general de tu sitio web.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.dev/i18n/es/efficiently-load-third-party-javascript/" target="_blank"&gt;Carga el JavaScript de terceros de manera eficiente&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.keycdn.com/blog/resource-hints" target="_blank"&gt;Resource Hints - What is Preload, Prefetch, and Preconnect?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="html"/><category term="performance"/></entry><entry><title>Cómo realizar debug en CSS con una línea de JS</title><link href="https://alexanderae.com/debug-en-css-con-una-linea-de-js.html" rel="alternate"/><published>2023-03-09T00:00:00-05:00</published><updated>2023-03-09T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2023-03-09:/debug-en-css-con-una-linea-de-js.html</id><summary type="html">&lt;p&gt;Para aquellos que realizan frontend y se preguntan por qué un elemento no se muestra o por qué se descuadra, tal vez este tip les ayude mucho al momento de encontrar el problema. Con una línea de javascript pueden pintar los bordes de cada elemento del DOM.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Siempre que me toca escribir algo de CSS, tengo a la mano un fragmento de código en JS que me ayuda a encontrar problemas:&lt;/p&gt;
&lt;p&gt;Noten que es independiente de cualquier librería JS, es decir, es Vanilla JS. Y su ingreso es mediante la consola de javascript:&lt;/p&gt;
&lt;p&gt;&lt;img alt="consola-js" src="/pictures/console-js.png" title="Ingreso del script en la consola JS de Google Chrome"&gt;&lt;/p&gt;
&lt;h2&gt;Opción 1&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::javascript
[].forEach.call($$(&amp;quot;*&amp;quot;),function(a){
  a.style.outline=&amp;quot;1px solid #&amp;quot;+(~~(Math.random()*(1&amp;lt;&amp;lt;24))).toString(16)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Opción 2&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::javascript
[].forEach.call(document.querySelectorAll(&amp;quot;*&amp;quot;),function(a){a.style.outline=&amp;quot;1px solid #&amp;quot;+(~~(Math.random()*(1&amp;lt;&amp;lt;24))).toString(16)})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Los resultados en ambos casos son similares a:&lt;/p&gt;
&lt;p&gt;&lt;img alt="debug-css" src="/pictures/one-line-debug-css.png" title="Una línea de JS para hacer debug a JS"&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/addyosmani/fd3999ea7fce242756b1"&gt;Addy Osmani 108 byte CSS Layout Debugger&lt;/a&gt;
&lt;br&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="css"/><category term="js"/></entry><entry><title>Actualizar postgresql sin perder información</title><link href="https://alexanderae.com/actualizar-postgresql.html" rel="alternate"/><published>2022-05-18T00:00:00-05:00</published><updated>2022-05-18T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2022-05-18:/actualizar-postgresql.html</id><summary type="html">&lt;p&gt;Después de actualizar postgresql a una versión superior, el servidor no pudo iniciar debido a la incompatibilidad con la versión anterior. Una opción para corregir el inicio de postgresql era eliminar el directorio de datos de postgres y reiniciarlo, pero con ello se perdería toda la información anterior. Con el método que se describe a continuación, es posible mantener los datos.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Utilizo  &lt;a href="https://endeavouros.com/" target="_blank"&gt;EndeavorOS&lt;/a&gt; como sistema operativo, y al ser del tipo &lt;em&gt;rolling rellease&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, tengo la costumbre 
de realizar actualizaciones frecuentes.&lt;/p&gt;
&lt;p&gt;En la última actualización, &lt;strong&gt;postgresql&lt;/strong&gt; dió el salto de la versión 13 a la versión 14, y con ello el motor de base de datos dejó de iniciar y mostraba el error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;An old version of the database format was found.
See https://wiki.archlinux.org/index.php/PostgreSQL#Upgrading_PostgreSQL
Failed to start PostgreSQL database server.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La causa del problema fue que existían bases de datos con la versión 13 que requerían migrarse a la versión 14.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Paso Previo&lt;/h2&gt;
&lt;p&gt;Para realizar la migración, es necesario instalar un paquete que contiene la versión anterior de postgres. En el caso de EndeavorOS, lo incluye en el paquete: &lt;strong&gt;"postgresql-old-upgrade"&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Durante el proceso que describiré tuve otro problema, me apareció el mensaje:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;El clúster de origen no fue apagado limpiamente.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;el cual tiene su equivalente en inglés:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;The source cluster was not shut down cleanly. Failure, exiting&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y la solución fue iniciar el motor de base de datos antiguo (los binarios de postgresql-old-upgrade).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PGDATA&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgsql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez iniciado, esperamos a que los procesos pendientes acaben y lo detenemos.
Con ello, podemos realizar la migración propiamente.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Solución&lt;/h2&gt;
&lt;p&gt;Movemos la base de datos a un nuevo directorio e inicializamos la base de datos con la nueva versión:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;olddata&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Migramos la información de la anterior versión de postgresql a la nueva versión mediante la herramienta &lt;strong&gt;"pg_upgrade"&lt;/strong&gt;. Nótese que se debe reemplazar la variable &lt;em&gt;PG_VERSION&lt;/em&gt; con la versión anterior:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgsql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PG_VERSION&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;olddata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;por ejemplo, en mi caso el comando fue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pgsql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;olddata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;con ello ya podemos iniciar la nueva base de datos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo systemctl start postgresql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href='https://suay.site/?p=1130' target='_blank'&gt;How to Upgrade PostgreSQL Databases Preserving Information&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Se refiere al concepto de entregar actualizaciones frecuentes en las aplicaciones. Por ejemplo &lt;em&gt;Arch Linux&lt;/em&gt; es un sistema operativo del tipo &lt;em&gt;rolling release&lt;/em&gt;, mientras &lt;em&gt;Ubuntu&lt;/em&gt; con su sistema de actualización cada 6 meses, no lo sería.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="postgresql"/><category term="linux"/></entry><entry><title>JSON en PostgreSQL</title><link href="https://alexanderae.com/json-postgresql.html" rel="alternate"/><published>2021-03-15T00:00:00-05:00</published><updated>2021-03-15T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2021-03-15:/json-postgresql.html</id><summary type="html">&lt;p&gt;PostgreSQL incluye soporte para el tipo de dato JSON desde la versión "9.2". En este tutorial se verán ejemplos de cómo trabajar con inserciones y consultas de campos JSON.&lt;/p&gt;</summary><content type="html">&lt;p&gt;PostgreSQL incluye soporte para el tipo de dato JSON desde la versión "9.2". En este tutorial se verán ejemplos de cómo trabajar con inserciones y consultas de campos JSON.&lt;/p&gt;
&lt;h2&gt;¿Qué es JSON?&lt;/h2&gt;
&lt;p&gt;JSON, es el acrónimo de "JavaScript Object Notation" o "notación de objeto de JavaScript". Es un formato de texto sencillo utilizado para el intercambio de datos.&lt;/p&gt;
&lt;p&gt;Su estructura es similar a la del siguiente ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::javascript
{
  &amp;quot;autor&amp;quot;: &amp;quot;Isaac Asimov&amp;quot;,
  &amp;quot;pais&amp;quot;: &amp;quot;Rusia&amp;quot;,   
  &amp;quot;libros&amp;quot;:[
    {
      &amp;quot;titulo&amp;quot;: &amp;quot;Yo robot&amp;quot;,
      &amp;quot;isbn&amp;quot;: &amp;quot;9780739312698&amp;quot;
    },{
      &amp;quot;nombre&amp;quot;: &amp;quot;El hombre bicentenario&amp;quot;,
      &amp;quot;isbn&amp;quot;: &amp;quot;9788440677778&amp;quot;
    } 
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Creación de una tabla que utiliza un campo de tipo JSON&lt;/h2&gt;
&lt;p&gt;La declaración de una columna JSON solo requiere el nombre del campo + el tipo de dato "json", por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::sql
CREATE TABLE book (
   id serial,
   info json
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En la tabla anterior almacenaremos información sobre grupos de libros por autores.&lt;/p&gt;
&lt;h2&gt;Inserción de registros&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::sql
INSERT INTO book (info)
    VALUES (&amp;#39;{&amp;quot;autor&amp;quot;: &amp;quot;Isaac Asimov&amp;quot;,&amp;quot;pais&amp;quot;: &amp;quot;Rusia&amp;quot;,&amp;quot;libros&amp;quot;:[{&amp;quot;titulo&amp;quot;: &amp;quot;Yo robot&amp;quot;,&amp;quot;isbn&amp;quot;: &amp;quot;9780739312698&amp;quot;}]}&amp;#39;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;o para insertar varios registros al mismo tiempo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::sql
INSERT INTO book (info)
    VALUES (&amp;#39;{&amp;quot;autor&amp;quot;: &amp;quot;Patrick Rothfuss&amp;quot;,&amp;quot;pais&amp;quot;: &amp;quot;EEUU&amp;quot;,&amp;quot;libros&amp;quot;:[{&amp;quot;titulo&amp;quot;: &amp;quot;El nombre del viento&amp;quot;,&amp;quot;isbn&amp;quot;: &amp;quot;9788499082479&amp;quot;}]}&amp;#39;),
    (&amp;#39;{&amp;quot;autor&amp;quot;: &amp;quot;Arthur C. Clarke&amp;quot;,&amp;quot;pais&amp;quot;: &amp;quot;Reino Unido&amp;quot;,&amp;quot;libros&amp;quot;:[{&amp;quot;titulo&amp;quot;: &amp;quot;Cánticos de la lejana Tierra&amp;quot;,&amp;quot;isbn&amp;quot;: &amp;quot;9788401322013&amp;quot;}]}&amp;#39;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Consultas&lt;/h2&gt;
&lt;p&gt;Para ver la información que ya tenemos almacenada podemos usar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::sql
SELECT id, info FROM book;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y para mostrar un atributo del json, podemos utilizar el operador "-&amp;gt;":&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kr"&gt;sql&lt;/span&gt;
&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;autor&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;autor (json)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Isaac Asimov"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Patrick Rothfuss"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Arthur C. Clarke"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Noten que el operador nos retorna un tipo "json", pero si utilizamos la versión "-&amp;gt;&amp;gt;" se transforma en un tipo "text":&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kr"&gt;sql&lt;/span&gt;
&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;autor&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;pais&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pais&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;autor (text)&lt;/th&gt;
&lt;th&gt;pais (json)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Isaac Asimov&lt;/td&gt;
&lt;td&gt;"Rusia"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patrick Rothfuss&lt;/td&gt;
&lt;td&gt;"EEUU"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arthur C. Clarke&lt;/td&gt;
&lt;td&gt;"Reino Unido"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Consultas con condicionales (WHERE)&lt;/h2&gt;
&lt;p&gt;Por ejemplo, si deseamos obtener los registros cuyos autores pertenezcan a un país, podemos realizar una consulta similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kr"&gt;sql&lt;/span&gt;
&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;pais&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Rusia&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nótese que para comparar un valor del json con un "text" debemos utilizar el operador: "-&amp;gt;&amp;gt;".&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Se pueden realizar consultas y operaciones más complejas, pero como al menos como introducción, espero que este post les haya sido de ayuda.&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/12/datatype-json.html" target="_blank"&gt;JSON Datatype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/12/functions-json.html" target="_blank"&gt;JSON Functions and Operators&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="postgresql"/></entry><entry><title>Vue + Summernote Editor</title><link href="https://alexanderae.com/vue-summernote-editor.html" rel="alternate"/><published>2020-07-27T00:00:00-05:00</published><updated>2020-07-27T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-07-27:/vue-summernote-editor.html</id><summary type="html">&lt;p&gt;Ejemplo mínimo que muestra cómo integrar vue.js y summernote editor.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Parte de un desarrollo que estoy realizando requiere integrar &lt;a href="https://vuejs.org/" target="_blank"&gt;Vue.js&lt;/a&gt; y &lt;a href="https://summernote.org/" target="_blank"&gt;Summernote&lt;/a&gt;. Lamentablemente no existe una integración oficial por lo que anotaré los pasos que seguí por si le son de ayuda a alguien.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Observemos que yo ya tengo un proyecto creado con &lt;strong&gt;vue-cli&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Instalo las dependencias necesarias&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;yarn add jquery&lt;/li&gt;
&lt;li&gt;yarn add summernote&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creo un componente al que llamaré &lt;em&gt;Summernote.vue&lt;/em&gt;, con un contenido similar a:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;element&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jquery&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;summernote/dist/summernote-lite.min.css&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;summernote/dist/summernote-lite.min&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Summernote&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;summernote_&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;nextTick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;defaultConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;es-ES&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;placeholder...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;810&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;onInit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ready&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;change&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;onEnter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;enter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;onFocus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;focus&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;onBlur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blur&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;onKeyup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keyup&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;onKeydown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keydown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;onPaste&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paste&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
                &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="n"&gt;defaultConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summernote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;summernote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summernote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summernote&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="n"&gt;scoped&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summernote&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Noten que estoy usando la versión lite de &lt;strong&gt;summernote&lt;/strong&gt;, aquella que no requiere bootstrap.&lt;/p&gt;
&lt;p&gt;Ahora, con lo anterior, puedo utilizar mi componente recién creado, por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;summernote&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;summernoteConfig&amp;quot;&lt;/span&gt; &lt;span class="nd"&gt;@ready&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;readySummernote&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;summernote&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;preview&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;contents&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Summernote&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;../Summernote&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;AnotherComponent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Summernote&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;summernoteConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;tabsize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;toolbar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;font&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bold&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;underline&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;clear&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;font&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;superscript&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;subscript&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;para&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ul&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ol&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;paragraph&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;table&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;table&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;insert&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;picture&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;view&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;codeview&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;readySummernote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summernote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;change&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo que hicimos fue:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Importar el componente que creamos para "Summernote"&lt;/li&gt;
&lt;li&gt;Crear una variable para la configuración del editor&lt;/li&gt;
&lt;li&gt;Creamos un método que reciba los cambios del contenido y los asigne a una variable que le indicamos.&lt;/li&gt;
&lt;li&gt;En el html también hemos incluido una vista previa de lo que genera el editor.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Debido a que mi conocimiento del javascript (vue) no es el suficiente, hay algunos otros puntos que intenté realizar y no pude, como por ejemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;integrar katex (un plugin de summernote para utilizar latex)&lt;/li&gt;
&lt;li&gt;cambiar el idioma de summernote (tuve conflictos con jquery)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pero espero que al menos como base lo anterior le sea de ayuda a alguien.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pasBone/vue-summernote-lite" target="_blank"&gt;vue summernote lite&lt;/a&gt;: un repositorio del que me guié y a quien pertenece la mayoría de la lógica utilizada aquí&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/summernote/summernote/issues/2597" target="_blank"&gt;How do I use Summernote with the Vue.js Framework?
&lt;/a&gt;: un issue en el repositorio de summernote el cual da algunas otras ideas del cómo enfocar el problema&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si les fue de ayuda, compartan la publicación o dejen un comentario :)
&lt;br&gt;&lt;/p&gt;</content><category term="blog"/><category term="vuejs"/><category term="javascript"/></entry><entry><title>Búsqueda avanzada con Postgresql y Django - I</title><link href="https://alexanderae.com/django-postgres-avanzado-1.html" rel="alternate"/><published>2020-07-20T00:00:00-05:00</published><updated>2020-07-20T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-07-20:/django-postgres-avanzado-1.html</id><summary type="html">&lt;p&gt;Postgresql es un motor opensource de base de datos, con el cual podemos lograr implementar sistemas de búsqueda complejos sin necesidad de recurrir a motores especializados como elasticsearch. En esta publicación se mostrarán algunas de las características de postgres y el cómo se pueden utilizar mediante Django.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Si tuviéramos que implementar un formulario de búsqueda en django, usualmente recurriríamos al método "filter" de los querysets y a algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="na"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nombre__icontains&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="no"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;pero existen situaciones en las que requerimos técnicas más complejas. Y si bien, una opción podría ser utilizar un motor de búsqueda avanzado como &lt;em&gt;ElasticSearch&lt;/em&gt;, también podríamos usar las características de &lt;strong&gt;postgresql&lt;/strong&gt; como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;trigrams&lt;/li&gt;
&lt;li&gt;unnacent&lt;/li&gt;
&lt;li&gt;full text search (fts)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;las cuales iremos revisando a detalle según avanzamos.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Entorno de prueba&lt;/h2&gt;
&lt;p&gt;Para este ejemplo utilizaremos una base de datos que contiene una tabla llamada &lt;strong&gt;Book&lt;/strong&gt; (libro).&lt;/p&gt;
&lt;p&gt;La tabla "Book" tiene los siguientes campos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;isbn (identificador único de un libro)&lt;/li&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;authors (separados por comas)&lt;/li&gt;
&lt;li&gt;description (reseña)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="estructura de la tabla Book" src="/pictures/table-books.png"&gt;&lt;/p&gt;
&lt;p&gt;Para facilitar lo anterior, he preparado un pequeño proyecto en django que se puede descargar de:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target='_blank' href='https://gitlab.com/__alexander__/django-fts-demo'&gt;django-fts-demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y un dataset que contiene la lista de 10'000 libros el cual se puede descargar de:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target='_blank' href='https://gitlab.com/librera/librera-books-10k'&gt;librera-books-10k&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Los pasos a seguir en caso quieran probar con la misma base de datos que utilizo serían:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clonar el proyecto de django&lt;/li&gt;
&lt;li&gt;Crear una nueva base de datos para el ejemplo y configurarla en el settings de django&lt;/li&gt;
&lt;li&gt;Ejecutar el siguiente script en el shell de django para importar el CSV a la tabla "Book"&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Nota:&lt;/em&gt; deben remover la primera línea del archivo CSV para que no se importe como un libro (corresponde a la cabecera de la tabla)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;books.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;authors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(Opcional) Crear un superusuario de django para poder ingresar al admin.&lt;/p&gt;
&lt;p&gt;Con lo anterior podremos observar que ya tenemos los 10k libros:&lt;/p&gt;
&lt;p&gt;&lt;img alt="10000 libros en el admin" src="/pictures/django-postgres/admin-10k-books.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Trigrams&lt;/h2&gt;
&lt;p&gt;Un &lt;strong&gt;trigram&lt;/strong&gt; es un grupo de 3 caracteres tomados de un string.&lt;/p&gt;
&lt;h3&gt;Instalando la extensión trigrams en postgresql&lt;/h3&gt;
&lt;p&gt;Siguiendo la documentación oficial de django:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity" target="_blank"&gt;trigram similarity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lo que necesitamos en principio es activar la extensión &lt;strong&gt;trigram&lt;/strong&gt; de postgres.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Método 1:&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;La podemos instalar desde el panel de postgres con la sentencia SQL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::sql
CREATE EXTENSION pg_trgm;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notando que necesitamos ejecutarla con un usuario que tenga permisos para instalar extensiones.&lt;/p&gt;
&lt;p&gt;Para lo anterior es posible que se nos requiera instalar el paquete &lt;em&gt;postgresql-conntrib&lt;/em&gt; respectivo de nuestro sistema operativo.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Método 2:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Una alternativa sería crear una migración manual en django similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.contrib.postgres.operations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TrigramExtension&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;books&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0001_initial&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;TrigramExtension&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y aplicarla:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./manage.py migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Ejemplo de un trigram&lt;/h3&gt;
&lt;p&gt;Por ejemplo, para el texto &lt;em&gt;"Harry Potter"&lt;/em&gt;, postgresql genera los siguientes trigrams:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{&amp;quot;  h&amp;quot;,&amp;quot;  p&amp;quot;,&amp;quot; ha&amp;quot;,&amp;quot; po&amp;quot;,arr,&amp;quot;er &amp;quot;,har,ott,pot,rry,&amp;quot;ry &amp;quot;,ter,tte}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;lo podemos verificar mediante la consola de postgres con la sentencia:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::sql
SELECT show_trgm(&amp;#39;Harry Potter&amp;#39;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Trigrams en Django - 1er intento&lt;/h3&gt;
&lt;p&gt;Primero veamos qué sucede si realizamos la búsqueda del texto "Harry Potter" en nuestro formulario:&lt;/p&gt;
&lt;p&gt;&lt;img alt="resultados de búsqueda iniciales" src="/pictures/django-postgres/django-filter.png"&gt;&lt;/p&gt;
&lt;p&gt;Obtenemos 9 resultados con la sentencia:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
Book.objects.filter(title__icontains=q)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora, siguiente la documentación, utilicemos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
books = Book.objects.filter(title__trigram_similar=q)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Observamos lo siguiente:&lt;/p&gt;
&lt;p&gt;&lt;img alt="resultados de búsqueda con trigrams" src="/pictures/django-postgres/trigram-01.png"&gt;&lt;/p&gt;
&lt;p&gt;Tenemos un problema, desaparecieron 2 resultados.&lt;/p&gt;
&lt;p&gt;Actualicemos la sentencia para ver qué pasó con los 2 resultados que no fueron contados:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
books = Book.objects.annotate(
            similarity=TrigramSimilarity(&amp;#39;title&amp;#39;, q),
        ).filter(title__icontains=q).order_by(&amp;#39;-similarity&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y también actualicemos el html para mostrar el campo "similarity" el cual representa qué tan similar es un resultado comparado a nuestra búsqueda.&lt;/p&gt;
&lt;p&gt;Tendremos lo siguiente:&lt;/p&gt;
&lt;p&gt;&lt;img alt="resultados de búsqueda con trigrams 02" src="/pictures/django-postgres/trigram-02.png"&gt;&lt;/p&gt;
&lt;p&gt;Según la documentación de django, solo son tomados los resultados con un valor de "similaridad mayor a 0.3", y según el anterior resultado, dos de nuestros resultados son excluidos.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Trigrams en Django - 2do intento&lt;/h3&gt;
&lt;p&gt;Entonces tal vez nos convenga ajustar este límite a un valor de 0.2 para incluir más resultados:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
books = Book.objects.annotate(
            similarity=TrigramSimilarity(&amp;#39;title&amp;#39;, q),
        ).filter(similarity__gt=0.2).order_by(&amp;#39;-similarity&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Al realizar nuevamente la búsqueda tendremos:&lt;/p&gt;
&lt;p&gt;&lt;img alt="resultados de búsqueda con trigrams 03" src="/pictures/django-postgres/trigram-03.png"&gt;&lt;/p&gt;
&lt;p&gt;Esta vez obtenemos inclusive un resultado extra ya que comparte cierta similitud con nuestra búsqueda.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Ventajas de utilizar trigrams&lt;/h3&gt;
&lt;p&gt;Al buscar similitudes en vez de coincidencias exactas un usuario puede equivocarse y escribir: &lt;em&gt;"Harry Porter"&lt;/em&gt; y aún así encontrar algunos resultados:&lt;/p&gt;
&lt;p&gt;&lt;img alt="resultados de búsqueda con trigrams 04" src="/pictures/django-postgres/trigram-04.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Nota:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Como indica la documentación de postgres, para obtener mejores resultados con trigrams, el término a buscar debe tener una longitud similar a los resultados esperados.&lt;/p&gt;
&lt;p&gt;Es por ello que al buscar "Harry Porter" las cadenas de resultados más largas muestran una menor similitud.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;A tener en cuenta !&lt;/h3&gt;
&lt;p&gt;Utilizar trigrams es mucho más pesado (en términos de recursos computacionales) por lo que la búsqueda puede ser más lenta según la cantidad de registros tengamos almacenados en nuestro sistema.&lt;/p&gt;
&lt;p&gt;Para mitigar este problema existen los "index", los cuales veremos en una siguiente ocasión.&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/pgtrgm.html" target="_blank"&gt;postgres trigrams&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="django"/><category term="postgresql"/></entry><entry><title>Subir archivos a Amazon S3 directamente</title><link href="https://alexanderae.com/subir-archivos-s3.html" rel="alternate"/><published>2020-06-14T00:00:00-05:00</published><updated>2020-06-14T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-06-14:/subir-archivos-s3.html</id><summary type="html">&lt;p&gt;Como parte de un proyecto necesito que ciertos usuarios puedan cargar imágenes a Amazon S3. Esto puede realizarse sin necesidad de que el procesamiento se realice en un servidor que yo administre y es lo que se verá a continuación&lt;/p&gt;</summary><content type="html">&lt;p&gt;Como parte de un proyecto necesito que ciertos usuarios puedan cargar imágenes a Amazon S3. Esto puede realizarse sin necesidad de que el procesamiento sea un servidor que yo administre, es decir, que el formulario puede configurarse para que envíe los archivos directamente a Amazon S3.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; si bien es inseguro que permitamos que cualquier usuario suba archivos a nuestro servidor (no sabemos qué archivos subirá), en mi caso serán "usuarios de confianza".&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Para lo anterior necesitamos realizar 2 tipos de tareas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Crear y configurar los servicios necesarios en la consola de AWS&lt;/li&gt;
&lt;li&gt;Escribir el código necesario para la carga de archivos&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Configuración de AWS&lt;/h2&gt;
&lt;p&gt;&lt;br&gt;
&lt;strong&gt;1.1.-&lt;/strong&gt; Lo primero es crear un &lt;em&gt;bucket&lt;/em&gt; en S3, así que nos dirigimos a la consola y escogemos la opción para crear un bucket.&lt;/p&gt;
&lt;p&gt;&lt;img alt="creación de un bucket en s3" src="/pictures/upload-s3/creacion-de-bucket.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;strong&gt;1.2.-&lt;/strong&gt; Permitimos el acceso público a nuestro bucket recién creado.&lt;/p&gt;
&lt;p&gt;Para ello nos dirigimos a la configuración del bucket, sección "Permisos" - "Bloquear Acceso Público" y desmarcamos los checks.&lt;/p&gt;
&lt;p&gt;&lt;br&gt; &lt;br&gt;
&lt;strong&gt;1.3.-&lt;/strong&gt; Configuramos el CORS del bucket S3.&lt;/p&gt;
&lt;p&gt;En la sección "Permisos" - "Configuración del CORS" añadimos un texto de la forma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::xml
&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;CORSConfiguration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://s3.amazonaws.com/doc/2006-03-01/&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;CORSRule&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;AllowedOrigin&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/AllowedOrigin&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;AllowedMethod&amp;gt;&lt;/span&gt;GET&lt;span class="nt"&gt;&amp;lt;/AllowedMethod&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;AllowedMethod&amp;gt;&lt;/span&gt;POST&lt;span class="nt"&gt;&amp;lt;/AllowedMethod&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;AllowedMethod&amp;gt;&lt;/span&gt;PUT&lt;span class="nt"&gt;&amp;lt;/AllowedMethod&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;AllowedHeader&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/AllowedHeader&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/CORSRule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/CORSConfiguration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notando que el &lt;em&gt;"*"&lt;/em&gt; en &lt;em&gt;AllowedHeader&lt;/em&gt; se refiere a que permitiremos que cualquier website solicite de nuestras imágenes. Esto tan solo lo realizaremos en nuestra demo ya que en realidad aquí debe ir el dominio que utilizaremos en nuestra web.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;strong&gt;1.4.-&lt;/strong&gt; Creamos un "conjunto de identidades anónimas" en "Amazon Cognito"&lt;/p&gt;
&lt;p&gt;Para permitir que usuarios sin registrar puedan subir archivos, escogemos la opción de &lt;em&gt;"Administrar grupos de identidades"&lt;/em&gt; y seleccionamos &lt;em&gt;"Crear nuevo grupo de identidades"&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Note que marcaremos la opción de &lt;em&gt;"Habilitar acceso a indentidades sin autenticar"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="creación de un grupo de identidades en aws cognito" src="/pictures/upload-s3/aws-cognito.png"&gt;&lt;/p&gt;
&lt;p&gt;Copiamos el ID que nos entrega (lo usaremos más adelante)&lt;/p&gt;
&lt;p&gt;&lt;img alt="id secreto de aws cognito" src="/pictures/upload-s3/aws-cognito-secrets.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;strong&gt;1.5.-&lt;/strong&gt; Ahora debemos brindarles permisos al nuevo rol creado para que sea capaz de manipular archivos de S3.&lt;/p&gt;
&lt;p&gt;En la consola de AWS, vamos al servicio "IAM", buscamos el rol creado, en mi caso algo similar a &lt;em&gt;Cognito_demo_1428Unauth_Role&lt;/em&gt; y le asociamos una nueva política:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::json
{
    &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
    &amp;quot;Statement&amp;quot;: [
        {
            &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
            &amp;quot;Action&amp;quot;: [
                &amp;quot;s3:*&amp;quot;
            ],
            &amp;quot;Resource&amp;quot;: [
                &amp;quot;arn:aws:s3:::demo-bucket-1428/*&amp;quot;
            ]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y todo listo, al menos la primera parte que se refiere a la configuración de AWS.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Código HTML / JS&lt;/h2&gt;
&lt;p&gt;Utilizaré &lt;em&gt;Cash.js&lt;/em&gt;, una alternativa ligera a Jquery para algunas manipulaciones del DOM.&lt;/p&gt;
&lt;p&gt;El html mínimo sería similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::html
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;photo&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;Escoja&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;foto
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;photo&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;progress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;”100”&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;”0”&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/progress&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Tenemos una barra de progreso de la carga, pero no es necesaria, se puede omitir.&lt;/p&gt;
&lt;p&gt;Luego debemos incluir el javascript del SDK de AWS (ver referencias) y el código de cash.js (puede utilizarse jquery o vanilla js)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::html
&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/cash/7.0.3/cash.min.js&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://sdk.amazonaws.com/js/aws-sdk-2.696.0.min.js&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y la lógica principal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bucketName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;demo-bucket-1428&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bucketRegion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;us-east-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IdentityPoolId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-east-1:credenciales-super-secretas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AWS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bucketRegion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AWS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CognitoIdentityCredentials&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;IdentityPoolId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IdentityPoolId&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AWS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2006-03-01&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;form&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;change&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;body&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cursor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;progress&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;photo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;saveFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;saveFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;directorio_de_prueba/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fileUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://s3.amazonaws.com/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bucketName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;ACL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;public-read&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;body&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cursor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;httpUploadProgress&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uploaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;progress&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uploaded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y eso es todo, ahora tenemos nuestro formulario que sube archivos a S3 sin necesidad de que pase a través de nuestro servidor.&lt;/p&gt;
&lt;p&gt;Como dato extra, para limitar los tipos de archivos permitidos, podemos seguir el siguiente post: &lt;a href="https://aws.amazon.com/es/premiumsupport/knowledge-center/s3-allow-certain-file-types/" target="_blank"&gt;How can I allow only certain file types to be uploaded to my Amazon S3 bucket?&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/fabiospampinato/cash" target="_blank"&gt;Cash JS&lt;/a&gt;: An absurdly small jQuery alternative for modern browsers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/s3-example-photo-album.html" target="_blank"&gt;Uploading Photos to Amazon S3 from a Browser&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://medium.com/@shresthshruti09/uploading-files-in-aws-s3-bucket-through-javascript-sdk-with-progress-bar-d2a4b3ee77b5" target="_blank"&gt;Uploading Files in AWS S3 Bucket through JavaScript SDK with Progress Bar&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/aws/aws-sdk-js" target="_blank"&gt;AWS SDK JS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="AWS"/></entry><entry><title>Capturar la pantalla con Python (Selenium)</title><link href="https://alexanderae.com/capturas-pantalla-python-selenium.html" rel="alternate"/><published>2020-06-10T00:00:00-05:00</published><updated>2020-06-10T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-06-10:/capturas-pantalla-python-selenium.html</id><summary type="html">&lt;p&gt;En el siguiente post veremos cómo obtener capturas de pantalla de websites con Selenium. Selenium es una herramienta utilizada para automatizar la interacción con los navegadores.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sabemos que en Python podemos obtener el código de HTML de una página web con muchas herramientas como: urllib, requests, faster_than_requests entre otras, pero para &lt;strong&gt;renderizar&lt;/strong&gt; el contenido de un website, necesitamos herramientas como &lt;strong&gt;selenium&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.selenium.dev/" target="_blank"&gt;Selenium&lt;/a&gt; es un entorno de pruebas para websites, el cual nos permite automatizar muchas tareas relacionadas al uso de un navegador mediante código.&lt;/p&gt;
&lt;p&gt;Para nuestro ejemplo, necesitaremos dos cosas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Instalar selenium&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install selenium
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Instalar &lt;a href="https://chromedriver.chromium.org/" target="_blank"&gt;chomedriver&lt;/a&gt;, el driver oficial para manipular Chrome.&lt;/p&gt;
&lt;p&gt;Para ello debemos descargar el driver de la página oficial según nuestro sistema operativo, y agregar el directorio que lo contiene al PATH.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; debemos descargar la versión adecuada según la versión de nuestro navegador.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;El código&lt;/h2&gt;
&lt;p&gt;Ahora podemos escribir el código necesario. Noten que usaremos chrome en su versión &lt;a href="https://developers.google.com/web/updates/2017/04/headless-chrome" target="_blank"&gt;headless&lt;/a&gt;, de este modo podemos interactuar con el navegador en entornos donde no necesitamos su interfaz gráfica.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;selenium&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.djangoproject.com/&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChromeOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--window-size=1024,768&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--headless&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--disable-gpu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;django.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo que nos produce la siguiente salida:&lt;/p&gt;
&lt;p&gt;&lt;img alt="captura de la página de Django" src="/pictures/selenium_django.png"&gt;&lt;/p&gt;
&lt;p&gt;Notamos el uso de algunos parámetros como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;--windows-size: para ajustar el tamaño de la ventana a capturar&lt;/li&gt;
&lt;li&gt;--headless: para que chrome se ejecute sin necesidad de iniciar la interfaz gráfica&lt;/li&gt;
&lt;li&gt;--disabled-gpu: parámetro necesario si usamos Windows como sistema operativo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; podemos deshabilitar la visualización del scrollbar con la opción extra &lt;em&gt;--hide-scrollbars&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Tomando una captura de la página completa&lt;/h2&gt;
&lt;p&gt;En la captura anterior, se observa que solo fue tomada una sección de la página. ¿Cómo podemos capturar la pantalla completa del navegador?&lt;/p&gt;
&lt;p&gt;Para ello podemos ejecutar un código javascript para conocer el tamaño de la página y tomar una nueva captura:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;selenium&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.djangoproject.com/&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChromeOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--window-size=1024,768&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--headless&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--hide-scrollbars&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--disable-gpu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# esperamos que cargue todo con un tiempo moderado&lt;/span&gt;
&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;return Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight )&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# realizamos la captura con el &amp;quot;alto&amp;quot; que hemos obtenido&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChromeOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--no-sandbox&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--window-size=1024,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--headless&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--hide-scrollbars&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--disable-gpu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--ignore-certificate-errors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;django_fullpage.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si, así de simple.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Observaciones finales&lt;/h2&gt;
&lt;p&gt;Como hemos visto, con selenium podemos automatizar el proceso de tomar capturas. Un usuario podría ingresar una url en nuestro sistema y mediante lo anterior, podríamos realizar una captura.&lt;/p&gt;
&lt;p&gt;Claro, con selenium podemos automatizar muchas otras tareas como darle click a botones, testear formularios u otros, por lo que recomiendo darle una revisión para conocer todas las posibilidades.&lt;/p&gt;</content><category term="blog"/><category term="python"/></entry><entry><title>Webscraping en 6 líneas de código</title><link href="https://alexanderae.com/webscraping-en-pocas-lineas-de-codigo.html" rel="alternate"/><published>2020-06-07T00:00:00-05:00</published><updated>2020-06-07T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-06-07:/webscraping-en-pocas-lineas-de-codigo.html</id><summary type="html">&lt;p&gt;Hace unos días leí un post sobre cómo realizar &lt;em&gt;web scraping&lt;/em&gt; con 6 líneas de código para generar sentencias INSERT de SQL y de ese modo contar con información de prueba para algún proyecto personal. Este post se basa en aquel, pero con otros ejemplos.&lt;/p&gt;</summary><content type="html">&lt;p&gt;¿Quién no ha realizado algún proyecto y ha necesitado información de prueba?&lt;/p&gt;
&lt;p&gt;Mediante el web scraping, esta tarea resulta trivial, y no te preocupes, no vas a escribir todo a mano.&lt;/p&gt;
&lt;p&gt;Para comenzar, utilizaré el mismo ejemplo de un post anterior: &lt;a href='https://alexanderae.com/webscraping-python.html' target='blank'&gt;Webscraping básico con Python - I&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En dicho post realizamos scraping al website de &lt;a href='https://news.ycombinator.com/' target='_blank'&gt;Hacker News&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En esta ocasión realizaremos algo similar pero solo con la librería estándar de python para enfocarlo de otra manera:&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Paso #1: Consultar a la url&lt;/h2&gt;
&lt;p&gt;Utilizamos la librería &lt;strong&gt;urllib&lt;/strong&gt; de python para consultar el enlace:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib.request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://news.ycombinator.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Paso #2: Extraemos los títulos&lt;/h2&gt;
&lt;p&gt;Al igual que en el post que escribí antes, observamos en el html de &lt;em&gt;Hacker News&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="hacker news html" src="/pictures/scraping_hacker_news.png"&gt;&lt;/p&gt;
&lt;p&gt;y notamos que el título de cada noticia tienen la forma de:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;=&lt;span class="s"&gt;&amp;quot;storylink&amp;quot;&lt;/span&gt;&amp;gt;&lt;span class="n"&gt;TÍTULO&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;con ello podemos hacer uso de las &lt;em&gt;expresiones regulares&lt;/em&gt; para extraer los bloques similares.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; No olvidemos que tenemos que convertir la salida de urlopen para poder leerla como string.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib.request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://news.ycombinator.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;all_titles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;class=&amp;quot;storylink&amp;quot;&amp;gt;(.*?)&amp;lt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Paso 3: El gran final, generar las sentencias SQL&lt;/h2&gt;
&lt;p&gt;Las sentencias INSERT tienen la siguiente forma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;INSERT INTO &amp;quot;nombre_tabla&amp;quot; (&amp;quot;columna1&amp;quot;, &amp;quot;columna2&amp;quot;, ...);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;organizando nuestro código para producir dicha salida, tendríamos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib.request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://news.ycombinator.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;all_titles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;class=&amp;quot;storylink&amp;quot;&amp;gt;(.*?)&amp;lt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_titles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;insert into Titles values (&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;);&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y al ejecutar dicho código en la consola tendríamos una salida similar a:&lt;/p&gt;
&lt;p&gt;&lt;img alt="hacker news html" src="/pictures/scraping_demo.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Ejemplo extra: obteniendo noticias del diario "El Comercio"&lt;/h2&gt;
&lt;p&gt;Tomando como base la página de noticias de &lt;em&gt;Política&lt;/em&gt; de "El Comercio", podríamos realizarlo con:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib.request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://elcomercio.pe/politica/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;all_titles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;class=&amp;quot;story-item__title block overflow-hidden primary-font line-h-xs mt-10&amp;quot; href=&amp;quot;([\w/-]*?)&amp;quot;&amp;gt;(.*?)&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_titles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;De este modo obtenemos los títulos de las últimas 50 noticias:&lt;/p&gt;
&lt;p&gt;&lt;img alt="hacker news html" src="/pictures/scraping_el_comercio.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Notas finales&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;No debemos olvidar que siempre que realicemos webscraping tengamos en cuenta los factores legales: propiedad intelectual, cuidarse de no agotar al servidor con muchas consultas simultáneas, sensibilidad de la información, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Podemos mejorar nuestro código mediante el uso de librerías como: requests, beautifulsoup, faster_than_requests u otras.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href='https://medium.com/@rodrigo.m.mesquita/get-data-for-your-db-schoolwork-with-6-lines-of-python-e68a108f65ff' target='_blank'&gt;Get data for your DB schoolwork with 6 lines of python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;</content><category term="blog"/><category term="python"/><category term="webscraping"/></entry><entry><title>Profiling con Python</title><link href="https://alexanderae.com/profiling-python.html" rel="alternate"/><published>2020-04-12T00:00:00-05:00</published><updated>2020-04-12T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-04-12:/profiling-python.html</id><summary type="html">&lt;p&gt;Revisaremos el concepto de profiling, los módulos que podemos utilizar en python (profile, cprofile) asi como el uso de una interfaz gráfica (snakeviz) para ver los resultados.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hoy tuve como tarea reducir el tiempo de ejecución de una rutina que tomaba 40 minutos. Era código extenso que veía por primera vez, por lo que decidí usar el módulo "cProfile" de python para realizar un análisis de los posibles cuellos de botella y saber &lt;em&gt;qué se debe tomar como prioridad al optimizar del total del código&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Finalmente logré alcanzar los 3 minutos de ejecución centrándome solo en los métodos más lentos o aquellos que se llamaban en altas cantidades. &lt;/p&gt;
&lt;h3&gt;¿A qué se le llama profiling?&lt;/h3&gt;
&lt;p&gt;Se refiere a la acción de realizar la medición de con qué frecuencia y en cuanto tiempo se ejecutan las diversas partes de un programa.&lt;/p&gt;
&lt;h3&gt;Profiling en Python&lt;/h3&gt;
&lt;p&gt;Python incluye los módulos &lt;strong&gt;cProfile&lt;/strong&gt; y &lt;strong&gt;profile&lt;/strong&gt; en su librería estandar para realizar profiling.&lt;/p&gt;
&lt;p&gt;Ambos módulos proveen las mismas interfaces (métodos) pero la diferencia radica en que &lt;em&gt;profile&lt;/em&gt; está implementado en python y es más fácil "extenderlo" (personalizarlo).&lt;/p&gt;
&lt;p&gt;La documentación nos recomienda utilizar &lt;strong&gt;cProfile&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Ejemplo de uso&lt;/h3&gt;
&lt;p&gt;Para el ejemplo utilizo la serie de Fibonacci:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;profile&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fib_seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fib_seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;

&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;print(fib_seq(32))&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cuyo resultado es:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
18454895/33    4.665    0.000    4.665    0.141 &amp;lt;stdin&amp;gt;:1(fib)
     33/1    0.000    0.000    4.665    4.665 &amp;lt;stdin&amp;gt;:1(fib_seq)
        1    0.000    0.000    4.666    4.666 &amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)
        1    0.000    0.000    4.666    4.666 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
       33    0.000    0.000    0.000    0.000 {method &amp;#39;append&amp;#39; of &amp;#39;list&amp;#39; objects}
        1    0.000    0.000    0.000    0.000 {method &amp;#39;disable&amp;#39; of &amp;#39;_lsprof.Profiler&amp;#39; objects}
       32    0.000    0.000    0.000    0.000 {method &amp;#39;extend&amp;#39; of &amp;#39;list&amp;#39; objects}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Donde tenemos que:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ncalls: es el total de veces que se llaman a la función. En este caso la función &lt;em&gt;fib&lt;/em&gt; se llama 18454895 veces debido a que es  una función recursiva.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tottime: es el total de tiempo que transcurre exclusivamente dentro de la función sin tomar en cuenta las llamadas a subfunciones de la misma.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;percall: es el tiempo anterior pero dividido entre el total de llamadas a la función (tiempo promedio).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cumtime: es el tiempo total dentro de una función (incluidas subfunciones)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;percall: "cumtime" / número de ejecuciones (tiempo promedio total)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;filename:lineno(function): se refiere a la función en cuestión&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para facilitar el paso de parámetros también podemos utilizar el método &lt;strong&gt;runctx&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    cProfile.runctx(&amp;#39;fib_seq(n)&amp;#39;, globals(), {&amp;#39;n&amp;#39;: 32})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;e inclusive podemos generar un archivo que contenga los resultados:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    cProfile.runctx(&amp;#39;fib_seq(n)&amp;#39;, globals(), {&amp;#39;n&amp;#39;: 32}, &amp;#39;output.prof&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Utilizando snakeviz para visualizar los resultados de forma gráfica&lt;/h3&gt;
&lt;p&gt;Snakeviz es un visor gráfico basado en el navegador para la salida del profiler "cProfile" de python.&lt;/p&gt;
&lt;p&gt;Para instalarlo bastaría:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;pip install snakeviz&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;y luego ya solo llamaríamos a la aplicación en consola mediante:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;snakeviz output.prof&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;y nos mostrará una salida similar a:&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshot of snakeviz" src="/pictures/snakeviz.png" title="Profiling - snakeviz"&gt;&lt;/p&gt;
&lt;p&gt;en la que se desglosan las respectivas funciones llamadas y sus tiempos.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Recursos:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://docs.python.org/3.8/library/profile.html'&gt;Python profile&lt;/a&gt;: Librería estándar de Python&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://pymotw.com/2/profile/'&gt;profile, cProfile, and pstats&lt;/a&gt;: Performance analysis of Python programs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href="https://jiffyclub.github.io/snakeviz/"&gt;Snakeviz&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="python"/><category term="optimizacion"/></entry><entry><title>Recursos para diseñadores (2020)</title><link href="https://alexanderae.com/recursos-para-dise%C3%B1adores-2020.html" rel="alternate"/><published>2020-03-29T00:00:00-05:00</published><updated>2020-03-29T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-03-29:/recursos-para-diseñadores-2020.html</id><summary type="html">&lt;p&gt;En algún momento he tenido que realizar tareas sencillas de diseño por lo que alguna de estas páginas me han sido de ayuda. Esto incluye: páginas con imágenes de stock gratuitas, generadores de paletas de colores, íconos o tipografías gratuitas entre otros&lt;/p&gt;</summary><content type="html">&lt;p&gt;Compilación de recursos para diseñadores que en algún momento me han sido de ayuda. Trataré  de incluir el mínimo de páginas por bloque para hacerlo conciso.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Imágenes stock&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://pixabay.com/'&gt;Pixabay&lt;/a&gt;: Creo que es la mejor y más ordenada entre las páginas que contienen imágenes stock gratuitas (1.6 millones de imágenes).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://unsplash.com/'&gt;Unsplash&lt;/a&gt;: Segunda opción personal a la hora de buscar imágenes (más de 1 millón de imágenes)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://visualhunt.com/'&gt;Visualhunt&lt;/a&gt;: Permite realizar búsquedas por colores&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.everypixel.com/'&gt;Everypixel&lt;/a&gt;: Buscador con múltiples filtros que agrupa a otros buscadores de imágenes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Otros recursos de imágenes stock&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target='_blank' href='https://undraw.co/illustrations'&gt;Undraw&lt;/a&gt;: ilustraciones&lt;/li&gt;
&lt;li&gt;&lt;a target='_blank' href='https://www.pexels.com/es-es/'&gt;Pexels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target='_blank' href='https://gratisography.com/'&gt;Gratisography&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target='_blank' href='https://wunderstock.com/'&gt;Wunderstock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Patrones de imágenes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target='_blank' href='http://thepatternlibrary.com/'&gt;The Pattern library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Colores&lt;/h2&gt;
&lt;h3&gt;Colección de paletas de colores&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.happyhues.co/'&gt;Happy hues&lt;/a&gt;: Paletas de colores con vista previa en el propio website&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.schemecolor.com/'&gt;Scheme color&lt;/a&gt;: Colección de paletas con búsqueda por colores&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://colorhunt.co/'&gt;Colorhunt&lt;/a&gt;: Colección de paletas de colores listas para usar&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Generadores de paletas&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://coolors.co/app'&gt;Coolors&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://mycolor.space/'&gt;Mycolor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.canva.com/colors/color-palette-generator/'&gt;Canva color palette&lt;/a&gt;: Extrae la paleta de colores desde una imagen&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Íconos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.iconfinder.com/'&gt;IconFinder&lt;/a&gt;: Se deben usar sus filtros para escoger solo los íconos gratuitos&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://orioniconlibrary.com/all-icons'&gt;Orion icon library&lt;/a&gt;: 8600 íconos gratuitos ordenados por categorías&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.baianat.com/resources/thousands'&gt;Thousands&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://feathericons.com/'&gt;Feather icons&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://boxicons.com/'&gt;Box icons&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://iconstore.co/'&gt;Icon store&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Emails&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target='_blank' href='https://reallygoodemails.com/'&gt;Really good emails&lt;/a&gt;: Colección de correos bien estructurados y diseñados.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Colecciones de páginas web a modo de inspiración&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://onepagelove.com/'&gt;One Page Love&lt;/a&gt;: Landing Pages con buen diseño&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='http://lapa.ninja/'&gt;lapa.ninja&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='http://uigarage.net/'&gt;UI Garage&lt;/a&gt;: Patrones de diseño de elementos web (Páginas 404, Dashboard, Mensajes de error, etc)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='http://www.onepagemania.com/'&gt;OnePageMania&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://pageflows.com/'&gt;PageFlows&lt;/a&gt;: Flujos de algunas acciones en sitios webs conocidos&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='http://mediaqueri.es/'&gt;Mediaqueri.es&lt;/a&gt;: Páginas en sus versiones responsive&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.siteinspire.com/'&gt;SiteInspire&lt;/a&gt;: Colección de sitios web&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://bestwebsite.gallery/'&gt;BestWebsite&lt;/a&gt;: Colección de sitios web&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.pages.xyz/'&gt;Pages.xyz&lt;/a&gt;: Muestra páginas del tipo: landing, productos, precios y aplicaciones.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Accesibilidad&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://wave.webaim.org/'&gt;Wave&lt;/a&gt;: Herramienta de prueba de accesibilidad de un sitio web&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.funkify.org/'&gt;Funkify&lt;/a&gt;: Extensión para chrome que simula la visualización de una página web para personas con diferentes habilidades y discapacidades.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://contrastchecker.com/'&gt;Contrast checker&lt;/a&gt;: Verifica el contraste de 2 colores (frente y fondo) &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Otros&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://tinypng.com/'&gt;TinyPNG&lt;/a&gt;: Herramienta para reducir el tamaño de imágenes (jpg, png)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='http://waifu2x.udp.jp/'&gt;Waifu2x&lt;/a&gt;: Herramienta para ampliar o mejorar imágenes sin perder resolución.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='http://paperkit.net/'&gt;PaperKit&lt;/a&gt;: Grillas para imprimir.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Páginas que compilan recursos (Inception)&lt;/h2&gt;
&lt;p&gt;&lt;img alt="inception" src="/pictures/inception.jpg" title="Inception"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.uigoodies.com/'&gt;UI Goodies&lt;/a&gt;: Amplia variedad de recursos como: animaciones, audios, libros, imágenes, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='http://www.sansfrancis.co/'&gt;Sans Francisco&lt;/a&gt;: Página que agrupa recursos relacionados al proceso UX que se sigue al implementar un website además de los recursos usuales (fotos stock, paletas de colores)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://www.indiemakers.tools'&gt;Indie Makers&lt;/a&gt;: Compilación de recursos de varios rubros como: diseño, gestión de proyectos, SEO, marketing, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Actualizado al 29 de Marzo del 2020&lt;/p&gt;
&lt;p&gt;Post publicado en &lt;a href='https://alexanderae.com'&gt;alexanderae.com&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="diseño"/></entry><entry><title>Convertir un archivo CSV a SQLite con Python</title><link href="https://alexanderae.com/convertir-csv-sqlite-python.html" rel="alternate"/><published>2020-03-24T00:00:00-05:00</published><updated>2020-03-24T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2020-03-24:/convertir-csv-sqlite-python.html</id><summary type="html">&lt;p&gt;Instrucciones para convertir un archivo CSV a un archivo SQLite. SQLite viene con su propia línea de comandos para realizar algunas tareas, entre ellas, la importación de archivos CSV.&lt;/p&gt;</summary><content type="html">&lt;h4&gt;¿Qué es un archivo CSV?&lt;/h4&gt;
&lt;p&gt;Un archivo &lt;strong&gt;CSV&lt;/strong&gt; (Comma Separated Values) es un archivo que contiene datos separados por comas (u otros separadores como tabuladores, espacios, puntos y coma, etc). En estos archivos cada línea representa un registro.&lt;/p&gt;
&lt;p&gt;Por ejemplo, este archivo toma la forma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Código, Nombre, Departamento
014285, Colegio Belén, Lima
028514, IIEE 2042, Arequipa
042857, Colegio Toribianitos, Lima
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;¿Cómo convertir un archivo CSV a un archivo SQLITE?&lt;/h4&gt;
&lt;p&gt;SQLite viene con su propia línea de comandos para realizar algunas tareas, entre ellas, la importación de archivos CSV.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sqlite&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;input.csv&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo que acabamos de realizar es:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Activar el modo CSV (.mode csv)&lt;/li&gt;
&lt;li&gt;Importar el archivo en la tabla indicada&lt;/li&gt;
&lt;li&gt;Verificamos el script utilizado para crear la tabla anterior&lt;/li&gt;
&lt;li&gt;Exportamos el archivo a una base de datos &lt;em&gt;sqlite&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Fin :)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;¿Por qué no uso python?&lt;/h4&gt;
&lt;p&gt;En mi caso, el archivo utilizado tenía 170k+ registros y no requería la modificación de sus registros previo a la inserción, por lo que utilizar python era mucho más lento y terminé usando la shell de sqlite directamente.&lt;/p&gt;
&lt;h4&gt;¿Cómo convertir un archivo CSV a SQLite con Python?&lt;/h4&gt;
&lt;p&gt;Dependiento de nuestras necesidades podemos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Utilizar un ORM para generar el código SQL (Pewee ORM, PonyORM, etc)&lt;/li&gt;
&lt;li&gt;Utilizar pandas&lt;/li&gt;
&lt;li&gt;Utilizar dataset (o en general alguna otra librería), por ejemplo:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dataset&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;csv2dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename_output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;file_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;input_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sqlite:///&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;filename_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;contactos&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;contact&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;uuid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;
            &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;csv2dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;direcciones.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;direcciones.sqlite&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Referencias&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a target='_blank' href='https://sqlite.org/cli.html'&gt;Command Line Shell for SQLITE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target='_blank' href='https://www.sqlitetutorial.net/sqlite-import-csv/'&gt;Import a CSV File Into an SQLite Table&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target='_blank' href='https://www.sqlitetutorial.net/sqlite-import-csv/'&gt;Dataset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target='_blank' href='https://blog.lordvan.com/blog/python-dataset-library-csv-to-sqlite/'&gt;Python Dataset Library -- CSV to SQLite&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content><category term="blog"/><category term="python"/></entry><entry><title>Peewee: ORM para python - I</title><link href="https://alexanderae.com/peewee-orm-para-python-1.html" rel="alternate"/><published>2019-12-20T22:00:00-05:00</published><updated>2019-12-20T22:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2019-12-20:/peewee-orm-para-python-1.html</id><summary type="html">&lt;h5&gt;¿Qué es?&lt;/h5&gt;
&lt;p&gt;Peewee es un &lt;a href="http://es.wikipedia.org/wiki/Mapeo_objeto-relacional"&gt;ORM&lt;/a&gt; para python. Incluye soporte para SQLite, MySQL, PostgreSQL y Cockroachdb.&lt;/p&gt;
&lt;h5&gt;¿Qué es un ORM?&lt;/h5&gt;
&lt;p&gt;Sus siglas significan &lt;em&gt;Object-Relational mapping&lt;/em&gt;, o en español: &lt;em&gt;Mapeo Objeto-relacional&lt;/em&gt;. Es una técnica que permite convertir entre los tipos de datos usados en los lenguajes de programación orientados a objetos y los tipos de datos de algun sistema de base de datos relacional.&lt;/p&gt;
&lt;h5&gt;Instalación&lt;/h5&gt;
&lt;p&gt;Se instala mediante &lt;a href="https://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;pip install peewee&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Para comprobar que se haya realizado la instalación, podemos …&lt;/p&gt;</summary><content type="html">&lt;h5&gt;¿Qué es?&lt;/h5&gt;
&lt;p&gt;Peewee es un &lt;a href="http://es.wikipedia.org/wiki/Mapeo_objeto-relacional"&gt;ORM&lt;/a&gt; para python. Incluye soporte para SQLite, MySQL, PostgreSQL y Cockroachdb.&lt;/p&gt;
&lt;h5&gt;¿Qué es un ORM?&lt;/h5&gt;
&lt;p&gt;Sus siglas significan &lt;em&gt;Object-Relational mapping&lt;/em&gt;, o en español: &lt;em&gt;Mapeo Objeto-relacional&lt;/em&gt;. Es una técnica que permite convertir entre los tipos de datos usados en los lenguajes de programación orientados a objetos y los tipos de datos de algun sistema de base de datos relacional.&lt;/p&gt;
&lt;h5&gt;Instalación&lt;/h5&gt;
&lt;p&gt;Se instala mediante &lt;a href="https://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;pip install peewee&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Para comprobar que se haya realizado la instalación, podemos acceder a la consola interactiva de python y escribimos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;peewee&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;peewee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__version__&lt;/span&gt;
&lt;span class="mf"&gt;3.13.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;puede consultar más información sobre la instalación &lt;a href="http://docs.peewee-orm.com/en/latest/peewee/installation.html"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h5&gt;Ejemplo de uso&lt;/h5&gt;
&lt;p&gt;Para ilustrar&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; la idea, utilizaremos el mismo ejemplo de un post anterior que realicé sobre &lt;a href="https://alexanderae.com/sqlalchemy-orm-para-python-1.html"&gt;sqlalchemy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;El ejemplo es sobre un modelo de datos para un sistema que se encarga de administrar registros de libros, autores, y etiquetas (o temas, para los libros).&lt;/p&gt;
&lt;p&gt;El esquema del modelo es el siguiente:&lt;/p&gt;
&lt;p&gt;&lt;img alt="modelo-libros" src="/pictures/sqlalchemy-modelo-libros.png" title="Modelo de datos"&gt;&lt;/p&gt;
&lt;p&gt;Consiste en las tablas Libro, Autor y Etiqueta principalmente.
Las tablas intermediarias &lt;em&gt;etiqueta_libro&lt;/em&gt; y &lt;em&gt;autor_libro&lt;/em&gt; se utilizan para expresar la relación de &lt;em&gt;muchos a muchos&lt;/em&gt; entre las otras tablas.&lt;/p&gt;
&lt;p&gt;Primero, debemos mapear el modelo de la base de datos por medio de peewee, para lo cual escribí tenemos el archivo siguiente que nombrare &lt;em&gt;models.py&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;peewee&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SqliteDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libros.db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Libro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fecha_publicacion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;isbn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;autores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libros&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;etiquetas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libros&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;


&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_tables&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autores&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_through_model&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                  &lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etiquetas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_through_model&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
                 &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo que realiza el código anterior es lo siguiente:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Importa los módulos necesarios&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Realiza la conexión a la base de datos, en este caso corresponde a sqlite&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapeamos las tablas como clases, utilizando los tipos de datos de &lt;strong&gt;peewee&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Añadimos las relaciones entre las tablas por medio del campo &lt;em&gt;ManyToManyField&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Al final, usamos la sentencia:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;db.create_tables
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;observando que para crear las tablas intermedias (libro_autor y libro_etiquetas) utilizamos una sintaxis especial.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;y ejecutamos el script mediante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    python models.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con ello, observaremos que se creó el archivo &lt;em&gt;"libros.db"&lt;/em&gt; el cual contiene nuestra base de datos.&lt;/p&gt;
&lt;p&gt;Se puede verificar el contenido de las tablas mediante &lt;em&gt;sqliteman&lt;/em&gt;&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;La inserción de registros la podemos realizar de la siguiente manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="n"&gt;autor_1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Patrick Rothfuss&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;autor_2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fred Saberhagen&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;autor_3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Julio verne&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;etiqueta_1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ciencia Ficción&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;etiqueta_2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fantasía&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;libro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;El nombre del viento&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;9788401337208&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;libro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autores&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a1, a2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;libro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etiquetas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;etiqueta_1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Para modificar algún campo solo debemos modificar el atributo y llamar al método &lt;em&gt;save&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
autor_3.nombre = &amp;#39;Julio Verne&amp;#39;
autor_3.save()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y las consultas son medainte el método &lt;em&gt;select&lt;/em&gt; y los filtros mediante el método &lt;em&gt;where&lt;/em&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
libro = Libro.select().where(Libro.isbn == &amp;#39;9788401337208&amp;#39;)[0]
print(libro.titulo)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Espero que el post pueda brindar una idea del cómo funciona &lt;strong&gt;peewee&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Para información más amplia puede guiarse de la &lt;a href="http://docs.peewee-orm.com/en/latest/index.html"&gt;documentación&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Sqliteman es un gestor de base de datos para sqlite3&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="python"/><category term="base-de-datos"/><category term="peewee"/></entry><entry><title>Webscraping básico con Python - III</title><link href="https://alexanderae.com/webscraping-python-3.html" rel="alternate"/><published>2019-12-04T00:00:00-05:00</published><updated>2019-12-04T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2019-12-04:/webscraping-python-3.html</id><summary type="html">&lt;p&gt;En esta ocasión, siguiendo el ejemplo del post anterior, realizaré la misma tarea pero utilizando &lt;strong&gt;&lt;a href='https://scrapy.org/' target='_blank'&gt;Scrapy&lt;/a&gt;&lt;/strong&gt;, un framework especializado en la tarea de realizar webscraping.&lt;/p&gt;
&lt;p&gt;Nuestro objetivo es el mismo: descargar la lista de libros y precios de &lt;a target='_blank' href='http://books.toscrape.com'&gt;Books to scrape&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Requisitos&lt;/h2&gt;
&lt;p&gt;Necesitamos instalar &lt;em&gt;scrapy&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;pip install scrapy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Comenzando&lt;/h3&gt;
&lt;p&gt;Nuestro script, según el ejemplo de la documentación de &lt;em&gt;scrapy&lt;/em&gt;, toma la siguiente forma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scrapy&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://books.toscrape.com&amp;#39;&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;BooksSpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;books&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;start_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;En esta ocasión, siguiendo el ejemplo del post anterior, realizaré la misma tarea pero utilizando &lt;strong&gt;&lt;a href='https://scrapy.org/' target='_blank'&gt;Scrapy&lt;/a&gt;&lt;/strong&gt;, un framework especializado en la tarea de realizar webscraping.&lt;/p&gt;
&lt;p&gt;Nuestro objetivo es el mismo: descargar la lista de libros y precios de &lt;a target='_blank' href='http://books.toscrape.com'&gt;Books to scrape&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Requisitos&lt;/h2&gt;
&lt;p&gt;Necesitamos instalar &lt;em&gt;scrapy&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;pip install scrapy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Comenzando&lt;/h3&gt;
&lt;p&gt;Nuestro script, según el ejemplo de la documentación de &lt;em&gt;scrapy&lt;/em&gt;, toma la siguiente forma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scrapy&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://books.toscrape.com&amp;#39;&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;BooksSpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;books&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;start_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.product_pod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;h3 a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;p.price_color::text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:],&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y ejecutamos el código con:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;scrapy runspider books.py -o books.csv&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;¿Cómo interpreta scrapy lo anterior?&lt;/h2&gt;
&lt;p&gt;Scrapy inicia consultando los enlaces en &lt;em&gt;start_urls&lt;/em&gt;, en este caso solo es un enlace, y posteriormente, ejecuta el método &lt;em&gt;"parse"&lt;/em&gt;, enviándole el objecto &lt;em&gt;"response"&lt;/em&gt; como parámetro.&lt;/p&gt;
&lt;p&gt;Dentro del método &lt;em&gt;parse&lt;/em&gt;, utilizamos selectores CSS mediante la librería &lt;em&gt;parsel&lt;/em&gt;, la cual actua de alternativa a &lt;em&gt;beautifulsoup&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Nótese que mediante la línea de comandos también indicamos el archivo de salida en formato CSV. Scrapy soporta los siguientes formatos de forma nativa: JSON, XML y CSV.&lt;/p&gt;
&lt;h3&gt;Iterando sobre las páginas&lt;/h3&gt;
&lt;p&gt;Si se observa el archivo de resultado, veremos que scrapy solo retorna los resultados de la primera página. Para ellos&lt;/p&gt;
&lt;p&gt;Actualizando el código para considerar la paginación, quedaría del siguiente modo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scrapy&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://books.toscrape.com&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;prefix_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;catalogue&amp;#39;&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;BooksSpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;books&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;start_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.product_pod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;h3 a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;p.price_color::text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:],&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;next_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.pager .next a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prefix_page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y al ejecutar lo anterior nuevamente:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;scrapy runspider books.py -o books.csv&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;obtendríamos de resultado los 1000 libros que existen en la página.&lt;/p&gt;
&lt;h3&gt;Conclusión&lt;/h3&gt;
&lt;p&gt;Scrapy resulta una herramienta muy práctica ya que:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tiene soporte nativo para selectores CSS&lt;/li&gt;
&lt;li&gt;Soporte nativo para generar archivos de salida (CSV, JSON)&lt;/li&gt;
&lt;li&gt;Permite rotar el "User Agent"&lt;/li&gt;
&lt;li&gt;Permite agregar una demora entre descargas para no sobrecargar las páginas a scrapear (DOWNLOAD_DELAY)&lt;/li&gt;
&lt;li&gt;Incluye una herramienta que realiza un benchmark a nuestro proceso&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Publicaciones anteriores&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://docs.scrapy.org/en/latest/intro/overview.html'&gt;Webscraping básico con Python - I&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a target='_blank' href='https://parsel.readthedocs.io/en/latest/usage.html'&gt;Webscraping básico con Python - II&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Referencias&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Documentación de &lt;a target='_blank' href='https://www.crummy.com/software/BeautifulSoup/bs4/doc/'&gt;Scrapy&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Documentación de &lt;a target='_blank' href='https://requests.readthedocs.io/en/master/'&gt;Parsel&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="webscraping"/><category term="python"/></entry><entry><title>Webscraping básico con Python - II</title><link href="https://alexanderae.com/webscraping-python-2.html" rel="alternate"/><published>2019-12-01T00:00:00-05:00</published><updated>2019-12-01T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2019-12-01:/webscraping-python-2.html</id><summary type="html">&lt;p&gt;Continuando con un post realizado hace unos meses sobre los fundamentos del webscraping: &lt;a target='_blank' href='https://alexanderae.com/webscraping-python.html'&gt;Webscraping básico con Python - I&lt;/a&gt;, en esta ocasión presento otro ejemplo práctico para obtener información de un website.&lt;/p&gt;
&lt;p&gt;Para nuestra pruebas utilizaremos &lt;a target='_blank' href='http://books.toscrape.com'&gt;Books to scrape&lt;/a&gt;, una página que simula ser una librería, creada con fines educativos por ScrapingHub&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Dicha web cuenta con:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1000 items en total&lt;/li&gt;
&lt;li&gt;Paginación&lt;/li&gt;
&lt;li&gt;20 items por página&lt;/li&gt;
&lt;li&gt;No requiere javascript para leer el contenido&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Requisitos&lt;/h2&gt;
&lt;p&gt;Necesitamos tener instaladas los siguientes paquetes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;beautifulsoup4 …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Continuando con un post realizado hace unos meses sobre los fundamentos del webscraping: &lt;a target='_blank' href='https://alexanderae.com/webscraping-python.html'&gt;Webscraping básico con Python - I&lt;/a&gt;, en esta ocasión presento otro ejemplo práctico para obtener información de un website.&lt;/p&gt;
&lt;p&gt;Para nuestra pruebas utilizaremos &lt;a target='_blank' href='http://books.toscrape.com'&gt;Books to scrape&lt;/a&gt;, una página que simula ser una librería, creada con fines educativos por ScrapingHub&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Dicha web cuenta con:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1000 items en total&lt;/li&gt;
&lt;li&gt;Paginación&lt;/li&gt;
&lt;li&gt;20 items por página&lt;/li&gt;
&lt;li&gt;No requiere javascript para leer el contenido&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Requisitos&lt;/h2&gt;
&lt;p&gt;Necesitamos tener instaladas los siguientes paquetes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;beautifulsoup4: facilita el scraping de websites permitiendo acceder a los elementos HTML de una página de forma sencilla &lt;/li&gt;
&lt;li&gt;lxml: HTML parser&lt;/li&gt;
&lt;li&gt;requests: permite realizar consultas HTTP &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;El problema&lt;/h2&gt;
&lt;p&gt;A modo de ejemplo, intentaremos obtener la información de los libros tales como título y precios.&lt;/p&gt;
&lt;h3&gt;Comenzando&lt;/h3&gt;
&lt;p&gt;Nuestro script de forma inicial podría tomar la siguiente forma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://books.toscrape.com&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo indicado anteriormente tan solo obtiene el contenido del Home (la pantalla inicial).&lt;/p&gt;
&lt;h3&gt;Listando las páginas&lt;/h3&gt;
&lt;p&gt;Analizando la página observamos que la paginación  en la sección inferior cuenta con los botones "previous" y "next".&lt;/p&gt;
&lt;p&gt;El enfoque que tomaremos será obtener el enlace "next", redirigirnos hacia aquella página y buscar el nuevo enlace "next". En caso de que no exista una página siguiente, nos detenemos.&lt;/p&gt;
&lt;p&gt;Notemos que &lt;b&gt;"next_url"&lt;/b&gt; se construye de dos maneras porque en la página 1 el enlace es del tipo &lt;b&gt;"catalogue/page-2.html"&lt;/b&gt; mientras que en la página 2 hacia adelante es del tipo &lt;b&gt;"page-3.html"&lt;/b&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;bs4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;


&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://books.toscrape.com&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;prefix_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;catalogue&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;obtiene_paginas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;lxml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;next_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.pager .next a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prefix_page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;obtiene_paginas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Obtenemos los libros y sus precios&lt;/h3&gt;
&lt;p&gt;Cada página tiene un listado de libros, a su vez, cada libro está en un bloque con una clase CSS: &lt;b&gt;"product_pod"&lt;/b&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
def get_books(soup):
    books = []
    html_books = soup.select(&amp;#39;.product_pod&amp;#39;)
    for html_book in html_books:
        books.append({
            &amp;#39;title&amp;#39;: html_book.select(&amp;#39;h3 a&amp;#39;)[0][&amp;#39;title&amp;#39;],
            &amp;#39;price&amp;#39;: html_book.select(&amp;#39;.price_color&amp;#39;)[0].get_text()[2:]
        })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note que para el precio, estamos comenzando desde el caracter número 3 ([2:]) ya que los 2 primeros caracteres contienen información no relevante.&lt;/p&gt;
&lt;h3&gt;Grabando los resultados en una archivo CSV&lt;/h3&gt;
&lt;p&gt;Un archivo CSV, es un archivo de texto con valores separados por comas que representan datos en forma de tabla.&lt;/p&gt;
&lt;p&gt;Escribimos una función que reciba la lista de libros y las escriba al final de un archivo CSV, de tal forma que nuestro código queda similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;export_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;books.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;csvwriter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;csvwriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Resultado&lt;/h3&gt;
&lt;p&gt;Nuestro código finalmente quedará del siguiente modo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;timeit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;default_timer&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;bs4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://books.toscrape.com&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;prefix_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;catalogue&amp;#39;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_pages&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;lxml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_books&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;export_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;next_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.pager .next a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prefix_page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_books&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;html_books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.product_pod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;html_book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;html_books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;html_book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;h3 a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;html_book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.price_color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;export_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;books.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;csvwriter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;csvwriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;get_pages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Noten que hemos añadido un contador para conocer cuánto tiempo demora nuestro código en ejecutarse.&lt;/p&gt;
&lt;p&gt;Si se ejecuta el script, podremos notar que el documento CSV creado contiene los 1000 libros que extrajimos desde la página de prueba.&lt;/p&gt;
&lt;h3&gt;Posibles mejoras&lt;/h3&gt;
&lt;p&gt;Un tip rápido para mejorar los tiempos de procesamiento es el de reemplazar la librería &lt;strong&gt;requests&lt;/strong&gt; por: &lt;a target='_blank' href='https://github.com/juancarlospaco/faster-than-requests'&gt;Faster-than-Requests
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En mi caso, el tiempo de ejecución total se redujo a la mitad con tan solo el reemplazar la librería.&lt;/p&gt;
&lt;h3&gt;Referencias&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Documentación de &lt;a target='_blank' href='https://www.crummy.com/software/BeautifulSoup/bs4/doc/'&gt;BeautifulSoup&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Documentación de &lt;a target='_blank' href='https://requests.readthedocs.io/en/master/'&gt;requests&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Documentación de &lt;a target='_blank' href='https://docs.python.org/3/library/csv.html#csv.writer'&gt;Python para manipular archivos CSV&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a target='_blank' href='https://scrapinghub.com/'&gt;ScrapingHub&lt;/a&gt; es una empresa dedicada a brindar servicios de webscraping.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="webscraping"/><category term="python"/></entry><entry><title>¿Cómo encontrar imágenes similares con Python?</title><link href="https://alexanderae.com/imagenes-similares-python.html" rel="alternate"/><published>2019-11-24T00:00:00-05:00</published><updated>2019-11-24T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2019-11-24:/imagenes-similares-python.html</id><summary type="html">&lt;h2&gt;El problema: imágenes que no coinciden (y deberían coincidir)&lt;/h2&gt;
&lt;p&gt;Una de las tareas en &lt;a href="https://librera.pe" target="_blank"&gt;Librera&lt;/a&gt; implica recopilar la información de un libro en base a su ISBN, para lo cual en algunos casos se recurre al &lt;strong&gt;webscraping&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;En algunos casos, al obtener la portada de un libro desde algún servicio externo como Google Books API, Goodreads API o scrapeando alguna web  verificábamos que obteníamos una portada incorrecta.&lt;/p&gt;
&lt;p&gt;Nuestro proceso para obtener la portada de un libro implicaba:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Consultar varias fuentes …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;h2&gt;El problema: imágenes que no coinciden (y deberían coincidir)&lt;/h2&gt;
&lt;p&gt;Una de las tareas en &lt;a href="https://librera.pe" target="_blank"&gt;Librera&lt;/a&gt; implica recopilar la información de un libro en base a su ISBN, para lo cual en algunos casos se recurre al &lt;strong&gt;webscraping&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;En algunos casos, al obtener la portada de un libro desde algún servicio externo como Google Books API, Goodreads API o scrapeando alguna web  verificábamos que obteníamos una portada incorrecta.&lt;/p&gt;
&lt;p&gt;Nuestro proceso para obtener la portada de un libro implicaba:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Consultar varias fuentes (API's)&lt;/li&gt;
&lt;li&gt;Escoger la portada en base al tamaño de la imagen, ya que un mayor tamaño, véase ancho x alto implicaba "mayor calidad".&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;Nota:&lt;/b&gt; No descargamos cada imagen, sino que tan solo verificábamos los primeros bits del archivo, ya que allí se encuentra información como las medidas de la misma.&lt;/p&gt;
&lt;p&gt;Pero con el tiempo notamos que algunas portadas no coincidían por equis motivos (luego se vio que esto era un 0.005% o menos de las imágenes)&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;La solución: comparar imágenes para encontrar coincidencias&lt;/h2&gt;
&lt;p&gt;Crear un script que descargue la portada de varias fuentes y las compare. La portada "escogida" además de tener el mayor tamaño, debe ser similar a alguna de las otras portadas, ya que es "altamente improbable" que el error se produzca entre varias fuentes.&lt;/p&gt;
&lt;p&gt;Y es por ello que escribo este post, resumiendo mi pequeña investigación sobre el tema.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Algoritmos para crear "hash" de imágenes&lt;/h2&gt;
&lt;p&gt;La comparación de las imágenes la podemos realizar de varias formas. En este caso, utilizaremos un hash&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, que sería un &lt;em&gt;string&lt;/em&gt; que actua a modo de resumen de cada imagen.&lt;/p&gt;
&lt;p&gt;Los hash creados se deben caracterizar por:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ser únicos para cada imagen&lt;/li&gt;
&lt;li&gt;Si una imagen sufre una ligera variación, el hash resultante debe variar mínimamente también.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para nuestras pruebas usaremos el cover del libro "Toda Mafalda":&lt;/p&gt;
&lt;p&gt;&lt;img alt="cover de toda mafalda" src="/pictures/hashing-images/toda_mafalda.jpg" title="toda mafalda - librera.pe"&gt;&lt;/p&gt;
&lt;p&gt;Existen algunos algoritmos usualmente empleados en estos temas y son:&lt;/p&gt;
&lt;h3&gt;Average Hash&lt;/h3&gt;
&lt;p&gt;El procedimiento del cálculo es el siguiente:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Generar una miniatura con tamaño fijo de la imagen: de este modo los cálculos serán más rápidos de realizar y los tamaños se homogenizan. Podemos trabajar con la medida de 8x8 con lo que tendremos 64 píxeles.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reducimos los colores transformando la imagen a escala de grises.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Realizamos el cálculo de la media de los 64 valores&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ahora revisamos si cada bit es mayor o menor a la media, con lo que tendremos 64 bits (0 o 1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Construimos el hash: transformamos la cadena de valores binarios a hexadecimal con lo que tendremos algo similar a: *ffffeff1e1e0661c *&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="proceso de average hash" src="/pictures/hashing-images/toda_mafalda_comparacion.jpg" title="average hash"&gt;&lt;/p&gt;
&lt;h3&gt;Perceptual Hash&lt;/h3&gt;
&lt;p&gt;Debido a su implementación, el algoritmo de &lt;strong&gt;Average Hash&lt;/strong&gt; es rápido y sencillo, pero también debido a su naturaleza, puede llegar a generar varios falsos positivos al momento de comparar imágenes similares cuando se realizan ajustes al "gama" de la imagen.&lt;/p&gt;
&lt;p&gt;El proceso es:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Escalamos la imagen, pero a diferencia del algoritmo "Average Hash" se recomienda utilizar un mayor tamaño como "32x32"&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reducimos los colores transformando la imagen a escala de grises.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Calculamos la DCT o [Transformada de cosenos discreta]&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reducimos la DCT: El cálculo anterior nos brinda una matrix de 32x32, pero solo tomaremos los 8x8 píxeles del lado superior izquierdo, lo que nos representa las menores frecuencias&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Calculamos el valor promedio (similar al algoritmo anterior)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Cálculamos el hash: establecemos los 64 bits en 0 o 1 según si cada valor supera o no el valor promedio calculado&lt;/li&gt;
&lt;li&gt;Construimos el hash: transformamos la cadena de valores binarios a hexadecimal con lo que tendremos algo similar a: &lt;em&gt;e6cd90620d947bce&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Otros algoritmos&lt;/h3&gt;
&lt;p&gt;Tenemos otros algoritmos que pueden ser revisados en las referencias al final del post como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block Hash&lt;/li&gt;
&lt;li&gt;Diference Hash&lt;/li&gt;
&lt;li&gt;Wavelet Hash&lt;/li&gt;
&lt;li&gt;Median Hash&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;¿Cómo comparamos los hashes de las imaǵenes?&lt;/h3&gt;
&lt;p&gt;Para nuestras pruebas, ajustamos el brillo, contraste y escala de la imagen original para analizar los hashes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="comparación de imágenes" src="/pictures/hashing-images/toda_mafalda_prueba.jpg" title="hashing de imágenes"&gt;&lt;/p&gt;
&lt;p&gt;Asumiendo que hemos calculado el hash escogido para nuestras imágenes, ¿cómo los comparamos?&lt;/p&gt;
&lt;p&gt;Utilizamos un método conocido como &lt;strong&gt;Distancia de Hamming&lt;/strong&gt;&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Para las imágenes de prueba, los phash (perceptual hash) son:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    e6cd90620d947bce
    e6ed90e609947b8c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La distancia de hamming calcula el número de bits que deben cambiar entre dos cadenas para ser idénticas.&lt;/p&gt;
&lt;p&gt;En este caso, el resultado es de "6", y al ser menor a 10 según algunos autores (&lt;em&gt;Dr. Neal Krawetz&lt;/em&gt;) se podría decir que las imágenes son similares.&lt;/p&gt;
&lt;h3&gt;Referencias&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html" target="_blank"&gt;Looks Like It - HackerFactor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fullstackml.com/wavelet-image-hash-in-python-3504fdd282b5" target="_blank"&gt;Wavelet image hash in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://content-blockchain.org/research/testing-different-image-hash-functions/" target="_blank"&gt;Testing different image hash functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pyimagesearch.com/2017/11/27/image-hashing-opencv-python/" target="_blank"&gt;Image hashing with OpenCV and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jenssegers.com/perceptual-image-hashes" target="_blank"&gt;Perceptual image hashes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://es.wikipedia.org/wiki/Funci%C3%B3n_hash" target="_blank"&gt;Función hash - Wikipedia&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://es.wikipedia.org/wiki/Transformada_de_coseno_discreta" target="_blank"&gt;Transformada de cosenos discreta&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="https://es.wikipedia.org/wiki/Distancia_de_Hamming" target="_blank"&gt;Distancia de Hamming&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="imagenes"/><category term="python"/></entry><entry><title>Proyectos personales del 2019</title><link href="https://alexanderae.com/proyectos-personales-del-2019.html" rel="alternate"/><published>2019-10-16T00:00:00-05:00</published><updated>2019-10-16T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2019-10-16:/proyectos-personales-del-2019.html</id><summary type="html">&lt;p&gt;Mis proyectos Personales&lt;/p&gt;
&lt;p&gt;Y para mi próximo truco, un post inusual.&lt;/p&gt;
&lt;p&gt;Este año tengo dos proyectos que avanzo en mi tiempo libre (o que intento al menos).&lt;/p&gt;
&lt;h2&gt;Librera&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://librera.pe" target="_blank"&gt;Librera&lt;/a&gt; es un &lt;strong&gt;marketplace de libros originales&lt;/strong&gt; el cual llevo en
conjunto con algunos compañeros. El objetivo es agrupar a diversos vendedores, ya sean librerías, vendedores
independientes u ocasionales y permitirles contar con un espacio en el que publicar sus libros.&lt;/p&gt;
&lt;p&gt;Uno de los problemas que intentamos resolver es la dificultad para …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Mis proyectos Personales&lt;/p&gt;
&lt;p&gt;Y para mi próximo truco, un post inusual.&lt;/p&gt;
&lt;p&gt;Este año tengo dos proyectos que avanzo en mi tiempo libre (o que intento al menos).&lt;/p&gt;
&lt;h2&gt;Librera&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://librera.pe" target="_blank"&gt;Librera&lt;/a&gt; es un &lt;strong&gt;marketplace de libros originales&lt;/strong&gt; el cual llevo en
conjunto con algunos compañeros. El objetivo es agrupar a diversos vendedores, ya sean librerías, vendedores
independientes u ocasionales y permitirles contar con un espacio en el que publicar sus libros.&lt;/p&gt;
&lt;p&gt;Uno de los problemas que intentamos resolver es la dificultad para obtener la mejor oferta respecto a algún libro en
específico. Con nuestra plataforma, al agrupar diversos proveedores, esperamos facilitar.&lt;/p&gt;
&lt;p&gt;El sistema utiliza tecnologías como: Python (Django), PostgresSQL (FTS), Redis, RQ, Selenium entre otros.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;img alt="captura de pantalla de librera.pe" src="/pictures/librera_libros.png" title="librera.pe"&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Además de ver el flujo normal de un ecommerce, lo que mayormente veo en este proyecto es el trabajo de realizar
&lt;strong&gt;Web Scraping&lt;/strong&gt; para obtener la información de los libros en base a su ISBN.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;tucole.pe&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://tucole.pe" target="_blank"&gt;tucole.pe&lt;/a&gt; es un agrupador de todos los colegios del Perú. Gracias a
información publicada por el &lt;em&gt;Ministerio de Educación&lt;/em&gt; tengo la idea de crear una web en la que los padres y los
propios alumnos sean capaces de consultar, opinar y comparar las entidades educativas a nivel nacional.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;img alt="captura de pantalla de tucole.pe" src="/pictures/tucole_colegios.png" title="tucole.pe"&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;En este caso, las tecnologías implicadas son: Python (Django), REST, GIS, Web Scraping.&lt;/p&gt;
&lt;p&gt;El frontend aún está en desarrollo, pero espero lograr que quede muy similar a la captura anteriormente indicada.&lt;/p&gt;</content><category term="blog"/><category term="proyectos"/><category term="informativo"/></entry><entry><title>Webscraping básico con Python - I</title><link href="https://alexanderae.com/webscraping-python.html" rel="alternate"/><published>2019-02-13T00:00:00-05:00</published><updated>2019-02-13T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2019-02-13:/webscraping-python.html</id><summary type="html">&lt;p&gt;¿Qué es el web scraping?&lt;/p&gt;
&lt;p&gt;Es una técnica utilizada mediante programas de software para extraer información de
sitios web. Usualmente, estos programas simulan la navegación de un humano en la
World Wide Web ya sea utilizando el protocolo HTTP manualmente, o incrustando un
navegador en una aplicación.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Entre algunos tipos de contenidos que podríamos necesitar extraer tenemos a:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;listados de noticias o empleos&lt;/li&gt;
&lt;li&gt;lista de productos, sus imágenes y precios&lt;/li&gt;
&lt;li&gt;relación de libros&lt;/li&gt;
&lt;li&gt;otros &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A modo de ejemplo, extraeré …&lt;/p&gt;</summary><content type="html">&lt;p&gt;¿Qué es el web scraping?&lt;/p&gt;
&lt;p&gt;Es una técnica utilizada mediante programas de software para extraer información de
sitios web. Usualmente, estos programas simulan la navegación de un humano en la
World Wide Web ya sea utilizando el protocolo HTTP manualmente, o incrustando un
navegador en una aplicación.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Entre algunos tipos de contenidos que podríamos necesitar extraer tenemos a:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;listados de noticias o empleos&lt;/li&gt;
&lt;li&gt;lista de productos, sus imágenes y precios&lt;/li&gt;
&lt;li&gt;relación de libros&lt;/li&gt;
&lt;li&gt;otros &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A modo de ejemplo, extraeré contenido de la primera página de
&lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Requerimientos&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;requests: para realizar la descarga del contenido del website&lt;/li&gt;
&lt;li&gt;beautifulsoup4: nos permite navegar en el contenido extraido de forma sencilla&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Los instalamos mediante pip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install requests beautifulsoup4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Obtenemos el contenido en formato html de la página a consultar&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://news.ycombinator.com/&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Utilizamos beautifulsoup para navegar a través del código html&lt;/h2&gt;
&lt;p&gt;Ahora que tenemos el contenido, podemos por ejemplo, intentar listar los títulos de
los &lt;em&gt;30 post&lt;/em&gt; que &lt;strong&gt;hacker news&lt;/strong&gt; nos muestra en la primera página.&lt;/p&gt;
&lt;p&gt;Notemos que podemos usar &lt;strong&gt;selectores css&lt;/strong&gt; con &lt;strong&gt;beautifulsoup&lt;/strong&gt;, por lo que
es recomendable conocer los mismos, como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;selectores por id: "#boton_enviar"&lt;/li&gt;
&lt;li&gt;selectores por clase: ".lista_de_items"&lt;/li&gt;
&lt;li&gt;selectores de tipo: "button"&lt;/li&gt;
&lt;li&gt;selectores de atributo: "[attr=value]"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Podemos darles un repaso en la documentación de &lt;a href="https://developer.mozilla.org/es/docs/Web/CSS/Selectores_CSS"&gt;Mozilla Developer Network&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Entonces, si utilizamos el inspector de elementos de chrome o firefox
en la web de Hacker News podemos observar algo como:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Selector de la tabla" src="/pictures/scraping_1.png"&gt;&lt;/p&gt;
&lt;p&gt;Donde vemos que el &lt;em&gt;contenedor de la lista de historias&lt;/em&gt; tiene la clase "itemlist".&lt;/p&gt;
&lt;p&gt;Y cada item de la tabla tiene la clase "storylink"&lt;/p&gt;
&lt;p&gt;&lt;img alt="Selector de la tabla" src="/pictures/scraping_2.png"&gt;&lt;/p&gt;
&lt;p&gt;Entonces nuestro código para iterar sobre cada elemento quedaría como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;bs4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://news.ycombinator.com/&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;html.parser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.itemlist .storylink&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;posts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Asi obtendremos el listado de títulos&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pos&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;
&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Bought&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;House&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Solar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Panels&lt;/span&gt;
&lt;span class="n"&gt;JP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Unveils&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USD&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Backed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cryptocurrency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;B2B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Payments&lt;/span&gt;
&lt;span class="n"&gt;Amazon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pulls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Planned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;New&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;York&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Campus&lt;/span&gt;
&lt;span class="n"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;don&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Distracted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fundamental&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Analysis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Extraer el enlace de cada post&lt;/h2&gt;
&lt;p&gt;Si quisiéramos avanzar un poco más podríamos obtener los enlaces
mediante el método "get":&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::&lt;span class="nv"&gt;python&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;posts&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;post&lt;/span&gt;.&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;post&lt;/span&gt;.&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notemos que podemos interactuar con el contenido de muchas maneras, las cuales
se pueden revisar en la &lt;a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/"&gt;documentación de beautifulsoup&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Otras consideraciones&lt;/h2&gt;
&lt;p&gt;Al realizar webscraping debemos tener en cuenta puntos como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;La legalidad: no siempre que algo esté publicado en internet significa
que podamos copiarlo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sobrecarga del servidor: hay que tener cuidado con realizar demasiadas
solicitudes al servidor al que queremos consultar, ya que no queremos realizar
un ataque DDOS e inhabilitar el website.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No siempre los contenidos están en el formato que deseamos, a veces tenemos
que utilizar expresiones regulares para extraer solo lo relevante.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Protección anti-webscraping: algunas webs tienen medidas como captchas,
o bloqueos por IP al exceder cierto número de solicitudes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Eso es todo.. por ahora&lt;/h2&gt;
&lt;p&gt;De momento sirve a modo de introducción. En otra oportunidad daré algún
ejemplo más complejo.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Definición según wikipedia&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="webscraping"/><category term="python"/></entry><entry><title>Instalar y configurar ModPageSpeed en nginx</title><link href="https://alexanderae.com/modpagespeed-nginx.html" rel="alternate"/><published>2018-09-18T00:00:00-05:00</published><updated>2018-09-18T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-09-18:/modpagespeed-nginx.html</id><summary type="html">&lt;p&gt;Hoy estuve investigando cómo mejorar el puntage de &lt;a href="https://developers.google.com/speed/pagespeed/insights/?hl=es"&gt;Google PageSpeed Insights&lt;/a&gt;, el cual es una página de google que mide la velocidad de carga de un website además de brindar algunas recomendaciones para mejorar dichas estadísticas.&lt;/p&gt;
&lt;p&gt;Investigando encontré &lt;a href="https://www.modpagespeed.com/"&gt;ModPageSpeeed&lt;/a&gt; un módulo para &lt;strong&gt;nginx&lt;/strong&gt; (y apache) que realiza varias de aquellas optimizaciones, algunas como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minificar el html&lt;/li&gt;
&lt;li&gt;Unificar archivos css o js&lt;/li&gt;
&lt;li&gt;Convertir formatos de imágenes (como de jpg a webp si el navegador lo soporta)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Instalación con nginx (ubuntu server …&lt;/h2&gt;</summary><content type="html">&lt;p&gt;Hoy estuve investigando cómo mejorar el puntage de &lt;a href="https://developers.google.com/speed/pagespeed/insights/?hl=es"&gt;Google PageSpeed Insights&lt;/a&gt;, el cual es una página de google que mide la velocidad de carga de un website además de brindar algunas recomendaciones para mejorar dichas estadísticas.&lt;/p&gt;
&lt;p&gt;Investigando encontré &lt;a href="https://www.modpagespeed.com/"&gt;ModPageSpeeed&lt;/a&gt; un módulo para &lt;strong&gt;nginx&lt;/strong&gt; (y apache) que realiza varias de aquellas optimizaciones, algunas como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minificar el html&lt;/li&gt;
&lt;li&gt;Unificar archivos css o js&lt;/li&gt;
&lt;li&gt;Convertir formatos de imágenes (como de jpg a webp si el navegador lo soporta)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Instalación con nginx (ubuntu server)&lt;/h2&gt;
&lt;p&gt;Activar el módulo requiere que nginx haya sido compilado con ModPageSpeed como extra, esto significa, que en caso ya contemos con nginx, deberemos reinstalarlo.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Nos aseguramos que tengamos instaladas las dependencias necesarias por nginx, esta referencia puede ayudarnos: &lt;a href="http://sharadchhetri.com/2018/05/15/install-and-compile-nginx-1-14-on-ubuntu-18-04-lts-server/"&gt;Install and compile nginx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Nota:&lt;/em&gt; la referencia es tan solo para las dependencias mas no para el proceso de compilar nginx&lt;/p&gt;
&lt;p&gt;Por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gcc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lipcre3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zlib1g&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libssl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libxml2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libxslt1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;libgd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libperl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libgd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libgeoip1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libgeoip&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unzip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;libjpeg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libfreetype6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libfreetype6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libtiff5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;libwebp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;liblcms2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;libxslt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Utilizamos la instalación automática recomendada por ModPageSpeed&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bash &amp;lt;(curl -f -L -sS https://ngxpagespeed.com/install) --nginx-version latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Durante el proceso es posible que se nos pregunte si deseamos agregar algun parámetro extra durante la configuración, en mi caso agregué:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;--with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;esto por ejemplo para contar con características como &lt;a href="https://www.xataka.com/servicios/http-2-asi-va-a-mejorar-la-velocidad-de-tu-navegacion-sin-que-tu-tengas-que-hacer-nada"&gt;http2&lt;/a&gt; o geoip.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Listo, ya lo tenemos instalado. En mi caso, en el directorio:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/usr/local/nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Configuración&lt;/h2&gt;
&lt;p&gt;Asumiendo que ya tengamos algún server block ejecutándose:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Crear un directorio como "/var/ngx_pagespeed_cache" y cambiar el propietario a nuestro usuario de nginx. Aquí se almacenan los archivos temporales que va generar &lt;strong&gt;ModPageSpeed&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Añadir una configuración similar a la &lt;a href="https://gist.github.com/annez/ded1d21999b790612dce"&gt;siguiente&lt;/a&gt; en cada server block donde necesitemos ModPageSpeed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pagespeed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pagespeed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FileCachePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ngx_pagespeed_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pagespeed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EnableFilters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;combine_css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;extend_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;rewrite_images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lazyload_images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;collapse_whitespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;inline_javascript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;inline_css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;local_storage_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;prioritize_critical_css&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pagespeed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EnableFilters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rewrite_css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;rewrite_javascript&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Ensure requests for pagespeed optimized resources go to the pagespeed handler&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# and no extraneous headers get set.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;add_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^/pagespeed_static/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^/ngx_pagespeed_beacon$&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Noten que podemos activar o desactivar los filtros a utilizar en la directiva &lt;em&gt;"pagespeed EnableFilters"&lt;/em&gt; según los necesitemos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Reiniciamos nginx y eso es todo&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Nota&lt;/em&gt;: cuando hemos instalado nginx, no se agregó al "path", ni tampoco se creó algun script para que auto-inicie, aquello queda a criterio del usuario.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Nota 2&lt;/em&gt;: siempre midan el cómo estaba antes y después, de ese modo se compara si realmente hubo alguna mejora o no (en mi caso la hubo).&lt;/p&gt;</content><category term="blog"/><category term="nginx"/><category term="optimizacion"/></entry><entry><title>Cómo hacer un form field de solo lectura en django</title><link href="https://alexanderae.com/django-form-field-readonly.html" rel="alternate"/><published>2018-06-22T00:00:00-05:00</published><updated>2018-06-22T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-06-22:/django-form-field-readonly.html</id><summary type="html">&lt;p&gt;Para hacer un &lt;em&gt;"Form Field"&lt;/em&gt; de solo lectura basta utilizar el atributo "attrs" de los widgets y agregar la propiedad &lt;em&gt;readonly&lt;/em&gt; como en el siguiente ejemplo:&lt;/p&gt;
&lt;p&gt;Antes:    &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CommentForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Después:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
class CommentForm(forms.Form):
    name = forms.CharField(
        widget=forms.TextInput(attrs={&amp;#39;readonly&amp;#39;:&amp;#39;readonly&amp;#39;})
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Observación:&lt;/h3&gt;
&lt;p&gt;Notemos que cuando un input es de solo lectura, el usuario no puede modificarlo pero se sigue enviando en el formulario.&lt;/p&gt;
&lt;p&gt;Para desactivar el envío …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Para hacer un &lt;em&gt;"Form Field"&lt;/em&gt; de solo lectura basta utilizar el atributo "attrs" de los widgets y agregar la propiedad &lt;em&gt;readonly&lt;/em&gt; como en el siguiente ejemplo:&lt;/p&gt;
&lt;p&gt;Antes:    &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CommentForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Después:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
class CommentForm(forms.Form):
    name = forms.CharField(
        widget=forms.TextInput(attrs={&amp;#39;readonly&amp;#39;:&amp;#39;readonly&amp;#39;})
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Observación:&lt;/h3&gt;
&lt;p&gt;Notemos que cuando un input es de solo lectura, el usuario no puede modificarlo pero se sigue enviando en el formulario.&lt;/p&gt;
&lt;p&gt;Para desactivar el envío totalmente se puede usar el atributo &lt;em&gt;disabled&lt;/em&gt; de los &lt;em&gt;form fields&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
class CommentForm(forms.Form):
    name = forms.CharField(disabled=True)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Referencias:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/2.1/ref/forms/widgets/#customizing-widget-instances"&gt;Django docs: customizing widget instances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/2.1/ref/forms/fields/#disabled"&gt;Django docs: core field arguments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;script type="application/ld+json"&gt;
{
    "@context": "http://schema.org",
    "@type": "Question",
    "name": "¿Cómo hacer un form field de solo lectura en django?",
    "text": "",
    "dateCreated": "2018-06-22",
    "acceptedAnswer": {
        "@type": "Answer",
        "text": "Para hacer un form field de solo lectura basta utilizar el atributo attrs de los widgets de django como el siguiente ejemplo.",
        "dateCreated": "2018-06-22",
        "author": {
            "@type": "Person",
            "name": "__alexander__"
        }
    }
}
&lt;/script&gt;</content><category term="blog"/><category term="django"/></entry><entry><title>Blogs de tecnología de grandes compañías</title><link href="https://alexanderae.com/blogs-de-tecnologia.html" rel="alternate"/><published>2018-06-20T00:00:00-05:00</published><updated>2018-06-20T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-06-20:/blogs-de-tecnologia.html</id><summary type="html">&lt;p&gt;A continuación algunos blogs de tecnología de grandes compañías que vale la pena seguir.&lt;/p&gt;
&lt;p&gt;Nota: en algunos casos destaco algún post/video&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/airbnb-engineering"&gt;Airbnb&lt;/a&gt;: &lt;a href="https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c"&gt;React Native at Airbnb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.algolia.com/"&gt;Algolia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.disqus.com/topic/engineering/"&gt;Disqus&lt;/a&gt;: &lt;a href="https://blog.disqus.com/scaling-django-to-8-billion-page-views"&gt;Scaling Django to 8 Billion Page Views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogs.dropbox.com/tech/"&gt;DropBox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.eventbrite.com/engineering/"&gt;Eventbrite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.facebook.com/posts/"&gt;Facebook&lt;/a&gt;: &lt;a href="https://code.facebook.com/posts/991252547593574/the-technology-behind-preview-photos/"&gt;The technology behind preview photos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.flickr.net/"&gt;Flickr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://about.gitlab.com/blog/"&gt;Gitlab&lt;/a&gt;: &lt;a href="https://about.gitlab.com/2017/02/10/postmortem-of-database-outage-of-january-31/"&gt;Postmortem of database outage of January 31&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/"&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://product.hubspot.com/blog/topic/engineering"&gt;Hubspot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://instagram-engineering.com/"&gt;Instagram&lt;/a&gt;: &lt;a href="https://www.youtube.com/watch?v=lx5WQjXLlq8"&gt;Django @ Instagram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/netflix-techblog"&gt;Netflix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@Pinterest_Engineering/"&gt;Pinterest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://engineering.quora.com/"&gt;Quora&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redditblog.com/topic/technology/"&gt;Reddit&lt;/a&gt;: &lt;a href="https://redditblog.com/2017/09/07/the-search-for-better-search-at-reddit/"&gt;The Search for Better Search at Reddit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eng.uber.com/"&gt;Uber&lt;/a&gt;: &lt;a href="https://eng.uber.com/mysql-migration/"&gt;Why Uber Engineering Switched from Postgres to MySQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.twitter.com/engineering/en_us.html#"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Otras …&lt;/h3&gt;</summary><content type="html">&lt;p&gt;A continuación algunos blogs de tecnología de grandes compañías que vale la pena seguir.&lt;/p&gt;
&lt;p&gt;Nota: en algunos casos destaco algún post/video&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/airbnb-engineering"&gt;Airbnb&lt;/a&gt;: &lt;a href="https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c"&gt;React Native at Airbnb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.algolia.com/"&gt;Algolia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.disqus.com/topic/engineering/"&gt;Disqus&lt;/a&gt;: &lt;a href="https://blog.disqus.com/scaling-django-to-8-billion-page-views"&gt;Scaling Django to 8 Billion Page Views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogs.dropbox.com/tech/"&gt;DropBox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.eventbrite.com/engineering/"&gt;Eventbrite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.facebook.com/posts/"&gt;Facebook&lt;/a&gt;: &lt;a href="https://code.facebook.com/posts/991252547593574/the-technology-behind-preview-photos/"&gt;The technology behind preview photos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.flickr.net/"&gt;Flickr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://about.gitlab.com/blog/"&gt;Gitlab&lt;/a&gt;: &lt;a href="https://about.gitlab.com/2017/02/10/postmortem-of-database-outage-of-january-31/"&gt;Postmortem of database outage of January 31&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/"&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://product.hubspot.com/blog/topic/engineering"&gt;Hubspot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://instagram-engineering.com/"&gt;Instagram&lt;/a&gt;: &lt;a href="https://www.youtube.com/watch?v=lx5WQjXLlq8"&gt;Django @ Instagram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/netflix-techblog"&gt;Netflix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@Pinterest_Engineering/"&gt;Pinterest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://engineering.quora.com/"&gt;Quora&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redditblog.com/topic/technology/"&gt;Reddit&lt;/a&gt;: &lt;a href="https://redditblog.com/2017/09/07/the-search-for-better-search-at-reddit/"&gt;The Search for Better Search at Reddit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eng.uber.com/"&gt;Uber&lt;/a&gt;: &lt;a href="https://eng.uber.com/mysql-migration/"&gt;Why Uber Engineering Switched from Postgres to MySQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.twitter.com/engineering/en_us.html#"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Otras referencias&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://www.quickcode.co/engineer-blogs"&gt;Engineering Blogs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sumodirjo/engineering-blogs"&gt;Github Engineering Blogs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=14163905"&gt;Ask HN: Which companies have the best blogs written by their engineering team?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content><category term="blog"/><category term="informativo"/></entry><entry><title>Implementar un sitemap en django</title><link href="https://alexanderae.com/django-implementar-sitemap.html" rel="alternate"/><published>2018-04-24T00:00:00-05:00</published><updated>2018-04-24T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-04-24:/django-implementar-sitemap.html</id><summary type="html">&lt;p&gt;Continuando con un proyecto personal, esta vez tuve que implementar un archivo &lt;strong&gt;sitemap.xml&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;¿Qué es el archivo sitemap.xml?&lt;/h3&gt;
&lt;p&gt;Es un archivo que los indexadores o "search engines" como google o bing utilizan para descubrir las páginas en nuestros websites. Ya que si bien los buscadores pueden "escanear" nuestra página sin ayuda hay casos en los que se recomienda incluir un sitemap (ver sección de &lt;em&gt;"Información adicional"&lt;/em&gt;).&lt;/p&gt;
&lt;h3&gt;¿Cómo implementar un sitemap en django?&lt;/h3&gt;
&lt;p&gt;Django cuenta con una aplicación oficial …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Continuando con un proyecto personal, esta vez tuve que implementar un archivo &lt;strong&gt;sitemap.xml&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;¿Qué es el archivo sitemap.xml?&lt;/h3&gt;
&lt;p&gt;Es un archivo que los indexadores o "search engines" como google o bing utilizan para descubrir las páginas en nuestros websites. Ya que si bien los buscadores pueden "escanear" nuestra página sin ayuda hay casos en los que se recomienda incluir un sitemap (ver sección de &lt;em&gt;"Información adicional"&lt;/em&gt;).&lt;/p&gt;
&lt;h3&gt;¿Cómo implementar un sitemap en django?&lt;/h3&gt;
&lt;p&gt;Django cuenta con una aplicación oficial para la creación de sitemaps. Siguiendo su documentación, los  pasos a seguir son:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Agregar 'django.contrib.sitemaps' a INSTALLED_APPS&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Asumiendo que tenemos una aplicación llamada "libros", podríamos crear el archivo &lt;em&gt;"libros/sitemaps.py"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;libros/models.py&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;django genera la url con el método &lt;em&gt;get_absolute_url&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
class Libro(models.Model):
    nombre = models.CharField(max_length=64)
    slug = models.SlugField(max_length=64, blank=True)

    def get_absolute_url(self):
        return reverse(&amp;#39;libros:libro&amp;#39;, kwargs={&amp;#39;slug&amp;#39;: self.slug})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;libros/sitemaps.py&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.contrib.sitemaps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sitemap&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Libro&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;LibroSitemap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sitemap&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;changefreq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;weekly&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3&lt;/strong&gt; Registramos el sitemap en el archivo &lt;em&gt;urls.py&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.contrib.sitemaps.views&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sitemap&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;libros.sitemaps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LibroSitemap&lt;/span&gt;

&lt;span class="n"&gt;sitemaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;libros&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LibroSitemap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sitemap.xml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sitemaps&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sitemaps&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;django.contrib.sitemaps.views.sitemap&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4&lt;/strong&gt; Visitamos la dirección &lt;strong&gt;/sitemap.xml&lt;/strong&gt; y eso sería todo.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Preguntas frecuentes&lt;/h3&gt;
&lt;p&gt;&lt;br&gt;
&lt;strong&gt;- ¿puedo tener un sitemap por cada aplicación?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si quisiéramos agregar otro sitemap, por ejemplo para nuestras páginas estáticas podríamos realizar algo como:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;pages/sitemaps.py&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.contrib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sitemaps&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PageSitemap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sitemaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sitemap&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;
    &lt;span class="n"&gt;changefreq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;daily&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;home&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;sobre-nosotros&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;contacto&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;urls.py&lt;/em&gt; : solo incluyo las secciones nuevas o que han cambiado&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="o"&gt;..&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pages.sitemaps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PageSitemap&lt;/span&gt;

&lt;span class="n"&gt;sitemaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;pages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageSitemap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;libros&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LibroSitemap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;- ¿Qué pasa si mi sitemap tiene más de 50 000 páginas?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Django permite crear un &lt;em&gt;index&lt;/em&gt; para nuestro sitemaps, se recomienda revisar la &lt;a href="!https://docs.djangoproject.com/en/2.0/ref/contrib/sitemaps/#creating-a-sitemap-index"&gt;documentación&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- Mi sitemap tiene muchas páginas, es muy lento de procesar ¿y ahora quién podrá defenderme?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;se puede utilizar el decorador &lt;em&gt;cache_page&lt;/em&gt; de django, ya que no tiene sentido realizar un cálculo muy frecuente, cuando nuestros contenidos se actualizan diariamente o semanalmente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- ¿cómo puedo utilizar https en mis sitemap?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;utiliza el parámetro &lt;em&gt;protocol&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Información adicional&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="!https://support.google.com/webmasters/answer/156184?hl=es"&gt;¿Necesito un sitemap?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="!https://docs.djangoproject.com/en/2.0/ref/contrib/sitemaps/"&gt;Documentación de django sobre la creación de un sitemap&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;script type="application/ld+json"&gt;
{
    "@context": "http://schema.org",
    "@type": "Question",
    "name": "¿Cómo implementar un sitemap en django?",
    "text": "",
    "dateCreated": "2018-04-24",
    "acceptedAnswer": {
        "@type": "Answer",
        "text": "Django cuenta con una aplicación oficial para la creación de sitemaps. Siguiendo su documentación, los  pasos a seguir son:",
        "dateCreated": "2018-04-24",
        "author": {
            "@type": "Person",
            "name": "__alexander__"
        }
    }
}
&lt;/script&gt;</content><category term="blog"/><category term="django"/><category term="seo"/></entry><entry><title>Descargar un archivo y grabarlo en un FileField</title><link href="https://alexanderae.com/django-descargar-archivo-grabar-model.html" rel="alternate"/><published>2018-04-19T00:00:00-05:00</published><updated>2018-04-19T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-04-19:/django-descargar-archivo-grabar-model.html</id><summary type="html">&lt;p&gt;Estoy desarrollando un proyecto en django y uno de los módulos debe consultar un API externo y descargar imágenes para almacenarlas en un modelo.&lt;/p&gt;
&lt;p&gt;Mi dilema fue:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;¿Cómo descargar un archivo y almacenarlo en un modelo de django?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;En resumen mi caso fue el siguiente:&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Me conectaba al API de un tercero para obtener cierta información. Yo enviaba un identificador del producto, &lt;em&gt;un código&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; El api me retornaba campos como: nombre e &lt;em&gt;imagen (una url)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; Para no …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Estoy desarrollando un proyecto en django y uno de los módulos debe consultar un API externo y descargar imágenes para almacenarlas en un modelo.&lt;/p&gt;
&lt;p&gt;Mi dilema fue:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;¿Cómo descargar un archivo y almacenarlo en un modelo de django?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;En resumen mi caso fue el siguiente:&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Me conectaba al API de un tercero para obtener cierta información. Yo enviaba un identificador del producto, &lt;em&gt;un código&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; El api me retornaba campos como: nombre e &lt;em&gt;imagen (una url)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; Para no realizar consultas sucesivas y aprovechar que la información del producto no cambia con el tiempo decidí crear un modelo para almacenar la información recibida en cada consulta.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Producto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;codigo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;imagen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;imagenes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; En mi aplicación tuve que crear una función que maneje la descarga del archivo y lo registre en el modelo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Producto&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;descarga_producto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;codigo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imagen_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Producto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;codigo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;codigo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imagen_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;lf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iter_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;lf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;producto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imagen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;filename&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;producto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En resumen lo que hace  el código anterior es:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Descarga la imagen con la librería requests, notando que activamos el flag "stream" el cual permite descargar el archivo por partes (en caso sea muy grande para no sobrecargar la memoria)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Conforme va descargando la imagen, la almacenamos en un &lt;strong&gt;NamedTemporaryFile&lt;/strong&gt; que es un archivo temporal que creamos y se almacena en el directorio de archivos temporales que utilicemos ('/tmp' generalmente)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Al terminar la descarga, le asignamos un nombre al archivo, lo grabamos y eso es todo.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; no estamos validando que el archivo sea efectivamente una imagen.&lt;/p&gt;
&lt;h4&gt;Referencia&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="!https://stackoverflow.com/a/16174886/1472750"&gt;Download a remote image and save it to a Django model
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;script type="application/ld+json"&gt;
{
    "@context": "http://schema.org",
    "@type": "Question",
    "name": "¿Cómo descargar un archivo y almacenarlo en un modelo de django?",
    "text": "",
    "dateCreated": "2018-04-19",
    "acceptedAnswer": {
        "@type": "Answer",
        "text": "Es posible manejar la descarga de un archivo con la librería requests y la clase NamedTemporaryFile mediante las siguientes instrucciones.",
        "dateCreated": "2018-04-19",
        "author": {
            "@type": "Person",
            "name": "__alexander__"
        }
    }
}
&lt;/script&gt;</content><category term="blog"/><category term="django"/></entry><entry><title>Aplicaciones que utilizo</title><link href="https://alexanderae.com/aplicaciones-que-utilizo.html" rel="alternate"/><published>2018-04-12T00:00:00-05:00</published><updated>2018-04-12T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-04-12:/aplicaciones-que-utilizo.html</id><summary type="html">&lt;p&gt;Hay quien me pregunta (1 o 2 personas, pero alguien a fin de cuentas) por las herramientas, aplicaciones o webs que utilizo regularmente, así que para evitarme enumerar todo otra vez escribo un post (en realidad porque me gusta escribir).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Las herramientas/apps/webs que utilizo con frecuencia son:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; &lt;a href="https://feedly.com"&gt;Feedly&lt;/a&gt;: es un lector de RSS que permite organizar las fuentes de las noticias y acceder desde un navegador web o alguna de sus apps.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; &lt;a href="https://evernote.com/intl/es/"&gt;Evernote&lt;/a&gt;: es una herramienta …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hay quien me pregunta (1 o 2 personas, pero alguien a fin de cuentas) por las herramientas, aplicaciones o webs que utilizo regularmente, así que para evitarme enumerar todo otra vez escribo un post (en realidad porque me gusta escribir).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Las herramientas/apps/webs que utilizo con frecuencia son:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; &lt;a href="https://feedly.com"&gt;Feedly&lt;/a&gt;: es un lector de RSS que permite organizar las fuentes de las noticias y acceder desde un navegador web o alguna de sus apps.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; &lt;a href="https://evernote.com/intl/es/"&gt;Evernote&lt;/a&gt;: es una herramienta para crear y organizar notas.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; &lt;a href="https://www.duolingo.com/"&gt;Duolingo&lt;/a&gt;: Aplicación para aprender idiomas mediante gamificación. En mi caso, para aprender inglés.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; &lt;a href="https://www.google.com/intl/es_ALL/drive/"&gt;Google Drive&lt;/a&gt;: Para almacenar documentos y editarlos en tiempo real con terceras personas.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;5.&lt;/em&gt; &lt;a href="https://twitter.com/__alexander_"&gt;Twitter&lt;/a&gt;: Lo utilizo como fuente de noticias principalmente.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;6.&lt;/em&gt; &lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;: Otra fuente de noticias, principalmente relacionadas al mundo de la tecnología.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;7.&lt;/em&gt; &lt;a href="https://medium.com/"&gt;Medium&lt;/a&gt;: Es un servicio de publicación de blogs, aunque en mi caso solo lo utilizo para leer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;8.&lt;/em&gt; &lt;a href="https://www.goodreads.com/"&gt;Goodreads&lt;/a&gt;: Aquí llevo un registro de libros que voy leyendo asi como obtengo algunas recomendaciones de qué libros Leer&lt;/p&gt;
&lt;p&gt;&lt;em&gt;9.&lt;/em&gt; &lt;a href="https://m.do.co/c/43e42bf3e09f"&gt;Digital Ocean&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;: Un proveedor de hosting (VPS) fácil de manejar. Es una buena alternativa frente a &lt;em&gt;Amazon Web Services&lt;/em&gt; ya que la interfaz es muy amigable.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;10.&lt;/em&gt; &lt;a href="https://www.namecheap.com/"&gt;Namecheap&lt;/a&gt;: Plataforma para comprar dominios y certificados SSL.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;11&lt;/em&gt; &lt;a href="https://github.com/alexander-ae"&gt;Github&lt;/a&gt; y &lt;a href="https://gitlab.com"&gt;Gitlab&lt;/a&gt;: Ambos para almacenar código, aunque últimamente prefiero gitlab, allí todo es "free".&lt;/p&gt;
&lt;p&gt;&lt;em&gt;12&lt;/em&gt; &lt;a href="https://www.jetbrains.com/pycharm/"&gt;Pycharm&lt;/a&gt;: El IDE que utlizo para programar en &lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;El enlace de digital ocean es un enlace referido, lo que implica que ambos obtendremos créditos gratis en caso se registre.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="informativo"/></entry><entry><title>Cómo integrar django y mailgun</title><link href="https://alexanderae.com/como-integrar-django-y-mailgun.html" rel="alternate"/><published>2018-04-08T10:26:00-05:00</published><updated>2018-04-08T10:26:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-04-08:/como-integrar-django-y-mailgun.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;Mailgun&lt;/strong&gt; es un servicio que permite enviar &lt;em&gt;emails transaccionales&lt;/em&gt; y emails masivos mediante la integración con su API o via SMTP.&lt;/p&gt;
&lt;p&gt;Entre las ventajas de mailgun tenemos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;permite monitorizar el estado del envío del email: ¿llegó al destino?, ¿fue rechazado?, entre otros.&lt;/li&gt;
&lt;li&gt;permite envíos masivos&lt;/li&gt;
&lt;li&gt;tiene integración con webhooks para eventos como: cuando el recipiente abre el mensaje, abre un enlace, sucede un problema al enviar el email entre otros.&lt;/li&gt;
&lt;li&gt;permite manejar varios dominios de forma separada para que uno …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Mailgun&lt;/strong&gt; es un servicio que permite enviar &lt;em&gt;emails transaccionales&lt;/em&gt; y emails masivos mediante la integración con su API o via SMTP.&lt;/p&gt;
&lt;p&gt;Entre las ventajas de mailgun tenemos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;permite monitorizar el estado del envío del email: ¿llegó al destino?, ¿fue rechazado?, entre otros.&lt;/li&gt;
&lt;li&gt;permite envíos masivos&lt;/li&gt;
&lt;li&gt;tiene integración con webhooks para eventos como: cuando el recipiente abre el mensaje, abre un enlace, sucede un problema al enviar el email entre otros.&lt;/li&gt;
&lt;li&gt;permite manejar varios dominios de forma separada para que uno no afecte la reputación de los demás en caso de algún problema de spam&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Los pasos para realizar la integración son:&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Registrar una cuenta en &lt;a href="https://www.mailgun.com"&gt;www.mailgun.com&lt;/a&gt; notando que cada mes tendremos para enviar hasta 10 000 emails gratuitos.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Registro en mailgun" src="pictures/mailgun-registro.png" title="Registro en mailgun"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; Luego en la opción &lt;em&gt;dominios&lt;/em&gt; debemos agregar el dominio que enviará los correos.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Agregar dominio en mailgun" src="pictures/mailgun-dominio.png" title="Dominio en mailgun"&gt;&lt;/p&gt;
&lt;p&gt;Observemos que nos recomiendan usar un subdominio (el detalle se puede revisar en sus preguntas frecuentes).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; Ahora que tenemos un dominio, debemos realizar algunas validaciones y configuraciones. Para esto, requerimos acceso al panel que gestiona los registros DNS del dominio.&lt;/p&gt;
&lt;p&gt;La validación requerida es:
&lt;img alt="Validación de un dominio en mailgun" src="pictures/mailgun-validar-dominio.png" title="Validación de un dominio en mailgun"&gt;&lt;/p&gt;
&lt;p&gt;En el caso de digital ocean lo editamos en la sección &lt;em&gt;"Networking"&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Edición de registros DNS en digital ocean" src="pictures/digital-ocean-dns.png" title="Edición de registros DNS en digital ocean"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; Esperamos un tiempo prudencial (en el caso de digital ocean fueron 3 minutos, pero puede tomar horas según el proveedor) y presionamos el botón para validar los cambios.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Credenciales de mailgun" src="pictures/mailgun-dominio-validado.png" title="Credenciales de mailgun"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;5.&lt;/em&gt; El último paso es editar la configuración de envío de correos de django. En nuestro &lt;em&gt;settings.py&lt;/em&gt; cambiamos los valores a algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_HOST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;smtp.mailgun.org&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_HOST_USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;postmaster@mailgun.librera.pe&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_HOST_PASSWORD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;not-my-real-password&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;postmaster@mailgun.librera.pe&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_PORT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_USE_TLS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y listo, eso es todo, ya podemos enviar correos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; se recomienda que las credenciales esten fuera del archivo settings como en variables de entorno o algún otro método.&lt;/p&gt;
&lt;p&gt;En caso se desee integrar características avanzadas de mailgun como 
webhooks, revisen su documentación, allí hay ejemplos de cómo manejarlo.&lt;/p&gt;
&lt;h3&gt;Más información&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.40defiebre.com/diferencias-email-marketing-vs-email-transaccional/"&gt;Diferencias entre email marketing vs email transaccional&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://documentation.mailgun.com/en/latest/"&gt;Documentación de mailgun&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://simpleisbetterthancomplex.com/tutorial/2017/05/27/how-to-configure-mailgun-to-send-emails-in-a-django-app.html"&gt;How to configure mailgun to send emails in a django app&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/2.0/topics/email/"&gt;Djando docs: sending email&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;script type="application/ld+json"&gt;
{
    "@context": "http://schema.org",
    "@type": "Question",
    "name": "¿Cómo integrar django y mailgun?",
    "text": "",
    "dateCreated": "2018-04-08",
    "acceptedAnswer": {
        "@type": "Answer",
        "text": "La integración consiste principalment en: Apuntar el dominio, validarlo y agregar la configuración del envío de emails de django.",
        "dateCreated": "2018-04-08",
        "author": {
            "@type": "Person",
            "name": "__alexander__"
        }
    }
}
&lt;/script&gt;</content><category term="blog"/><category term="django"/></entry><entry><title>Análisis de legibilidad de textos</title><link href="https://alexanderae.com/analisis-de-legibilidad-de-textos.html" rel="alternate"/><published>2018-02-14T00:00:00-05:00</published><updated>2018-02-14T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-02-14:/analisis-de-legibilidad-de-textos.html</id><summary type="html">&lt;p&gt;Al parecer en el inglés existen dos palabras que hacen referencia a la facilidad para leer un texto&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;legibility: legibilidad tipográfica, que se refiere a la tipografía (aspecto visual del texto)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Readability"&gt;readability&lt;/a&gt;: legibilidad lingüistica, que se refiere a la estructura lingüistica como el número de palabras que se usan y la forma en las que se combinan.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Este artículo trata sobre la fórmula necesaria para calcular la &lt;em&gt;legibilidad lingüistica&lt;/em&gt; y su implementación en python&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Medir la &lt;em&gt;legibilidad&lt;/em&gt; de …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Al parecer en el inglés existen dos palabras que hacen referencia a la facilidad para leer un texto&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;legibility: legibilidad tipográfica, que se refiere a la tipografía (aspecto visual del texto)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Readability"&gt;readability&lt;/a&gt;: legibilidad lingüistica, que se refiere a la estructura lingüistica como el número de palabras que se usan y la forma en las que se combinan.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Este artículo trata sobre la fórmula necesaria para calcular la &lt;em&gt;legibilidad lingüistica&lt;/em&gt; y su implementación en python&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Medir la &lt;em&gt;legibilidad&lt;/em&gt; de textos en español puede ser requerido para:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;evaluar la edad necesaria para comprender un texto&lt;/li&gt;
&lt;li&gt;calcular qué tan sencillo de entender será para nuestro público objetivo&lt;/li&gt;
&lt;li&gt;calcular tiempos de lectura&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Algunas de las fórmulas aplicables al español son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Escala INFLESZ (2008)&lt;/li&gt;
&lt;li&gt;Legibilidad µ (2006)&lt;/li&gt;
&lt;li&gt;Nivel de perspicuidad - Szigriszt-Pazos (1993)&lt;/li&gt;
&lt;li&gt;Nivel de Comprensibilidad - Crawford (1989)&lt;/li&gt;
&lt;li&gt;Nivel de comprensibilidad - Gutiérrez de Polini (1972)&lt;/li&gt;
&lt;li&gt;Escala de lecturabilidad - Fernández Huerta (1959)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En este caso solo calcularemos la escala &lt;em&gt;INFLESZ&lt;/em&gt;&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;, la cual requiere obtener el índice &lt;em&gt;Flesh-Szigriszt&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Índice FLESCH-SZIGRISZT = 206,835 – (62,3 x S/P ) - P/F
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Donde P es el número de palabras del texto, S el número de
sílabas y F es el número de frases. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Escala Inflesz:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Puntos&lt;/th&gt;
&lt;th&gt;Grado&lt;/th&gt;
&lt;th&gt;Tipo de Publicación&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 40&lt;/td&gt;
&lt;td&gt;Muy difícil&lt;/td&gt;
&lt;td&gt;Universitario, Científico&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 - 55&lt;/td&gt;
&lt;td&gt;Algo difícil&lt;/td&gt;
&lt;td&gt;Bachillerato, Divulgación, Científico, Prensa especializada&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55 - 65&lt;/td&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;ESO, Prensa general, Prensa deportiva&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;65 - 80&lt;/td&gt;
&lt;td&gt;Bastante fácil&lt;/td&gt;
&lt;td&gt;Educación primaria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; 80&lt;/td&gt;
&lt;td&gt;Muy fácil&lt;/td&gt;
&lt;td&gt;Educación Primaria, Cómic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Para llegar a lo anterior debemos dividir el problema, por lo cual necesitaremos:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; el número de palabras de un texto: lo podemos obtener de la siguiente manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
def count_words(text):
    return len(text.split())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; el número de sílabas de una palabra: este punto es un tanto más complejo porque requiere utilizar las reglas del español para dividir sílabas, para nuestro caso la referencia es: &lt;a href="https://github.com/mabodo/sibilizador"&gt;https://github.com/mabodo/sibilizador&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; el número de frases de un texto: utilizamos una expresión regular para agrupar los separadores como puntos, comas, dos puntos, entre otros. Luego utilizamos el método split de un objeto regex para separar las frases y las contamos.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
def count_sentences(text):
    text = text.replace(&amp;quot;\n&amp;quot;, &amp;quot;&amp;quot;)
    sentence_end = re.compile(&amp;#39;[.:;!?\)\()]&amp;#39;)
    sencences = sentence_end.split(text)
    return len(list(filter(None, sencences)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y con esto, ya solo quedaría unir los bloques para lograr el cálculo deseado. Se puede ver el script original en python en &lt;a href="https://github.com/amunozf/legibilidad"&gt;github&lt;/a&gt; o la versión que empaqueté para &lt;a href="https://gitlab.com/__alexander__/legibilidad"&gt;pypi&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Más información&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/__alexander__/legibilidad"&gt;Código del paquete en pypi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://legible.es/"&gt;legible.es&lt;/a&gt;: web con información sobre análisis de legibilidad de textos en español&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nngroup.com/articles/legibility-readability-comprehension/"&gt;Legibility, Readability, and Comprehension: Making Users Read Your Words&lt;/a&gt;: análisis del impacto de la legibilidad en la experiencia de usuario&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://www.proz.com/kudoz/english_to_spanish/psychology/3583621-legibility_and_readability.html"&gt;Diferencia entre readability y legibility&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Me baso en el script publicado por Alejandro Muñoz en su blog: &lt;a href="https://legible.es/"&gt;legible.es&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Inés Barrio, 2007, &lt;a href="https://legibilidad.blogspot.pe/"&gt;Legibilidad y salud&lt;/a&gt;. Tésis doctoral. pag 289&amp;#160;&lt;a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="analisis-de-textos"/><category term="accesibilidad"/><category term="ux"/></entry><entry><title>Herramientas para extraer texto de archivos PDF</title><link href="https://alexanderae.com/extraccion-de-texto-desde-pdf.html" rel="alternate"/><published>2018-02-12T00:00:00-05:00</published><updated>2018-02-12T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2018-02-12:/extraccion-de-texto-desde-pdf.html</id><summary type="html">&lt;p&gt;Algunos casos de uso en los que se requiera extraer texto de archivos pdf pueden ser:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ejecutar análisis de curriculums vitae (hojas de vida)&lt;/li&gt;
&lt;li&gt;para permitir la búsqueda por contenidos en un archivo&lt;/li&gt;
&lt;li&gt;obtención de los datos de contacto de empresas desde boletas o facturas de pago&lt;/li&gt;
&lt;li&gt;conversión a un formato más amigable para su presentación en la web (html) entre otros&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A continuación, 3 herramientas que se pueden utilizar para extraer texto de archivos PDF:&lt;/p&gt;
&lt;h2&gt;1. PDFMiner (python):&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Enlace …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Algunos casos de uso en los que se requiera extraer texto de archivos pdf pueden ser:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ejecutar análisis de curriculums vitae (hojas de vida)&lt;/li&gt;
&lt;li&gt;para permitir la búsqueda por contenidos en un archivo&lt;/li&gt;
&lt;li&gt;obtención de los datos de contacto de empresas desde boletas o facturas de pago&lt;/li&gt;
&lt;li&gt;conversión a un formato más amigable para su presentación en la web (html) entre otros&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A continuación, 3 herramientas que se pueden utilizar para extraer texto de archivos PDF:&lt;/p&gt;
&lt;h2&gt;1. PDFMiner (python):&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Enlace&lt;/strong&gt;: &lt;a href="https://github.com/pdfminer/pdfminer.six"&gt;pdfminer-six&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;** Instalación **:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip install pdfminer.six&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;** Uso **:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pdf2txt.py archivo.pdf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;** Características resaltantes **:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;permite escoger el formato de salida: txt, xml, html&lt;/li&gt;
&lt;li&gt;selección del rango de páginas a escanear&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. Apache Tika (java):&lt;/h2&gt;
&lt;p&gt;Apache tika es una herramienta que extrae metadatos y texto desde varios formatos como: doc, ppt, xls, pdf entre otros&lt;/p&gt;
&lt;p&gt;** Enlace **: &lt;a href="https://tika.apache.org/"&gt;apache-tika&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;** Instalación y uso **:&lt;/p&gt;
&lt;p&gt;Se requiere contar con java instalado, descargar tika y ejecutarlo de forma similar a:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java -jar tika-server.jar&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;** Características **&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reconoce múltiples formatos de entrada&lt;/li&gt;
&lt;li&gt;permite escoger el formato de salida: txt, xml, html&lt;/li&gt;
&lt;li&gt;extracción de metadata: cantidad de páginas, versión de pdf, extensión del documento, número de páginas entre otros.&lt;/li&gt;
&lt;li&gt;cuenta con una herramienta gráfica: &lt;em&gt;tika-app.jar&lt;/em&gt; que se puede utilizar para realizar pruebas de forma rápida&lt;/li&gt;
&lt;li&gt;existe un wrapper para utilizarlo con python: &lt;a href="https://github.com/chrismattmann/tika-python"&gt;tika-python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. pdftotxt (linux)&lt;/h2&gt;
&lt;p&gt;Herramienta para extraer pdf incluida por defecto en varias distribuciones linux.&lt;/p&gt;
&lt;p&gt;** Documentación **: &lt;a href="https://www.mankier.com/1/pdftotext"&gt;pdftotext&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;** Uso **&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pdftotext documento.pdf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Con lo anterior, se crea el archivo: &lt;em&gt;documento.txt&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;** Características **&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;selección del rango de páginas a escanear&lt;/li&gt;
&lt;li&gt;es la más sencilla de utilizar ya que viene preinstalada&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Comentarios finales&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;En general, quien me ha brindado mejores resultados para pdf's complejos ha sido &lt;strong&gt;apache tika&lt;/strong&gt; y pdftotext para documentos sencillos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Si lo que se busca es extraer imágenes de un PDF, en mi caso &lt;a href="https://pdfbox.apache.org/"&gt;PDFBox&lt;/a&gt; me ha dado buenos resultados también.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Referencia:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tools for Extracting Data and Text from PDFs - A Review: &lt;a href="http://okfnlabs.org/blog/2016/04/19/pdf-tools-extract-text-and-data-from-pdfs.html"&gt;tools-for-extracting-data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="pdf"/></entry><entry><title>Autodeploy en Gitlab</title><link href="https://alexanderae.com/autodeploy-en-gitlab.html" rel="alternate"/><published>2017-07-09T00:00:00-05:00</published><updated>2017-07-09T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2017-07-09:/autodeploy-en-gitlab.html</id><summary type="html">&lt;p&gt;En la industria del software siempre se busca automatizar las tareas repetitivas para dedicarle más tiempo a las que son realmente importantes. Una de estas tareas repetitivas es el llamado &lt;strong&gt;deploy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;El &lt;em&gt;deploy&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; consiste en el conjunto de actividades que realizamos para que el software sea usable por el público.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Como ejemplo, listo las tareas que realizo para actualizar un proyecto que tiene html's estáticos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Publicamos de un nuevo cambio en el repositorio, gitlab para nuestro caso (push)&lt;/li&gt;
&lt;li&gt;Nos …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;En la industria del software siempre se busca automatizar las tareas repetitivas para dedicarle más tiempo a las que son realmente importantes. Una de estas tareas repetitivas es el llamado &lt;strong&gt;deploy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;El &lt;em&gt;deploy&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; consiste en el conjunto de actividades que realizamos para que el software sea usable por el público.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Como ejemplo, listo las tareas que realizo para actualizar un proyecto que tiene html's estáticos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Publicamos de un nuevo cambio en el repositorio, gitlab para nuestro caso (push)&lt;/li&gt;
&lt;li&gt;Nos conectamos al servidor&lt;/li&gt;
&lt;li&gt;Actualizamos el código en el servidor (pull)&lt;/li&gt;
&lt;li&gt;Copiamos los archivos del proyecto al directorio en que el servidor web (nginx) trabaja.&lt;/li&gt;
&lt;li&gt;Visualizamos los cambios en el website mediante el navegador&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;¿Cómo reducir el trabajo manual?&lt;/h2&gt;
&lt;p&gt;Encontré una aplicación opensource llamada &lt;a href="https://github.com/olipo186/Git-Auto-Deploy"&gt;Git-Auto-Deploy&lt;/a&gt; que actua como un pequeño servidor cuya función es escuchar los webhooks&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; que se lanzan cada vez que publicamos un cambio en el repositorio.&lt;/p&gt;
&lt;p&gt;Los pasos a seguir para tenerlo configurado en nuestro servidor son:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Crear una llave ssh pública en el servidor: &lt;code&gt;ssh-keygen -t rsa&lt;/code&gt; y luego la copiamos &lt;code&gt;cat ~/.ssh/id_rsa.pub&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; Agregamos la clave como &lt;em&gt;deployment key&lt;/em&gt; en el panel del repositorio de gitlab:&lt;/p&gt;
&lt;p&gt;&lt;img alt="gitlab deployment" src="/pictures/gitlab-deployment-key.png" title="gitlab deployment keys"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; En el servidor en el que se va a realizar el auto-deploy clonamos la aplicación: &lt;code&gt;git clone https://github.com/olipo186/Git-Auto-Deploy.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; En el directorio de &lt;em&gt;Git-Auto-Deploy&lt;/em&gt;, duplicamos el archivo de configuración de ejemplo: &lt;code&gt;cd GitDeploy; cp config.json.sample config.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;5.&lt;/em&gt; Editamos el archivo de configuración, el mio quedó similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http-port&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;log-file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/gitautodeploy.log&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pid-file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.gitautodeploy.pid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;log-level&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;INFO&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;repositories&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git@gitlab.com:test/test.git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;branch&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;master&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;remote&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/www/test/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deploy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo deploying;cp -r /var/www/test/* /var/www/html/.;echo end&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;secret-token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my-secret-gitlab-token&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;6.&lt;/em&gt; Ejecutamos la aplicación en segundo plano: &lt;code&gt;python2.7 GitAutoDeploy.py -d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;7.&lt;/em&gt; Registramos el webhook en gitlab: Gitlab: Settings : Integrations&lt;/p&gt;
&lt;p&gt;&lt;img alt="gitlab webhooks" src="/pictures/gitlab-webhooks.png" title="gitlab webhooks"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;8.&lt;/em&gt; Esto es todo, ahora cada que realicemos un push, la secuencia de comandos que hayamos registrado se debe ejecutar.&lt;/p&gt;
&lt;h2&gt;Referencia:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Autodeploy your gitlab projects: &lt;a href="https://johnflynn.me/autodeploy-your-gitlab-projects/"&gt;autodeploy-gitlab-post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Sofware deployment: [https://en.wikipedia.org/wiki/Software_deployment][https://en.wikipedia.org/wiki/Software_deployment]&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Webhooks: [https://es.wikipedia.org/wiki/Webhook][https://es.wikipedia.org/wiki/Webhook]&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="git"/><category term="sysadmin"/></entry><entry><title>Optimizando un sitio web - 1ra parte</title><link href="https://alexanderae.com/optimizando-un-website-1.html" rel="alternate"/><published>2017-05-01T00:00:00-05:00</published><updated>2017-05-01T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2017-05-01:/optimizando-un-website-1.html</id><summary type="html">&lt;p&gt;Hace buen tiempo quería escribir sobre este tema, lo usual, de registro para mí y en el blog por si le ayuda a alguien más.&lt;/p&gt;
&lt;p&gt;Un sitio web se puede optimizar en múltiples aspectos, pero para comenzar me voy a centrar en el lado del frontend y no sin antes indicar que estoy utilizando recomendaciones de múltiples recursos entre los que destacan &lt;a href="https://developers.google.com/speed/pagespeed/insights/"&gt;Google PageSpeed Insights&lt;/a&gt; y &lt;a href="https://tools.pingdom.com/"&gt;Pingdom Website Speed Test&lt;/a&gt; y otros websites a los que voy a citar.&lt;/p&gt;
&lt;h2&gt;¿Por …&lt;/h2&gt;</summary><content type="html">&lt;p&gt;Hace buen tiempo quería escribir sobre este tema, lo usual, de registro para mí y en el blog por si le ayuda a alguien más.&lt;/p&gt;
&lt;p&gt;Un sitio web se puede optimizar en múltiples aspectos, pero para comenzar me voy a centrar en el lado del frontend y no sin antes indicar que estoy utilizando recomendaciones de múltiples recursos entre los que destacan &lt;a href="https://developers.google.com/speed/pagespeed/insights/"&gt;Google PageSpeed Insights&lt;/a&gt; y &lt;a href="https://tools.pingdom.com/"&gt;Pingdom Website Speed Test&lt;/a&gt; y otros websites a los que voy a citar.&lt;/p&gt;
&lt;h2&gt;¿Por qué optimizar?&lt;/h2&gt;
&lt;p&gt;Tal vez ganar unos kilobytes o milisegundos por cada recomendación a seguir no parezca mucho pero en su conjunto, importan.&lt;/p&gt;
&lt;p&gt;Cuántas veces me ha pasado que abandono un blog porque el tiempo de carga es lento ya sea por que estoy con una red limitada como la del celular o por el peso del propio website.&lt;/p&gt;
&lt;p&gt;Y así como me pasa, &lt;a href="https://blog.kissmetrics.com/loading-time/"&gt;estudios&lt;/a&gt; indican que un visitante tiende a abandonar los sitios que identifica como lentos lo cuál se traduce en menos conversiones.&lt;/p&gt;
&lt;p&gt;Entonces, ¿por qué?, diría que para brindar una mejor experiencia al usuario, porque ese usuario, podemos ser nosotros mismos en otro momento.&lt;/p&gt;
&lt;p&gt;Claro, hay otras ventajas como un mejor posicionamiento en los buscadores, mejorar las conversiones o tan solo utilizar menos recursos del lado del servidor&lt;/p&gt;
&lt;h2&gt;Mejoras en el frontend&lt;/h2&gt;
&lt;h3&gt;Minificar HTML, CSS y JS&lt;/h3&gt;
&lt;p&gt;Los espacios en blanco, saltos de línea y los comentarios pueden ocupar valiosos bytes.&lt;/p&gt;
&lt;p&gt;Para removerlos pueden usar herramientas de postprocesamiento incorporadas en el framework o cms utilizado (plugins de wordpress) o con herramientas como gulp, grunt entre otros.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/speed/docs/insights/MinifyResources?hl=es-419"&gt;Google web developers: minificar recursos&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Optimizar la entrega de webfonts&lt;/h3&gt;
&lt;p&gt;Al utilizar una 'web font' nos puede pasar que obtengamos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;flash of invisible content: no vemos el texto hasta no descargar la tipografía&lt;/li&gt;
&lt;li&gt;flash of unstyled contend: vemos el texto pero este cambia su apariencia cuando al tipografía termina de descargar&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Como muchos, recomiendo optimizarlo para el segundo caso, lo que se llama &lt;em&gt;progressive enhancement&lt;/em&gt; mediante técnicas de lazy loading.&lt;/p&gt;
&lt;p&gt;Existen otras formar de optimizar la entrega de tipografías como reducir el número de las mismas en la web o &lt;a href="https://developers.google.com/fonts/docs/getting_started#specifying_script_subsets"&gt;entregar solo el subconjunto de caracteres&lt;/a&gt; que utilizaremos.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization"&gt;Google web font optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://meowni.ca/posts/web-fonts/"&gt;Web fonts, boy, I don't know&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Migrar a un stack de 'safe web fonts' o 'system fonts'&lt;/h3&gt;
&lt;p&gt;ya no estamos en el pasado cuando las tipografías por defecto de los sistemas operativos no eran 'agradables', ahora tenemos algunas opciones dentro de cada S.O. como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;windows: calibri, tahoma, consolas&lt;/li&gt;
&lt;li&gt;macOS, iOS: helvetica, san francisco&lt;/li&gt;
&lt;li&gt;android: roboto, droid sans&lt;/li&gt;
&lt;li&gt;linux: ubuntu, oxygen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;entonces, si podemos usar una fuente que viene instalada en el sistema, con la que el usuario está familiarizado y no tiene que descargar ningún extra ¿por qué no?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://woorkup.com/system-font"&gt;Moving to a System Font Stack in 2017&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://bitsofco.de/the-new-system-font-stack/"&gt;The new system font stack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Habilitar la compresión de recursos&lt;/h3&gt;
&lt;p&gt;Además de minificar un archivo, también podemos comprimirlo mediante gzip. Esto generalmente se logra agregando unas cuantas líneas a la configuración de nuestro sevidor ya sea apache, nginx u otro.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/speed/docs/insights/EnableCompression"&gt;Google: enable compression&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer"&gt;Google: optimize encoding and transfer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Optimizar imágenes&lt;/h3&gt;
&lt;p&gt;Las imágenes se pueden optimizar de diferentes maneras:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mediante herramientas como: mozjpeg, optipng o gifsicle que recodifican la imagen, disminuyen la paleta de colores o una combinación de ambas.&lt;/li&gt;
&lt;li&gt;reemplazando la imagen original por otro formato, como puede ser un png por un archivo svg o un png por un jpeg&lt;/li&gt;
&lt;li&gt;utilizando webp&lt;/li&gt;
&lt;li&gt;reemplazando la imagen por un efecto css&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Algunos recursos&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization"&gt;Google image optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://alexanderae.com/optimizacion-de-imagenes-archivos-png.html"&gt;Optimización de archivos PNG&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tinypng.com/"&gt;TinyPNG&lt;/a&gt;: herramienta online para comprimir jpg y png&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/thumbor/thumbor"&gt;Thumbor&lt;/a&gt;: servidor de imágenes opensource&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Reduce el tiempo de respuesta del servidor&lt;/h3&gt;
&lt;p&gt;Si bien, esta recomendación brindada por google tiene que ver con el backend, la anoto aquí por si alguien se pregunta el qué es.&lt;/p&gt;
&lt;p&gt;Se refiere al tiempo que toma el servidor en procesar nuestra solicitud. Este tiempo puede verse afectado por factores como consultas SQL pesadas, renderizado de html, cálculos complejos entre otros.&lt;/p&gt;
&lt;h3&gt;Especificar la caché del navegador&lt;/h3&gt;
&lt;p&gt;Mediante el uso de algunas cabeceras, el navegador nos permite indicarle el tiempo de expiración de un recurso.&lt;/p&gt;
&lt;p&gt;Por ejemplo, la siguiente cabecera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;expires&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;Tue&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GMT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Quiere decir que el recurso expira en la fecha indicada y hasta entonces no es necesario que el navegador lo vuelva a descargar al visitar nuevamente la página.&lt;/p&gt;
&lt;p&gt;Esto se activa desde la configuración del servidor: apache o nginx&lt;/p&gt;
&lt;p&gt;Por ejemplo, para agregar recursos estáticos a la cache de nginx podemos agregar algo similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
    expires 7d;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/speed/docs/insights/LeverageBrowserCaching"&gt;Google Leverage Browser Caching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Elimina descargas innecesarias&lt;/h3&gt;
&lt;p&gt;Un buen ejemplo sería cuando el administrador de un website decide agregar un slide o carrousel con muchas imágenes sin preguntarse siquiera si el visitante se detendrá a revisar cada una de ellas.&lt;/p&gt;
&lt;p&gt;Debemos evaluar si los recursos que utilizamos en una web son realmente utilizados, si compensa el costo de la descarga o tal vez podríamos prescindir de algunos.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/eliminate-downloads"&gt;Google: eliminate downloads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Notas finales&lt;/h2&gt;
&lt;p&gt;Algunas herramientas que pueden utilizar para medir el estado de un website son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/speed/pagespeed/insights/"&gt;Google Page Speed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.pingdom.com/"&gt;Pingdom Tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Como observación final, indico que voy a tratar de mantener este artículo actualizado e iré añadiendo más recomendaciones con el tiempo.&lt;/p&gt;</content><category term="blog"/><category term="optimizacion"/></entry><entry><title>Buenas prácticas para escalar Django</title><link href="https://alexanderae.com/django-best-practices.html" rel="alternate"/><published>2016-07-16T00:00:00-05:00</published><updated>2016-07-16T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2016-07-16:/django-best-practices.html</id><summary type="html">&lt;p&gt;Mi resumen de una de las conferencias brindadas en la DjangoCon Europa 2016. Anton Pirker nos introduce en la historia (ficticia) sobre el cómo aparece un problema para escalar un nuevo proyecto con django en la vida real de un desarrollador que no tiene experiencia en la administración de servidores.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Mi resumen de una de las conferencias brindadas en la DjangoCon Europa 2016. &lt;a href="http://www.anton-pirker.at/"&gt;Anton Pirker&lt;/a&gt; nos introduce en la historia (ficticia) sobre el cómo aparece un problema para escalar un nuevo proyecto con django en la &lt;em&gt;vida real&lt;/em&gt; de un desarrollador que no tiene experiencia en la administración de servidores.&lt;/p&gt;
&lt;p&gt;Para quien cuente con tiempo, le recomiendo que revise el video original, el cual está al al final de esta entrada (25 minutos). Pero para quien solo tenga unos minutos, aquí va mi resumen.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Anton comienza introduciendo al equipo:&lt;/p&gt;
&lt;p&gt;Betty, desarrolladora web con experiencia en django y Marissa con el perfil administrativo.&lt;/p&gt;
&lt;p&gt;&lt;img alt="team" src="/pictures/best-practices-for-scaling-django/1-team.png" title="the team"&gt;&lt;/p&gt;
&lt;p&gt;Luego nos describe el stack actual del proyecto:&lt;/p&gt;
&lt;p&gt;&lt;img alt="estructura del proyecto inicial" src="/pictures/best-practices-for-scaling-django/2-stack.png" title="estructura inicial del proyecto"&gt;&lt;/p&gt;
&lt;p&gt;como se observa, tenemos &lt;em&gt;nginx&lt;/em&gt; como servidor web, &lt;em&gt;gunicorn&lt;/em&gt; como servidor python y &lt;em&gt;postgresql&lt;/em&gt; como servidor de base de datos.&lt;/p&gt;
&lt;p&gt;Se nos muestra que el sitio web, ya en producción, comienza a tener problemas, anda un poco lento. Betty decide investigar y descubre &lt;a href="https://github.com/django-debug-toolbar/django-debug-toolbar"&gt;django debug toolbar&lt;/a&gt;. Con ello observa lo siguiente:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;El sitio web está realizando 1292 querys en un tiempo de 813 ms (relativo, claro, a la pc al ordenador en que se realizan pruebas).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Betty investiga que una de las maneras para reducir el número de querys es mediante &lt;a href="https://docs.djangoproject.com/es/1.9/ref/models/querysets/#select-related"&gt;select related&lt;/a&gt; y &lt;a href="https://docs.djangoproject.com/es/1.9/ref/models/querysets/#prefetch-related"&gt;prefetch related&lt;/a&gt;. Con lo que después de una nueva medición, llega a:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;201 querys y 136 ms bajo las mismas condiciones&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Este es un primer logro, por lo que decide monitorizar el servidor para saber qué sucede en él.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Escucha sobre &lt;a href="https://newrelic.com/"&gt;New Relic&lt;/a&gt;, un servicio de monitorización de servidores con lo que observa las siguientes estadísticas:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;server 1: 99.3% cpu, 99.6% disk , 91.6% memory&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Con ello decide cambiar a 2 servidores y las nuevas estadísticas son:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;server 1: 73.4% cpu , 49.3% disk , 80.8% memory
db1     : 58.2% cpu , 54.3% disk , 55.6% memory&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Siempre se debe monitorear los servidores para saber qué está pasando.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Betty sigue avanzando, esta vez desea experimentar con la cache, por lo que decide probar &lt;a href="https://memcached.org/"&gt;memcached&lt;/a&gt;. Memcached es un sistema que almacena objetos en memoria para ser accedidos de manera más eficiente.&lt;/p&gt;
&lt;p&gt;&lt;img alt="memcached" src="/pictures/best-practices-for-scaling-django/3-memcached.png" title="memcached"&gt;&lt;/p&gt;
&lt;p&gt;Entonces, decide colocar un comentario en el html para saber en qué momento se está generando la página y observa algo como lo siguiente:&lt;/p&gt;
&lt;p&gt;&lt;img alt="memcached-fail" src="/pictures/best-practices-for-scaling-django/3-memcached-fail.png" title="memcached html"&gt;&lt;/p&gt;
&lt;p&gt;El problema es que observa que la fecha-hora de generación de las páginas siempre cambia. Esto significa que la cache no está trabajando como debería.&lt;/p&gt;
&lt;p&gt;Luego de investigar un poco, concluye que es por las cookies. Por lo que decide removerlas.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Luego, sucede un error. Al parecer todos los usuario observan lo mismo, todos son &lt;em&gt;Gina Caputo&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="memcached-same-user" src="/pictures/best-practices-for-scaling-django/4-same-user.png" title="same user"&gt;&lt;/p&gt;
&lt;p&gt;Entonces, la expresión de Betty, al comprender la causa, es similar a:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fail" src="/pictures/best-practices-for-scaling-django/5-fail.png" title="fail"&gt;&lt;/p&gt;
&lt;p&gt;Y Marissa, bueno.. ya se imaginarán.&lt;/p&gt;
&lt;p&gt;Betty investiga a fondo y después de un tiempo, leyendo sobre cómo funciona la cache, los middlewares de django y demás, logra solucionar el problema.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Despues de un fin de semana tranquilo, aparece otro error. Cada cierto tiempo, los usuarios observan :&lt;/p&gt;
&lt;p&gt;&lt;img alt="database error" src="/pictures/best-practices-for-scaling-django/6-database-error.png" title="database error"&gt;&lt;/p&gt;
&lt;p&gt;y el log indica:&lt;/p&gt;
&lt;p&gt;&lt;img alt="database log" src="/pictures/best-practices-for-scaling-django/7-log.png" title="database log"&gt;&lt;/p&gt;
&lt;p&gt;El error es causado porque el servidor de base de datos agota las conexiones. Entonces, decide utilizar &lt;a href="https://pgbouncer.github.io/"&gt;pg bouncer&lt;/a&gt;, un gestor de conexiones para postgresql. Ahora si, parece que se ha vuelto una experta en bases de datos.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Y por fin tiene tiempo para investigar lo que siempre quiso, procesos asíncronos.&lt;/p&gt;
&lt;p&gt;&lt;img alt="async" src="/pictures/best-practices-for-scaling-django/8-async.png" title="async"&gt;&lt;/p&gt;
&lt;p&gt;Lee sobre &lt;a href="http://www.celeryproject.org/"&gt;celery&lt;/a&gt;. Celery trabaja mediante "workers", los cuales procesan las tareas que están almacenada en una cola(queue) que Django va añadiendo.
También aprende que debe utilizar un almacen de base de datos, por lo que investiga sobre &lt;a href="http://redis.io/"&gt;redis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Luego implementa &lt;a href="http://flower.readthedocs.io/en/latest/"&gt;flower&lt;/a&gt;, una herramienta para monitorizar celery, con lo que el stack finalmente queda similar a:&lt;/p&gt;
&lt;p&gt;&lt;img alt="final stack" src="/pictures/best-practices-for-scaling-django/9-final.png" title="final stack"&gt;&lt;/p&gt;
&lt;p&gt;y por fin logra el &lt;strong&gt;Level 2&lt;/strong&gt; como desarroladora:&lt;/p&gt;
&lt;p&gt;&lt;img alt="level 2 de 800" src="/pictures/best-practices-for-scaling-django/10-level-2.png" title="level 2"&gt;&lt;/p&gt;
&lt;p&gt;Con ello finaliza la charla, salvo por algunas preguntas de los espectadores.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Entre mis conclusiones, podría decir que se nos ha mostrado que llevar un proyecto a producción puede ser mucho más complicado de lo esperado. Puede requerir un esfuerzo colosal si no tenemos experiencia manejando servidores.&lt;/p&gt;
&lt;p&gt;Pero, como siempre, nadie comienza sabiendo todo y poco a poco, se pueden ir añadiendo componentes según se necesiten.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Adjunto el video original:&lt;/p&gt;
&lt;div class='responsive-embed-youtube'&gt; 
    &lt;iframe class='video' width="560" height="315" src="https://www.youtube.com/embed/Ul-pHtOfA9U" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Y los slides en &lt;a href="http://slides.com/antonpirker/best-practicesforscaling-django"&gt;slides.com&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="django"/></entry><entry><title>Procesos de software y prácticas ágiles I</title><link href="https://alexanderae.com/software-processes-agile-practices-1.html" rel="alternate"/><published>2015-11-26T00:00:00-05:00</published><updated>2015-11-26T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2015-11-26:/software-processes-agile-practices-1.html</id><summary type="html">&lt;p&gt;Estoy llevando una especialización en Coursera llamada Sofware Product Management. A modo de resumen dicha especialización revisa las principales metodologías y prácticas de desarrollo ágil con el fin de que estemos en la capacidad de liderar a un equipo de desarrollo.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Estoy llevando una especialización en &lt;strong&gt;Coursera&lt;/strong&gt; llamada &lt;a href="https://www.coursera.org/specializations/product-management"&gt;Software Product Management&lt;/a&gt;. A modo de resumen dicha especialización revisa las principales metodologías y prácticas de desarrollo ágil con el fin de que estemos en la capacidad de liderar a un equipo de desarrollo.&lt;/p&gt;
&lt;p&gt;Sin mayor preámbulo, adjunto mis apuntes de la primera semana:&lt;/p&gt;
&lt;h4&gt;Semana 1 - Lección 1: Introducción&lt;/h4&gt;
&lt;p&gt;Objetivos de un mejor software:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;tener el producto correcto&lt;/li&gt;
&lt;li&gt;bien hecho&lt;/li&gt;
&lt;li&gt;bien administrado&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;¿cómo conectar objetivos y personas? : por medio de procesos y prácticas&lt;/p&gt;
&lt;h4&gt;Semana 1 - Lección 1: Procesos y prácticas&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Proceso&lt;/strong&gt; -&amp;gt; organiza el desarrollo de un producto de software en diferentes etapas. comprende desde el inicio de la idea hasta el retiro del producto (de ser necesario).&lt;/p&gt;
&lt;p&gt;las &lt;strong&gt;fases&lt;/strong&gt; pueden darse en serie o paralelo, algunas pueden ser:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;especificación -&amp;gt; concepción de la idea + listado de requerimientos&lt;/li&gt;
&lt;li&gt;diseño e implementación&lt;/li&gt;
&lt;li&gt;verificación y validación&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un proceso agrupa varias fases o etapas, las cuales a su vez comprenden un conjunto de &lt;strong&gt;actividades&lt;/strong&gt;. las actividades contienen las &lt;strong&gt;tareas&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;proceso &amp;gt; fases &amp;gt; actividades &amp;gt; tareas&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;las tareas pueden depender de otras tareas (implica orden)&lt;/li&gt;
&lt;li&gt;un rol es un deber u obligación que una persona toma o desempeña: desarrollador, tester, CEO, cliente o product manager&lt;/li&gt;
&lt;li&gt;"work product": diseños, requerimientos, código fuente, casos de test, documentación interna..&lt;/li&gt;
&lt;li&gt;una tarea puede dar como resultado o requerir como entrada un "work product"&lt;/li&gt;
&lt;li&gt;una tarea requiere recursos para ser completada: dinero, tiempo, tecnología, conocimiento o personal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Las &lt;strong&gt;prácticas&lt;/strong&gt; son tácticas que el SPM (&lt;strong&gt;software product manager&lt;/strong&gt;) usa para hacer que los procesos se ejecuten fácilmente.
Proveen guías en aspectos del desarrollo como : asignar fechas y organizar tareas, prevenir el desperdicio de recursos y monitorear el desarrollo del producto de trabajo. por ejemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;planear qué características van en cada lanzamiento&lt;/li&gt;
&lt;li&gt;estimar la duración de las tareas&lt;/li&gt;
&lt;li&gt;mantener reuniones breves diarias&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Las prácticas se reúnen frecuentemente en una &lt;strong&gt;metodología&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Una metodología es un conjunto de prácticas. (por ejemplo scrum)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;El &lt;a href="http://agilemanifesto.org/iso/es/"&gt;manifiesto ágil&lt;/a&gt; provee una filosofía. Y una metodología ágil provee un conjunto de lineamientos y reglas. Independientemente de las prácticas que uses. Ellas están destinadas a proveer una estructura para lo que involucre el proyecto. Esta estructura puede ayudar a planear efectivamente un proyecto, completar fechas límites, mejorar la comunicación, seguir el progreso y finalmente, entregar un mejor software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Por ejemplo, en la fase de especificación, tenemos muchas prácticas que describen cómo obtener y expresar requerimientos de los clientes.&lt;/p&gt;
&lt;p&gt;Muchas metodologías promueven y facilitan la comunicación abierta entre clientes, SPM y desarrolladores.&lt;/p&gt;
&lt;p&gt;Tener una comunicación abierta entre desarrolladores permite un desarrollo eficiente ya que las expectaciones son claras y todos conocen lo que todos están haciendo.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Un planeamiento y documentación efectiva previene que los trabajos sean completados por más de una persona. También previene que los desarrolladores produzcan una característica que tal vez no sea exactamente a lo que el cliente solicita.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Esto ayuda a mantener el proyecto en los tiempos planeados lo que ayuda a su vez a mantenerlo dentro del presupuesto.&lt;/p&gt;
&lt;p&gt;Las prácticas y procesos son flexibles. Es posible tomar aspectos de varios procesos y prácticas para crear algo que cumpla todas tus necesidades. También pueden ser mejoradas y desarrolladas a través del tiempo.&lt;/p&gt;
&lt;p&gt;Aprendiendo múltiples procesos y prácticas uno será capaz de generar o seleccionar un proceso o un conjunto de prácticas que funcione mejor para tu equipo.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;Semana 1 - Lección 2:&lt;/h4&gt;
&lt;p&gt;Una actividad en ingeniería de software es un grupo de tareas relacionadas en el desarrollo de software. Cada actividad tiene como entrada y salida, &lt;em&gt;productos de trabajo&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Actividades de ingeniería de software" src="/pictures/software-engineering-activities.png" title="Actividades de ingeniería de software"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Un equipo debe decidir algunos aspectos del desarrollo, como convenciones de código, niveles de documentación, estrategias de testing y la definición de terminado.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cualquier parte del proyecto que podría ir mal en algún modo sería la entrada para la actividad de gestión de riesgos. La salida es un plan de riesgos que delinea los riesgos potenciales y sus respectivos planes de mitigación.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Los tiempos de desarrollo están basados en &lt;strong&gt;estimados&lt;/strong&gt; de &lt;em&gt;cuánto tiempo tomará completar las tareas&lt;/em&gt;. Cuando los estimados están muy lejos, el cronograma se compromete. La entrada es una lista de tareas que necesitan ser completadas. La salida son los cronogramas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;La actividad de asignación de recursos incluye planeamiento y programación del cronograma de recursos de personal. Esto significa descomponer el horario de trabajo y asignar tareas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hacer mediciones es otra actividad que envuelve tareas como la definición, cálculo y análisis de métricas. Las métricas son usadas para realizar seguimiento y evaluar el producto y/o proceso. Las métricas pueden medir la densidad de defectos del producto, la velocidad a la que el equipo completa las tareas, el número de funciones completadas.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Algunas de las fases del desarrollo de software pueden ser:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;** Fase de especificación **&lt;/p&gt;
&lt;p&gt;Corresponde a la identificación de ideas y necesidades: &lt;strong&gt;sin una idea no hay producto a desarrollar&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;** Fase de implementación **&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verificación y validación&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Es donde validamos si el producto hace lo que está destinado a hacer y que cumpla las necesidades del cliente.&lt;/p&gt;
&lt;p&gt;Las retrospectivas ayudan a identificar las áreas en las que se pueden mejorar la siguiente vez.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;Nota:&lt;/h4&gt;
&lt;p&gt;El curso incluye material en formato PDF que se puede descargar desde la página de recursos del mismo o desde &lt;a href="https://drive.google.com/file/d/0B9sAAyxUlH2teVBQeXZlZGlmbkk/view?usp=sharing"&gt;aquí&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="coursera"/><category term="cursos"/><category term="desarrollo-de-software"/></entry><entry><title>Django: Ejemplo de Form + InlineFormset</title><link href="https://alexanderae.com/django-form-inlineformset.html" rel="alternate"/><published>2015-02-23T00:00:00-05:00</published><updated>2015-02-23T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2015-02-23:/django-form-inlineformset.html</id><summary type="html">&lt;p&gt;A modo de práctica con los Form + Formset desarrollaré una aplicación para registrar recetas con sus respectivos ingredientes y procedimientos. El propósito será poder editar todo el detalle de una receta incluidos los ingredientes y procedimiento en la misma pantalla, de modo similar a como se trabaja en el admin de django con los inlines.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A modo de práctica con los Form + Formset desarrollaré una aplicación para registrar recetas con sus respectivos ingredientes y procedimientos &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;El propósito será poder editar todo el detalle de una receta incluidos los ingredientes y procedimiento en la misma pantalla, de modo similar a como se trabaja en el admin de django con los inlines &lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h4&gt;1. MODELS&lt;/h4&gt;
&lt;p&gt;Necesitaremos 3 modelos: recetas, ingredientes y procedimientos.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# models.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Receta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;descripcion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Ingrediente&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;receta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;descripcion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Instruccion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;receta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;numero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositiveSmallIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Paso número&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;descripcion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como se ve, nada complicado para comenzar. El modelo "principal" es el de la receta. Tenemos también a los modelos &lt;code&gt;Ingrediente&lt;/code&gt; e &lt;code&gt;Intrucción&lt;/code&gt; con sus respectivos &lt;code&gt;Foreign Key&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;2. FORM:&lt;/h4&gt;
&lt;p&gt;Necesitamos un formulario para la receta:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# forms.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Receta&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;RecetaForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelForm&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Receta&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;3. URL's&lt;/h4&gt;
&lt;p&gt;Necesitamos 3 url's para: el listado de recetas, registro y edición de las mismas.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="p"&gt;..&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;recetas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recetas&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;lista&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;),&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;recetas&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;registrar&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;recetas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registro_edicion&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;registrar&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;),&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;recetas&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;receta_id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;\d+)/$&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;recetas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registro_edicion&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;editar&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;4. VIEWS&lt;/h4&gt;
&lt;p&gt;Creamos dos vistas, una para listar nuestras recetas y la otra para manejar el registro y edición de las mismas.&lt;/p&gt;
&lt;p&gt;Lo "complicado" se ve en la segunda vista:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Obtenemos una instancia de la receta actual en caso de que la estemos editando o creamos una nueva para el caso de un registro.&lt;/li&gt;
&lt;li&gt;Inicializamos los dos &lt;a href="https://docs.djangoproject.com/en/1.7/topics/forms/modelforms/#inline-formsets"&gt;InlineFormset&lt;/a&gt;, uno para los ingredientes y otro para las instrucciones.&lt;/li&gt;
&lt;li&gt;En caso de que sea un pedido POST, validamos el formulario y los inlineformset, de ser válidos grabamos los cambios.&lt;/li&gt;
&lt;li&gt;En caso de que sea un GET, inicializamos el form y los 2 formsets.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.shortcuts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render_to_response&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.forms.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;inlineformset_factory&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ingrediente&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Instruccion&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.forms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecetaForm&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;recetas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;recetas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;recetas.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;registro_edicion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receta_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;receta_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;receta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;receta_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;receta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;IngredienteFormSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inlineformset_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ingrediente&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;can_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;InstruccionFormSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inlineformset_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Receta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Instruccion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;can_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecetaForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ingredienteFormset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IngredienteFormSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;instruccionFormset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InstruccionFormSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ingredienteFormset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;instruccionFormset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;ingredienteFormset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;instruccionFormset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lista&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RecetaForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ingredienteFormset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IngredienteFormSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;instruccionFormset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InstruccionFormSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;receta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;registro-edicion.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;5. TEMPLATES&lt;/h4&gt;
&lt;p&gt;Necesitamos un template para el listado de recetas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::jinja
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;base.html&amp;#39;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Recetas&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;ol&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;receta&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;recetas&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;receta.get_absolute_url&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;receta.titulo&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;url&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;registrar&amp;#39;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Registrar&lt;span class="w"&gt; &lt;/span&gt;receta&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Observen que hemos añadido un método &lt;code&gt;get_absolute_url&lt;/code&gt; al modelo &lt;code&gt;Receta&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
    def get_absolute_url(self):
        return reverse(&amp;#39;editar&amp;#39;, kwargs={&amp;#39;receta_id&amp;#39;: self.id})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y el template para la parte del registro/edición que consta de:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sección de administración para los formset (por ejemplo un campo lleva la cuenta del número actual de inlines y otro el del número inicial de los mismos)&lt;/li&gt;
&lt;li&gt;El formulario de la receta&lt;/li&gt;
&lt;li&gt;Los formularios para cada formset&lt;/li&gt;
&lt;li&gt;Un template para cada formset que se toma como base para el proceso de "Añadir un nuevo Ingrediente o Instrucción". Nótese que para clonar este template estoy utilizando un poco de jquery.&lt;/li&gt;
&lt;li&gt;Código javascript que maneja el proceso de clonación&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El template es el que sigue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::jinja
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;base.html&amp;#39;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;post&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;csrf_token&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;ingredienteFormset.management_form&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;instruccionFormset.management_form&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;fieldset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;form &amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;field&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;form&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;form-row&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;field-box&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;field.errors&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;field.label_tag&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;field&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Ingredientes&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;formset-ingredientes&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;form&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;ingredienteFormset&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;form.id&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;form.as_ul&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;btnIngrediente&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Añadir&lt;span class="w"&gt; &lt;/span&gt;Ingrediente&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Instrucciones&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;formset-instrucciones&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;form&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;instruccionFormset&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;form.id&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;form.as_ul&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;btnInstruccion&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Añadir&lt;span class="w"&gt; &lt;/span&gt;Instrucción&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Enviar&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;template/ingrediente&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;ingredienteFormset.empty_form.as_ul&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;template/instruccion&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;instruccionFormset.empty_form.as_ul&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
$(function(){

&lt;span class="w"&gt;    &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Reemplaza&lt;span class="w"&gt; &lt;/span&gt;todas&lt;span class="w"&gt; &lt;/span&gt;las&lt;span class="w"&gt; &lt;/span&gt;coincidencias&lt;span class="w"&gt; &lt;/span&gt;en&lt;span class="w"&gt; &lt;/span&gt;vez&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;solo&lt;span class="w"&gt; &lt;/span&gt;la&lt;span class="w"&gt; &lt;/span&gt;primera
&lt;span class="w"&gt;    &lt;/span&gt;function&lt;span class="w"&gt; &lt;/span&gt;replaceAll(text,&lt;span class="w"&gt; &lt;/span&gt;busca,&lt;span class="w"&gt; &lt;/span&gt;reemplaza){
&lt;span class="w"&gt;          &lt;/span&gt;while&lt;span class="w"&gt; &lt;/span&gt;(text.toString().indexOf(busca)&lt;span class="w"&gt; &lt;/span&gt;!=&lt;span class="w"&gt; &lt;/span&gt;-1)
&lt;span class="w"&gt;            &lt;/span&gt;text&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;text.toString().replace(busca,&lt;span class="w"&gt; &lt;/span&gt;reemplaza);
&lt;span class="w"&gt;          &lt;/span&gt;return&lt;span class="w"&gt; &lt;/span&gt;text;
&lt;span class="w"&gt;    &lt;/span&gt;}

&lt;span class="w"&gt;    &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;$totalIngredientes&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;$(&amp;#39;#id_ingrediente_set-TOTAL_FORMS&amp;#39;);

&lt;span class="w"&gt;    &lt;/span&gt;$(&amp;#39;#btnIngrediente&amp;#39;).click(function(event)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;        &lt;/span&gt;event.preventDefault();
&lt;span class="w"&gt;        &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;total&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;parseInt($totalIngredientes.val(),&lt;span class="w"&gt; &lt;/span&gt;10);
&lt;span class="w"&gt;        &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;clon&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;$(&amp;#39;script[type=&amp;quot;template/ingrediente&amp;quot;]&amp;#39;).html();
&lt;span class="w"&gt;        &lt;/span&gt;clon_html&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;replaceAll(clon,&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;__prefix__&amp;#39;,&lt;span class="w"&gt; &lt;/span&gt;(total).toString());
&lt;span class="w"&gt;        &lt;/span&gt;$(&amp;#39;#formset-ingredientes&amp;#39;).append(clon_html);
&lt;span class="w"&gt;        &lt;/span&gt;$totalIngredientes.val(total&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;1);
&lt;span class="w"&gt;    &lt;/span&gt;});

&lt;span class="w"&gt;    &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;$totalInstrucciones&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;$(&amp;#39;#id_instruccion_set-TOTAL_FORMS&amp;#39;);

&lt;span class="w"&gt;    &lt;/span&gt;$(&amp;#39;#btnInstruccion&amp;#39;).click(function(event)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;        &lt;/span&gt;event.preventDefault();
&lt;span class="w"&gt;        &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;total&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;parseInt($totalInstrucciones.val(),&lt;span class="w"&gt; &lt;/span&gt;10);
&lt;span class="w"&gt;        &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;clon&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;$(&amp;#39;script[type=&amp;quot;template/instruccion&amp;quot;]&amp;#39;).html();
&lt;span class="w"&gt;        &lt;/span&gt;clon_html&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;replaceAll(clon,&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;__prefix__&amp;#39;,&lt;span class="w"&gt; &lt;/span&gt;(total).toString());
&lt;span class="w"&gt;        &lt;/span&gt;$(&amp;#39;#formset-instrucciones&amp;#39;).append(clon_html);
&lt;span class="w"&gt;        &lt;/span&gt;$totalInstrucciones.val(total&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt;  &lt;/span&gt;1);
&lt;span class="w"&gt;    &lt;/span&gt;});
})
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
Y con ello concluye el proceso.. claro, sin olvidar que hay que ensamblar las partes&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Por cierto, si bien he utilizado &lt;a href="https://docs.djangoproject.com/en/1.7/topics/http/views/"&gt;vistas basadas en funciones&lt;/a&gt;, sé que es posible realizar lo mismo con las &lt;a href="https://docs.djangoproject.com/en/1.7/topics/class-based-views/"&gt;vistas basadas en clases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;#42; Aunque tengo mis dudas respecto la complejidad del código en ese otro caso.&lt;/p&gt;
&lt;hr&gt;
&lt;h5&gt;Referencias&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://djangosnippets.org/snippets/1246/"&gt;Form with one Formset example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;La idea la tomé de este &lt;a href="http://kevindias.com/writing/django-class-based-views-multiple-inline-formsets/"&gt;post&lt;/a&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;En el admin de django es posible editar un modelo con sus "&lt;a href="https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.TabularInline"&gt;inline models&lt;/a&gt;" mediante los &lt;code&gt;TabularInline&lt;/code&gt; y &lt;code&gt;StackedInline&lt;/code&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;pero aquello queda de tarea&amp;#160;&lt;a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="django"/><category term="tutorial"/></entry><entry><title>RiotJS: Tutorial #1</title><link href="https://alexanderae.com/riotjs-tutorial-1.html" rel="alternate"/><published>2015-02-22T00:00:00-05:00</published><updated>2015-02-22T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2015-02-22:/riotjs-tutorial-1.html</id><summary type="html">&lt;p&gt;Slug: riotjs-tutorial-1
Author: &lt;strong&gt;alexander&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ahora que cuento con un tanto de tiempo libre, es momento de revisar algunos frameworks javascript.&lt;/p&gt;
&lt;p&gt;Después de analizar las opciones (y vaya que son muchas) comenzaré con RiotJS&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://muut.com/riotjs/"&gt;RiotJS&lt;/a&gt; es un &lt;a href="https://muut.com/blog/technology/riot-2.0/index.html"&gt;framework javascript minimalista&lt;/a&gt;, entre sus características destacadan: el sistema de etiquetas personalidadas, su sencilla sintaxis y su pequeño tamaño.&lt;/p&gt;
&lt;p&gt;Nota: Este tutorial es una adaptación de "&lt;a href="https://juriansluiman.nl/article/154/the-react-tutorial-for-riot"&gt;The react tutorial for riot&lt;/a&gt;", el cual a su vez es otra adaptación del tutorial de &lt;a href="http://facebook.github.io/react/docs/tutorial.html"&gt;ReactJS …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Slug: riotjs-tutorial-1
Author: &lt;strong&gt;alexander&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ahora que cuento con un tanto de tiempo libre, es momento de revisar algunos frameworks javascript.&lt;/p&gt;
&lt;p&gt;Después de analizar las opciones (y vaya que son muchas) comenzaré con RiotJS&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://muut.com/riotjs/"&gt;RiotJS&lt;/a&gt; es un &lt;a href="https://muut.com/blog/technology/riot-2.0/index.html"&gt;framework javascript minimalista&lt;/a&gt;, entre sus características destacadan: el sistema de etiquetas personalidadas, su sencilla sintaxis y su pequeño tamaño.&lt;/p&gt;
&lt;p&gt;Nota: Este tutorial es una adaptación de "&lt;a href="https://juriansluiman.nl/article/154/the-react-tutorial-for-riot"&gt;The react tutorial for riot&lt;/a&gt;", el cual a su vez es otra adaptación del tutorial de &lt;a href="http://facebook.github.io/react/docs/tutorial.html"&gt;ReactJS&lt;/a&gt; para  RiotJS.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;El objetivo del tutorial&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; es construir un sistema de comentarios que podría incluirse en un blog. Las características de este sistema son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tiene una vista de todos los comentarios anteriores&lt;/li&gt;
&lt;li&gt;Cuenta con un formulario para registrar nuevos comentarios&lt;/li&gt;
&lt;li&gt;Se integra con un backend para almacenar los comentarios&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y como adicionales:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comentarios optimistas: los comentarios aparecen antes de ser registrados en el backend&lt;/li&gt;
&lt;li&gt;Actualizaciones en vivo, es posible ver los comentarios de otras personas en tiempo real.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;1. Estructura&lt;/h4&gt;
&lt;p&gt;Los bloques a considerar para nuestra implementación son:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;- CommentBox
    - CommentList
        - Comment
    - CommentForm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Los cuales se refieren al contenedor general, la lista de comentarios, cada comentario propiamente dicho y al formulario.&lt;/p&gt;
&lt;p&gt;Como mencionamos, uno de los puntos fuertes de Riot es que nos permite usar etiquetas personalizadas. Para aprovechar esto crearemos un archivo llamado &lt;em&gt;comments.tag&lt;/em&gt; y escribiremos lo siguiente en el:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::text
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-box&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Comentarios&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;comments=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/comment-box&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-form&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;submit&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Publicar&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/comment-form&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-list&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;each=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;opts.comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/comment-list&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Mi&lt;span class="w"&gt; &lt;/span&gt;nombre&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Mi&lt;span class="w"&gt; &lt;/span&gt;comentario&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/comment&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo anterior representa a los bloques inicialmente descritos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nuestro &lt;code&gt;comment-box&lt;/code&gt; que tiene un elemento &lt;code&gt;comment-list&lt;/code&gt; al cual le está pasando como parámetro la lista de comentarios.&lt;/li&gt;
&lt;li&gt;El &lt;code&gt;comment-form&lt;/code&gt; que representa al formulario por el que se registran nuevos comentarios&lt;/li&gt;
&lt;li&gt;El &lt;code&gt;comment-list&lt;/code&gt; que contiene una lista de elementos &lt;code&gt;comment&lt;/code&gt; sobre la que va a iterar.&lt;/li&gt;
&lt;li&gt;El &lt;code&gt;comment&lt;/code&gt; que es el comentario propiamente dicho.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;2. Montar los componentes&lt;/h4&gt;
&lt;p&gt;Ahora que tenemos los componentes, vamos a integrarlos con nuestra web.&lt;/p&gt;
&lt;p&gt;Nuestro archivo HTML principal (index.html) debe lucir similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Sistema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comentarios&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://cdn.jsdelivr.net/g/riot@2.0.11&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;riot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En el html, hemos incluido nuestro contentenedor general (&lt;code&gt;&amp;lt;comment-box&amp;gt;&lt;/code&gt;), el archivo &lt;code&gt;comments.js&lt;/code&gt; y un script que monta&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt; nuestra etiqueta &lt;code&gt;&amp;lt;comment-box&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Observen además que hemos incluido un script &lt;code&gt;comments.js&lt;/code&gt; en el documento, este se obtiene al compilar nuestro archivo &lt;em&gt;comments.tag&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Para compilar el archivo &lt;code&gt;.tag&lt;/code&gt; tenemos dos opciones:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Incluir el archivo &lt;code&gt;compiler.js&lt;/code&gt; en el head de nuestra página&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; Compilar en el lado del servidor, para ello necesitamos instalar riot mediante npm:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;npm install riot&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Con ello tendremos acceso al binario &lt;strong&gt;riot&lt;/strong&gt;, al cual le indicaremos que observe los cambios en los archivos &lt;em&gt;tag&lt;/em&gt; en nuestro directorio actual y los compile:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;riot -w .&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Para comprobar si todo va bien hasta ahora, podríamos levantar un servidor en local:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;python -m SimpleHTTPServer&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;si vamos a &lt;code&gt;127.0.0.1:8000&lt;/code&gt;, veremos que solo se ha cargado el formulario (nota: verificar que no exista ningún error en el inspector de elementos&lt;sup id="fnref:5"&gt;&lt;a class="footnote-ref" href="#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;
&lt;p&gt;En el siguiente paso añadiremos algunas funcionalidades.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;3. Incluyendo variables y estados&lt;/h4&gt;
&lt;p&gt;Otra de las características de riot nos permite enviar variables al tag&lt;sup id="fnref:6"&gt;&lt;a class="footnote-ref" href="#fn:6"&gt;6&lt;/a&gt;&lt;/sup&gt; que estamos montando.&lt;/p&gt;
&lt;p&gt;Editamos el archivo &lt;code&gt;index.html&lt;/code&gt;, cambiamos la línea que monta el tag por:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;riot.mount('comment-box', {title: 'Comentarios'});&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;y actualizamos nuestro archivo &lt;code&gt;.tag&lt;/code&gt; para que incluya nuestra variable &lt;code&gt;title&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::text
&lt;span class="nt"&gt;&amp;lt;comment-box&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;opts.title&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;comments=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/comment-box&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;#42;Las opciones que enviamos a nuestro tag como contexto quedan almacenadas en la variable &lt;code&gt;opts&lt;/code&gt;&lt;sup id="fnref:7"&gt;&lt;a class="footnote-ref" href="#fn:7"&gt;7&lt;/a&gt;&lt;/sup&gt;, con lo que el título de nuestra tag será el parámetro que enviamos.&lt;/p&gt;
&lt;p&gt;Añadiendo un poco más de código para manejar el registro de nuevos comentarios:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::text
&lt;span class="nt"&gt;&amp;lt;comment-box&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;opts.title&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;comments=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment-form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;this.comments&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;[]
&lt;span class="w"&gt;  &lt;/span&gt;add(comment)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;    &lt;/span&gt;this.comments.push(comment)
&lt;span class="w"&gt;  &lt;/span&gt;}
&lt;span class="nt"&gt;&amp;lt;/comment-box&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;comment-form&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;submit&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Publicar&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;add(e)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;    &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;comment&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;      &lt;/span&gt;name:&lt;span class="w"&gt; &lt;/span&gt;this.name.value,
&lt;span class="w"&gt;      &lt;/span&gt;message:&lt;span class="w"&gt; &lt;/span&gt;this.message.value
&lt;span class="w"&gt;    &lt;/span&gt;}
&lt;span class="w"&gt;    &lt;/span&gt;this.parent.add(comment)
&lt;span class="w"&gt;    &lt;/span&gt;e.target.reset()
&lt;span class="w"&gt;  &lt;/span&gt;}
&lt;span class="nt"&gt;&amp;lt;/comment-form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;comment-list&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;each=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;opts.comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/comment-list&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;comment&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;this.parent.name&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;this.parent.message&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/comment&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hemos creado una función &lt;code&gt;add(e)&lt;/code&gt; que maneja el evento &lt;em&gt;onsubmit&lt;/em&gt; de nuestro formulario. Esta función captura los valores ingresados, los envía al tag padre (&lt;code&gt;&amp;lt;comment-box&amp;gt;&lt;/code&gt;) y resetea&lt;sup id="fnref:8"&gt;&lt;a class="footnote-ref" href="#fn:8"&gt;8&lt;/a&gt;&lt;/sup&gt; el formulario. Observe que los campos con un atributo name se pueden acceder directamente mediante &lt;code&gt;this.name&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;En el tag &lt;code&gt;&amp;lt;comment-box&amp;gt;&lt;/code&gt; se tiene otra función que inserta el comentario en la lista de comentarios.&lt;/p&gt;
&lt;p&gt;También, en nuestra etiqueta &lt;code&gt;comment&lt;/code&gt; estamos mostrando los atributos &lt;code&gt;name&lt;/code&gt; y &lt;code&gt;message&lt;/code&gt; de cada iteración, por medio de los atributos del padre.&lt;/p&gt;
&lt;p&gt;Otra opción más &lt;em&gt;"explícita"&lt;/em&gt; sería enviando el nombre y mensaje como variables de contexto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::text
&lt;span class="nt"&gt;&amp;lt;comment-list&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;each=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;opts.comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this.name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;message=&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this.message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/comment-list&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;comment&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;opts.name&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;opts.message&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/comment&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con lo realizado hasta ahora, podemos volver a abrir nuestro navegador y probar.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;4. Carga y persistencia de nuestros comentarios&lt;/h4&gt;
&lt;p&gt;Lo que sigue es implementar la comunicación de nuestra aplicación con algún backend para que los comentarios registrados queden almacenados. Para el ejemplo, voy a utilizar &lt;a href="https://www.parse.com"&gt;Parse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nos registramos y creamos una &lt;a href="https://parse.com/apps"&gt;nueva aplicación&lt;/a&gt;. Luego copiamos los valores del &lt;code&gt;Application ID&lt;/code&gt; y el &lt;code&gt;Javascript Key&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;En nuestro archivo html debemos insertar el script de parse (para facilidad nuestra, podemos usar el del CDN):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script src="//www.parsecdn.com/js/parse-1.3.4.min.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Luego, debemos inicializar la aplicación:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Parse.initialize("APP_KEY", "JS_KEY");&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;En nuestra etiqueta &lt;code&gt;comment-box&lt;/code&gt;, modificamos el método &lt;code&gt;add&lt;/code&gt; para que también registre el comentario en Parse:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Comment&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;_comment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Añadimos también un método &lt;code&gt;load&lt;/code&gt; que realizará la &lt;a href="https://parse.com/docs/js_guide#objects-retrieving"&gt;carga&lt;/a&gt; de comentarios desde Parse:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Comment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queryComment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;queryComment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmp_comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;tmp_comments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;comments&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmp_comments&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nótese que al terminar de recuperar los comentarios, estamos activando manualmente el evento update para que se refresquen los comentarios visibles.&lt;/p&gt;
&lt;p&gt;También debemos iniciar nuestro método manualmente al menos una vez &lt;code&gt;this.load()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Luego, para que el sitio refresque los comentarios cada cierto tiempo, podemos añadir:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;setInterval(this.load, opts.interval)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Y en nuestra llamada al &lt;code&gt;mount&lt;/code&gt; en el index, agregamos el parámetro interval:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;riot.mount('comment-box', {title: 'Comments', interval: 2500});&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Pueden revisar la versión final del código en este &lt;a href="https://gist.github.com/alexander-ae/cff216db327e07c07bea"&gt;gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Como se observa en el resultado final, lo que me sorprende es la poca cantidad de código que se requiere para tener un sistema de comentarios con persistencia en el backend haciendo presente que tampoco se pierde legibilidad en el código.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Referencias:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://juriansluiman.nl/article/154/the-react-tutorial-for-riot"&gt;The react tutorial for riot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;El plan original incluye revisar AngularJS y ReactJS también.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;El detalle es casi idéntico al tutorial de ReactJS, tan solo difiere en la capacidad para realizar comentarios con markdown.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Como backend estoy utilizando &lt;a href="https://www.parse.com"&gt;ParseJS&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;RiotJS llama montar al proceso de incluir nuestras etiquetas personalizadas y su respectiva lógica en el documento HTML.&amp;#160;&lt;a class="footnote-backref" href="#fnref:4" title="Jump back to footnote 4 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Me refiero al inspector de elementos de chrome o a su equivalente en firefox.&amp;#160;&lt;a class="footnote-backref" href="#fnref:5" title="Jump back to footnote 5 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;tag o etiqueta&amp;#160;&lt;a class="footnote-backref" href="#fnref:6" title="Jump back to footnote 6 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;&lt;a href="https://muut.com/riotjs/api/#mount"&gt;riot.js mount&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:7" title="Jump back to footnote 7 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;Se puede acceder al elemento que origina el evento con &lt;code&gt;e.target&lt;/code&gt;. Para más detalle, consultar la documentación: &lt;a href="[https://muut.com/riotjs/guide/#event-object]"&gt;https://muut.com/riotjs/guide/#event-object&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:8" title="Jump back to footnote 8 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="riotjs"/><category term="javascript"/><category term="parse"/><category term="tutorial"/></entry><entry><title>Awesome Django</title><link href="https://alexanderae.com/awesome-django-apps.html" rel="alternate"/><published>2015-01-24T00:00:00-05:00</published><updated>2015-01-24T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2015-01-24:/awesome-django-apps.html</id><summary type="html">&lt;p&gt;Si tuviera que enumerar los motivos por los que utilizo &lt;a href="https://www.djangoproject.com/"&gt;django&lt;/a&gt;, serían: &lt;a href="https://www.python.org/"&gt;python&lt;/a&gt;, su &lt;a href="https://docs.djangoproject.com/en/dev/misc/design-philosophies/"&gt;filosofía&lt;/a&gt; y su &lt;a href="https://www.djangoproject.com/community/"&gt;comunidad&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En este post plasmo una pequeña recopilación de aplicaciones y proyectos de terceros que me han facilitado el programar con django, esperando que a alguién más le sea de utilidad.&lt;/p&gt;
&lt;p&gt;Aunque tal vez me olvide de uno que otro, aquí va mi lista:&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;1. Django debug toolbar&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/django-debug-toolbar/django-debug-toolbar"&gt;https://github.com/django-debug-toolbar/django-debug-toolbar&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Conjunto de paneles configurables que muestran información que ayuda al …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Si tuviera que enumerar los motivos por los que utilizo &lt;a href="https://www.djangoproject.com/"&gt;django&lt;/a&gt;, serían: &lt;a href="https://www.python.org/"&gt;python&lt;/a&gt;, su &lt;a href="https://docs.djangoproject.com/en/dev/misc/design-philosophies/"&gt;filosofía&lt;/a&gt; y su &lt;a href="https://www.djangoproject.com/community/"&gt;comunidad&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En este post plasmo una pequeña recopilación de aplicaciones y proyectos de terceros que me han facilitado el programar con django, esperando que a alguién más le sea de utilidad.&lt;/p&gt;
&lt;p&gt;Aunque tal vez me olvide de uno que otro, aquí va mi lista:&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;1. Django debug toolbar&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/django-debug-toolbar/django-debug-toolbar"&gt;https://github.com/django-debug-toolbar/django-debug-toolbar&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Conjunto de paneles configurables que muestran información que ayuda al proceso de hacer debug del request actual. Algunos de ellos son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Versiones: para conocer la versión de python, django y otros paquetes utilizados&lt;/li&gt;
&lt;li&gt;SQL: muestra la lista de consultas SQL invocadas en el request.&lt;/li&gt;
&lt;li&gt;Configuraciones: lista de variables del archivo settings&lt;/li&gt;
&lt;li&gt;Templates: plantillas html renderizadas&lt;/li&gt;
&lt;li&gt;otros..&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Django grappelli&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/sehmaschine/django-grappelli"&gt;https://github.com/sehmaschine/django-grappelli&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Es un "tema" para reemplazar a la apariencia por defecto del admin de django.
Cuenta además con varias utilidades para hacer el uso del admin más personalizable.&lt;/p&gt;
&lt;h4&gt;3. Django Filebrowser&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/sehmaschine/django-filebrowser"&gt;https://github.com/sehmaschine/django-filebrowser&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Gestor de archivos media para el admin de django.
Nota: es del mismo desarrollador de &lt;em&gt;grappelli&lt;/em&gt; por lo que se integran perfectamente.&lt;/p&gt;
&lt;h4&gt;4. Django Admin Honeypot&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/dmpayton/django-admin-honeypot"&gt;https://github.com/dmpayton/django-admin-honeypot&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Un falso login para el admin de django que notifica a los administradores de intentos de acceso desautorizados.&lt;/p&gt;
&lt;h4&gt;5. Django Ratelimit&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/jsocol/django-ratelimit"&gt;https://github.com/jsocol/django-ratelimit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Decorador que restringe el acceso a determinadas vistas (views) en base a ciertos parámetros como por ejemplo, el número de consultas realizadas por una IP en cierto intervalo.&lt;/p&gt;
&lt;h4&gt;6. Django Nose&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/django-nose/django-nose"&gt;https://github.com/django-nose/django-nose&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Todas las bondades de &lt;a href="http://nose.readthedocs.org/en/latest/"&gt;nose&lt;/a&gt; para django.&lt;/p&gt;
&lt;h4&gt;7. Django Extensions&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/django-extensions/django-extensions"&gt;https://github.com/django-extensions/django-extensions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Conjunto de comandos adicionales para la administración del proyecto (manage.py).
Entre las utilidades destacan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;shell_plus: shell que auto carga los modelos de nuestra aplicación.&lt;/li&gt;
&lt;li&gt;runscript: ejecuta un script en el contexto de nuestro proyecto.&lt;/li&gt;
&lt;li&gt;show_urls: muestra la lista de todas las url's.&lt;/li&gt;
&lt;li&gt;graph_models: permite visualizar nuestros modelos en un diagrama.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8. Django Celery&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://celery.readthedocs.org/en/latest/index.html"&gt;http://celery.readthedocs.org/en/latest/index.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Gestor de colas distribuido.&lt;/p&gt;
&lt;h4&gt;9. Django RQ&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/ui/django-rq"&gt;https://github.com/ui/django-rq&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Provee integración entre &lt;a href="https://github.com/nvie/rq"&gt;RQ&lt;/a&gt; y django. &lt;em&gt;RQ&lt;/em&gt; es un gestor de colas que utiliza &lt;strong&gt;redis&lt;/strong&gt; como backend. Su objetivo es ser una alternativa ligera a &lt;em&gt;celery&lt;/em&gt;.&lt;/p&gt;
&lt;h4&gt;10. Django Sentry&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/getsentry/sentry"&gt;https://github.com/getsentry/sentry&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Sentry es un proyecto que nos ayuda en las tareas de detección, registro y agrupación de los errores de nuestro proyecto.&lt;/p&gt;
&lt;h4&gt;11. Python Social Auth&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/omab/python-social-auth"&gt;https://github.com/omab/python-social-auth&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Aplicación para gestionar la autenticación y registro en diversas plataformas sociales (facebook, twitter, etc).&lt;/p&gt;
&lt;h4&gt;12. Django Taggit&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/alex/django-taggit"&gt;https://github.com/alex/django-taggit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Sistema de gestión de etiquetas en nuestros modelos.&lt;/p&gt;
&lt;h4&gt;13. Django Geoposition&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/philippbosch/django-geoposition"&gt;https://github.com/philippbosch/django-geoposition&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Model field&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; que permite insertar coordenadas geolocalizadas con ayuda de google maps.&lt;/p&gt;
&lt;h4&gt;14. Django Recaptcha&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/praekelt/django-recaptcha"&gt;https://github.com/praekelt/django-recaptcha&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Form field&lt;/em&gt;&lt;sup id="fnref2:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; para insertar un recaptcha en los formularios.&lt;/p&gt;
&lt;h4&gt;15. Django Mptt&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/django-mptt/django-mptt"&gt;https://github.com/django-mptt/django-mptt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Utilidades para implementar un modelo que almacena información jerárquica.
Por ejemplo, un sistema de categorías anidadas.&lt;/p&gt;
&lt;h4&gt;16. Django Redis&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/niwibe/django-redis"&gt;https://github.com/niwibe/django-redis&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Aplicación que permite utilizar redis como un backend de cache.&lt;/p&gt;
&lt;h4&gt;17. Django Summernote&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/summernote/django-summernote"&gt;https://github.com/summernote/django-summernote&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Editor [wysiwyg]&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; para django.&lt;/p&gt;
&lt;h4&gt;18. Sorl Thumbnail&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/mariocesar/sorl-thumbnail"&gt;https://github.com/mariocesar/sorl-thumbnail&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Gestor de miniaturas.&lt;/p&gt;
&lt;h4&gt;19. Django ModelTranslation&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/deschler/django-modeltranslation"&gt;https://github.com/deschler/django-modeltranslation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Traduce nuestros modelos utilizando un enfoque similar al del admin (por medio de registros).&lt;/p&gt;
&lt;h4&gt;20. Restless&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/toastdriven/restless"&gt;https://github.com/toastdriven/restless&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Miniframework REST ligero.&lt;/p&gt;
&lt;h4&gt;21. Django Watchman&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/mwarkentin/django-watchman"&gt;https://github.com/mwarkentin/django-watchman&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Genera una url para mostrar el estado de algunos backends (cache, database, email).&lt;/p&gt;
&lt;h4&gt;22. Django Simple Captcha&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/mbi/django-simple-captcha"&gt;https://github.com/mbi/django-simple-captcha&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Generador de captchas simples.&lt;/p&gt;
&lt;h4&gt;23. Django uuslug&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/un33k/django-uuslug"&gt;https://github.com/un33k/django-uuslug&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Generador de slug's unicode únicos.&lt;/p&gt;
&lt;h4&gt;24. Supervisor&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/Supervisor/supervisor"&gt;https://github.com/Supervisor/supervisor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Sistema de control de procesos para UNIX. Lo he utilizado para monitorizar y reiniciar automáticamente algunos demonios (uwsgi, colas).&lt;/p&gt;
&lt;h4&gt;25. Fabric&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://www.fabfile.org/"&gt;http://www.fabfile.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Herramienta de línea de comandos que facilita tanto el despliegue de aplicaciones como las tareas de administración de un sistema.&lt;/p&gt;
&lt;h4&gt;26. uwsgi&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://uwsgi-docs.readthedocs.org/en/latest/"&gt;https://uwsgi-docs.readthedocs.org/en/latest/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Servidor de aplicaciones para python (soporta otros lenguajes).
Lo utilizo generalmente en conjunto con nginx como servidor web.&lt;/p&gt;
&lt;h4&gt;27.  Pillow&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/python-pillow/Pillow"&gt;https://github.com/python-pillow/Pillow&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Fork de &lt;em&gt;PIL&lt;/em&gt;, es una librería de manipulación de imágenes de python.&lt;/p&gt;
&lt;h4&gt;28. Unidecode&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/iki/unidecode"&gt;https://github.com/iki/unidecode&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Transliteración de texto unicode. Permite representar texto unicode en ASCII.&lt;/p&gt;
&lt;h4&gt;29. PEP 8&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/jcrocholl/pep8"&gt;https://github.com/jcrocholl/pep8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Herramienta de checkeo de estilos.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Referencias:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://elweb.co/33-projects-that-make-developing-django-apps-awesome/"&gt;33 projects that make developing django apps awesome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.leucinerichbio.com/why-is-django-awesome/"&gt;Why django is awesome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.djangopackages.com/"&gt;Django Packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rosarior/awesome-django"&gt;Awesome Django&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;No se me ocurre ninguna traducción adecuada.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref2:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Lo que ves es lo que obtienes (o al menos esa es la idea)&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="django"/><category term="django-apps"/><category term="python"/></entry><entry><title>Dropzone + Django</title><link href="https://alexanderae.com/dropzonejs-django.html" rel="alternate"/><published>2014-10-19T00:00:00-05:00</published><updated>2014-10-19T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2014-10-19:/dropzonejs-django.html</id><summary type="html">&lt;p&gt;DropzoneJS es una libreria opensource que permite subir archivos mediante la acción 'arrastrar y soltar' con la capacidad de mostrar vistas previas de las imágenes. Y django.. bueno, ya sabemos qué es django.&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://www.dropzonejs.com/"&gt;DropzoneJS&lt;/a&gt; es una libreria opensource que permite subir archivos mediante la acción "arrastrar y soltar" con la capacidad de mostrar vistas previas de las imágenes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Para este ejemplo utilicé &lt;a href="https://www.djangoproject.com/"&gt;django&lt;/a&gt; 1.7 + Python 2.7, pero no debería existir ningún problema para utilizarlo con otras versiones.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Los pasos que realicé, en resumen son:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Descargar el último &lt;em&gt;&lt;a href="https://github.com/enyo/dropzone/releases"&gt;release&lt;/a&gt;&lt;/em&gt; y añadirlo a nuestro directorio de archivos &lt;em&gt;static&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; Creamos el modelo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Picture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;imagen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Imagen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;imagenes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; .. el formulario:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
class PictureForm(forms.ModelForm):

    class Meta:
        model = Picture
        fields = (&amp;#39;imagen&amp;#39;,)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; .. la url:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^upload-picture/$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;upload_picture&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;upload_picture&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;5.&lt;/em&gt; .. el view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.shortcuts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render_to_response&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.forms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PictureForm&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;upload_picture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PictureForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FILES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;picture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PictureForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;upload_picture.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;6.&lt;/em&gt; Y por último, el template "upload_picture.html":&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::html
{%&lt;span class="w"&gt; &lt;/span&gt;load&lt;span class="w"&gt; &lt;/span&gt;static&lt;span class="w"&gt; &lt;/span&gt;%}

&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{% static &amp;#39;dropzone/css/dropzone.css&amp;#39; %}&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{% url &amp;#39;upload_picture&amp;#39; %}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dropzone&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;myDropzone&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;multipart/form-data&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;{%&lt;span class="w"&gt; &lt;/span&gt;csrf_token&lt;span class="w"&gt; &lt;/span&gt;%}

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fallback&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;multiple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{% static &amp;#39;dropzone/dropzone.min.js&amp;#39; %}&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;Dropzone.options.myDropzone&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                &lt;/span&gt;paramName:&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;imagen&amp;quot;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;el&lt;span class="w"&gt; &lt;/span&gt;nombre&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;nuestro&lt;span class="w"&gt; &lt;/span&gt;input
&lt;span class="w"&gt;                &lt;/span&gt;autoProcessQueue&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="w"&gt;                &lt;/span&gt;parallelUploads:&lt;span class="w"&gt; &lt;/span&gt;1,

&lt;span class="w"&gt;                &lt;/span&gt;init:&lt;span class="w"&gt; &lt;/span&gt;function()&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                    &lt;/span&gt;this.on(&amp;quot;success&amp;quot;,&lt;span class="w"&gt; &lt;/span&gt;function(file,&lt;span class="w"&gt; &lt;/span&gt;responseText)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                        &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;evento&lt;span class="w"&gt; &lt;/span&gt;lanzado&lt;span class="w"&gt; &lt;/span&gt;al&lt;span class="w"&gt; &lt;/span&gt;terminar&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;subir&lt;span class="w"&gt; &lt;/span&gt;las&lt;span class="w"&gt; &lt;/span&gt;imágenes&lt;span class="w"&gt; &lt;/span&gt;en&lt;span class="w"&gt; &lt;/span&gt;cola
&lt;span class="w"&gt;                        &lt;/span&gt;console.log(responseText);
&lt;span class="w"&gt;                    &lt;/span&gt;});
&lt;span class="w"&gt;                &lt;/span&gt;}
&lt;span class="w"&gt;            &lt;/span&gt;};
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Tan sencillo como eso.&lt;/p&gt;
&lt;p&gt;Nota: tomé como referencia a este post &lt;a href="http://amatellanes.wordpress.com/2013/11/05/dropzonejs-django-how-to-build-a-file-upload-form/"&gt;"DropzoneJs + Django: How to build a file upload"&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="django"/><category term="js"/></entry><entry><title>Archivo .hgignore para Android Studio</title><link href="https://alexanderae.com/hgignore-para-android-studio.html" rel="alternate"/><published>2014-05-29T00:00:00-05:00</published><updated>2014-05-29T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2014-05-29:/hgignore-para-android-studio.html</id><summary type="html">&lt;p&gt;Ya que no es buen hábito incluir archivos autogenerados en un proyecto, y necesitaba una lista de archivos a ignorar para usar conjuntamente con el Android Studio, encontré el siguiente archivo..&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ya que no es buen hábito incluir archivos autogenerados en un proyecto, y necesitaba una lista de archivos a ignorar para usar conjuntamente al Android Studio..&lt;/p&gt;
&lt;p&gt;Estuve buscando y terminé utilizando el siguiente archivo, el cual obtuve del blog: &lt;a href="http://digitalassassin.net/2013/12/hgignore-file-for-android-studio/"&gt;Digital Assassin&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# built application files&lt;/span&gt;
&lt;span class="nl"&gt;glob:&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;.apk&lt;/span&gt;
&lt;span class="nl"&gt;glob:&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;.ap_&lt;/span&gt;

&lt;span class="c1"&gt;# files for the dex VM&lt;/span&gt;
&lt;span class="nl"&gt;glob:&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;.dex&lt;/span&gt;

&lt;span class="c1"&gt;# Java class files&lt;/span&gt;
&lt;span class="nl"&gt;glob:&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="na"&gt;.class&lt;/span&gt;

&lt;span class="c1"&gt;# generated files&lt;/span&gt;
&lt;span class="nf"&gt;bin&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nf"&gt;gen&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;# Ignore gradle files&lt;/span&gt;
&lt;span class="na"&gt;.gradle&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;# Local configuration file (sdk path, etc)&lt;/span&gt;
&lt;span class="nf"&gt;local.properties&lt;/span&gt;

&lt;span class="c1"&gt;# Proguard folder generated by Eclipse&lt;/span&gt;
&lt;span class="nf"&gt;proguard&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;# Android Studio&lt;/span&gt;
&lt;span class="na"&gt;.idea&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;workspace.xml&lt;/span&gt;
&lt;span class="na"&gt;.idea&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;tasks.xml&lt;/span&gt;
&lt;span class="na"&gt;.iws&lt;/span&gt;
&lt;span class="na"&gt;.DS_Store&lt;/span&gt;

&lt;span class="c1"&gt;# linux&lt;/span&gt;
&lt;span class="na"&gt;.directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="blog"/><category term="android"/><category term="mercurial"/></entry><entry><title>Servidor en django para Google Cloud Messaging para Android (II)</title><link href="https://alexanderae.com/django-android-google-cloud-messaging-server-2.html" rel="alternate"/><published>2014-05-27T00:00:00-05:00</published><updated>2014-05-27T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2014-05-27:/django-android-google-cloud-messaging-server-2.html</id><summary type="html">&lt;p&gt;En la &lt;a href="/django-android-google-cloud-messaging-server-1.html"&gt;primera parte&lt;/a&gt; mostré el cómo implementar un servidor en django que interactúe con google cloud messages. En esta segunda parte, queda el implementar un cliente en Android que será quien reciba las notificaciones.&lt;/p&gt;
&lt;p&gt;Las instrucciones siguientes, toman como referencia el ejemplo publicado en la &lt;a href="http://developer.android.com/google/gcm/client.html"&gt;documentación&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Paso 3: Implementando el cliente en android&lt;/h4&gt;
&lt;p&gt;Creamos un nuevo proyecto cuyo nombre de paquete debe ser el que configuramos en el archivo settings.py del proyecto en django. Luego:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Añadimos &lt;em&gt;Google …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;En la &lt;a href="/django-android-google-cloud-messaging-server-1.html"&gt;primera parte&lt;/a&gt; mostré el cómo implementar un servidor en django que interactúe con google cloud messages. En esta segunda parte, queda el implementar un cliente en Android que será quien reciba las notificaciones.&lt;/p&gt;
&lt;p&gt;Las instrucciones siguientes, toman como referencia el ejemplo publicado en la &lt;a href="http://developer.android.com/google/gcm/client.html"&gt;documentación&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Paso 3: Implementando el cliente en android&lt;/h4&gt;
&lt;p&gt;Creamos un nuevo proyecto cuyo nombre de paquete debe ser el que configuramos en el archivo settings.py del proyecto en django. Luego:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Añadimos &lt;em&gt;Google Play Services&lt;/em&gt; a las dependencias, y en caso de que estemos utilizando graddle, el archivo &lt;em&gt;build.gradle&lt;/em&gt; debería terminar con una sección similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;dependencies {
  compile &amp;quot;com.google.android.gms:play-services:3.1.+&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; Añadimos los permisos al archivo &lt;em&gt;manifest&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::xml
&amp;lt;uses-permission android:name=&amp;quot;android.permission.INTERNET&amp;quot; /&amp;gt;
&amp;lt;uses-permission android:name=&amp;quot;android.permission.GET_ACCOUNTS&amp;quot; /&amp;gt;
&amp;lt;uses-permission android:name=&amp;quot;android.permission.WAKE_LOCK&amp;quot; /&amp;gt;
&amp;lt;uses-permission android:name=&amp;quot;com.google.android.c2dm.permission.RECEIVE&amp;quot; /&amp;gt;

&amp;lt;permission android:name=&amp;quot;com.alexanderae.demo_gcm.permission.C2D_MESSAGE&amp;quot;
    android:protectionLevel=&amp;quot;signature&amp;quot; /&amp;gt;
&amp;lt;uses-permission android:name=&amp;quot;com.alexanderae.demo_gcm.permission.C2D_MESSAGE&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Los dos últimos bloques son para prevenir que alguna aplicación ajena a la nuestra registre o reciba los mensajes que van dirigidos a nuestra app.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; También en el &lt;em&gt;manifest&lt;/em&gt;, debemos de añadir un &lt;em&gt;BroadcastReceiver&lt;/em&gt; que capturará el evento de "recibir una notificación" y un Servicio que será lanzado por dicho &lt;em&gt;receiver&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;receiver&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;.GcmBroadcastReceiver&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;android:permission=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;com.google.android.c2dm.permission.SEND&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;com.google.android.c2dm.intent.RECEIVE&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;com.alexanderae.demo_gcm&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/receiver&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;.GcmIntentService&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dentro del bloque &lt;em&gt;application&lt;/em&gt; debemos incluir la versión de &lt;em&gt;google play services&lt;/em&gt; a utilizar. Para ello, añadimos el siguiente bloque:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;xml&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;android&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;com.google.android.gms.version&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;android&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;@integer/google_play_services_version&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; Creamos una nueva clase que contendrá al receiver que habíamos declarado en el manifest (&lt;em&gt;GcmBroadcastReceiver.java&lt;/em&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
&lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GcmBroadcastReceiver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WakefulBroadcastReceiver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Override&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;onReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Intent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Especificamos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;servicio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;que&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manejará&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;evento&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ComponentName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ComponentName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPackageName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;GcmIntentService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Inicia&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;servicio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manteniendo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;al&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dispositivo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;despierto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;startWakefulService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;setResultCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Activity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESULT_OK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;5.&lt;/em&gt; Creamos el servicio declarado también en el manifest (&lt;em&gt;GcmIntentService.java&lt;/em&gt;).
Para este ejemplo, nuestra acción a realizar será el lanzar una notificación:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
&lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GcmIntentService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IntentService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NotificationManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mNotificationManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GcmIntentService&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GcmIntentService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GcmIntentService&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Override&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;onHandleIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Intent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getExtras&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gcm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;El&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parámetro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getMessageType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;debe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enviado&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;por&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BroadcastReceiver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;messageType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gcm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getMessageType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Filtramos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;los&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mensajes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basados&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tipo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aparentemente&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;un&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;futuro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cercano&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;se&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;añadirán&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tipos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mensajes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;MESSAGE_TYPE_SEND_ERROR&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Send error: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;MESSAGE_TYPE_DELETED&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Deleted messages on server: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Si&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;es&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;un&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mensaje&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ejecutamos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;acción&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deseada&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;MESSAGE_TYPE_MESSAGE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Lanzamos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;la&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notificación&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Received: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Received: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Liberamos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bloqueo&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;GcmBroadcastReceiver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completeWakefulIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;modo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ejemplo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lanzamos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;una&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notificación&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;al&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;recibir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mensaje&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Podemos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;realizar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cualquier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;otra&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;acción&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;según&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nuestras&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;necesidades&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mNotificationManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NotificationManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getSystemService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION_SERVICE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;PendingIntent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contentIntent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PendingIntent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MainActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;NotificationCompat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NotificationCompat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setSmallIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_launcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setContentTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Notificación GCM&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NotificationCompat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigTextStyle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bigText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setContentText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setContentIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentIntent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mNotificationManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;6.&lt;/em&gt; En nuestro &lt;em&gt;MainActivity&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;Agregamos algunas variables y constantes a utilizar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
&lt;span class="n"&gt;TextView&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mDisplay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// debemos añadir un TextView al layout&lt;/span&gt;
&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gcm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PLAY_SERVICES_RESOLUTION_REQUEST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID asignado por GCM para el dispositivo&lt;/span&gt;
&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MainActivity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SENDER_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;160161620856&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID del Proyecto obtenido de la consola de desarrolladores&lt;/span&gt;
&lt;span class="c1"&gt;// de Google&lt;/span&gt;
&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NAMESPACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://192.168.1.35:8001/gcm/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// URL del servidor GCM (django)&lt;/span&gt;

&lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PROPERTY_REG_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;registration_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PROPERTY_APP_VERSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;appVersion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En el &lt;em&gt;onCreate&lt;/em&gt; verificamos que &lt;em&gt;&lt;a href="http://developer.android.com/google/play-services/index.html"&gt;Google Play Services&lt;/a&gt;&lt;/em&gt; esté instalado, en caso afirmativo, continuamos recuperando el &lt;em&gt;código de registro&lt;/em&gt;, caso contrario, llamamos a la función &lt;em&gt;registerInBackground&lt;/em&gt; para crear un nuevo código.&lt;/p&gt;
&lt;p&gt;Se nos recomienda que verifiquemos que &lt;em&gt;GPS&lt;/em&gt; (Google Play Services) esté instalado en cada llamada al evento &lt;em&gt;onResume&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
&lt;span class="nv"&gt;@Override&lt;/span&gt;
&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;setContentView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activity_main&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;mDisplay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getApplicationContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Verifica&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;que&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Google&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Play&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;esté&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;instalado&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkPlayServices&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;gcm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getRegistrationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;registerInBackground&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;No se encontró Google Play Services.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;@Override&lt;/span&gt;
&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;onResume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onResume&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;checkPlayServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Agregamos las funciones que verifican la instalación de &lt;em&gt;GPS&lt;/em&gt;, la que obtiene el código de registro (&lt;em&gt;getRegistrationId&lt;/em&gt; + &lt;em&gt;getGCMPreferences&lt;/em&gt;) y la que obtiene la versión de la aplicación (&lt;em&gt;getAppVersion&lt;/em&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;java&lt;/span&gt;
&lt;span class="c1"&gt;// Verifica que Google Play Services esté instalado.&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;checkPlayServices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resultCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GooglePlayServicesUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isGooglePlayServicesAvailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ConnectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GooglePlayServicesUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isUserRecoverableError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultCode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;GooglePlayServicesUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getErrorDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;PLAY_SERVICES_RESOLUTION_REQUEST&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Dispositivo no soportado!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Obtiene el código de registro&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getRegistrationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SharedPreferences&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getGCMPreferences&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registrationId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROPERTY_REG_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registrationId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Registro no encontrado&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Verifica si la aplicación fue actualizada, en cuyo caso, se debe volver a generar&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// un código de registro.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registeredVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROPERTY_APP_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MIN_VALUE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;currentVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getAppVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registeredVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;La versión de la aplicación ha cambiado.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registrationId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Obtiene el código de registro desde los SharedPreferences&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SharedPreferences&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getGCMPreferences&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getSharedPreferences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MainActivity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getSimpleName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODE_PRIVATE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Obtiene la versión actual de la aplicación&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getAppVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;PackageInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;packageInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPackageManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPackageInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPackageName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;packageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versionCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PackageManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NameNotFoundException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No se encontró el nombre del paquete: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por último, añadimos el &lt;em&gt;AsyncTask&lt;/em&gt; que realiza el registro en &lt;em&gt;GCM&lt;/em&gt; (registerInBackground), la función que envía el ID a nuestro servidor (&lt;em&gt;sendRegistrationIdToBackend&lt;/em&gt;) y la que almacena el código en los SharedPreferences (&lt;em&gt;storeRegistrationId&lt;/em&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registerInBackground&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RegistroGCM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AsyncTask&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Override&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;doInBackground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Void&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gcm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;gcm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GoogleCloudMessaging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gcm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SENDER_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Device registered, registration ID=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Enviamos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;código&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nuestro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;servidor&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;sendRegistrationIdToBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Almacenamos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;código&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registro&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;storeRegistrationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error :&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Si&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;un&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;se&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;debe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;informar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;al&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;usuario&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;que&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;realize&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;una&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;acción&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;adecuada&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Override&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;onPostExecute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;mDisplay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;RegistroGCM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RegistroGCM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;registro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sendRegistrationIdToBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;url_registro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NAMESPACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;registra-usuario?usuario_id=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DefaultHttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_registro&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;response: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error de registro en mi servidor: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getCause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; || &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Almacena&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;código&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;los&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SharedPreferences&lt;/span&gt;
&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;storeRegistrationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SharedPreferences&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getGCMPreferences&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getAppVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Saving regId on app version &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Editor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;putString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROPERTY_REG_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;putInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROPERTY_APP_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y con ello, ya podríamos realizar las pruebas respectivas desde nuestro servidor en django.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Nota:&lt;/em&gt; No he creado un repositorio para este proyecto en android, pero ya que es prácticamente idéntico al de la documentaciñón, se puede consultar el &lt;a href="https://code.google.com/p/gcm/source/browse/"&gt;repositorio de google&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="django"/><category term="android"/><category term="google-cloud-messaging"/></entry><entry><title>Servidor en django para Google Cloud Messaging para Android (I)</title><link href="https://alexanderae.com/django-android-google-cloud-messaging-server-1.html" rel="alternate"/><published>2014-05-26T00:00:00-05:00</published><updated>2014-05-26T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2014-05-26:/django-android-google-cloud-messaging-server-1.html</id><summary type="html">&lt;p&gt;&lt;a href="http://developer.android.com/google/gcm/index.html"&gt;Google Cloud Messaging&lt;/a&gt;, para quien aun tenga dudas, es un servicio de google que nos permite enviar&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; mensajes a los usuarios de nuestra aplicación en &lt;em&gt;Android&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Entre otros, podríamos necesitar esta característica para enviar notificaciones, por ejemplo informar sobre la publicación de nuevas ofertas sobre alguno de nuestros productos o incluso para que el dispositivo ejecute ciertas acciones como enviar un mensaje de texto o informar sobre su posición entre muchas otras opciones.&lt;/p&gt;
&lt;p&gt;¿Qué debemos hacer para implementar este …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://developer.android.com/google/gcm/index.html"&gt;Google Cloud Messaging&lt;/a&gt;, para quien aun tenga dudas, es un servicio de google que nos permite enviar&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; mensajes a los usuarios de nuestra aplicación en &lt;em&gt;Android&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Entre otros, podríamos necesitar esta característica para enviar notificaciones, por ejemplo informar sobre la publicación de nuevas ofertas sobre alguno de nuestros productos o incluso para que el dispositivo ejecute ciertas acciones como enviar un mensaje de texto o informar sobre su posición entre muchas otras opciones.&lt;/p&gt;
&lt;p&gt;¿Qué debemos hacer para implementar este servicio?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Obtener un API key mediante la &lt;a href="https://cloud.google.com/console"&gt;consola de desarrolladores de google&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Implementar un servidor, el cual se encargará de enviar la información a nuestra aplicación en android. Nosotros usaremos django.&lt;/li&gt;
&lt;li&gt;Escribir el cliente en android que será quien reciba la información.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4&gt;Paso 1: Obteniendo un API Key&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Abrimos la &lt;a href="https://cloud.google.com/console"&gt;consola de desarrolladores&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Creamos un nuevo proyecto.&lt;/li&gt;
&lt;li&gt;Al crear el proyecto, obtendremos un número único. Dicho número será utilizado luego como el ID del servidor que envia el mensaje (GCM Sender ID).&lt;/li&gt;
&lt;li&gt;Ingresamos al menú &lt;em&gt;"API's y autenticación"&lt;/em&gt;, allí se nos listarán las API's de google disponibles. Para nuestro caso, activaremos la que dice "Google Cloud Messaging for Android"&lt;/li&gt;
&lt;li&gt;Ingresamos al submenú &lt;em&gt;"Credenciales"&lt;/em&gt; de "API's y autenticación".&lt;/li&gt;
&lt;li&gt;En el apartado &lt;em&gt;"Acceso a API Pública"&lt;/em&gt;, clickeamos la opción de "Crear una clave nueva" y escogemos la del tipo "Clave de Servidor".&lt;/li&gt;
&lt;li&gt;En el nuevo cuadro de diálogo debemos de ingresar la IP de nuestro servidor, para que el API solo acepte conexiones desde allí. Tal y como nos recomienda google, por motivos de prueba podemos ingresar: &lt;em&gt;0.0.0.0/0&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;En la nueva página mostrada debemos de copiar la "Clave de la API" que será la que usaremos para autenticar a nuestro servidor de aplicación.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4&gt;Paso 2: Implementando el servidor en django&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; En un nuevo proyecto o uno ya existente, creamos una nueva aplicación que será la que contenga al servidor GCM.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; En el archivo &lt;em&gt;models.py&lt;/em&gt; de nuestra aplicación creamos un modelo &lt;em&gt;UsuarioAndroid&lt;/em&gt; que almacenará a cada dispositivo registrado, quedando de la siguiente manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;UsuarioAndroid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;android_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ID Android&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Usuario Android&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Usuarios Android&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__unicode__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Al mostrar el usuario, solo mostraremos los 16 primeros caracteres&lt;/span&gt;
        &lt;span class="c1"&gt;# del código asignado.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;android_id&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; A modo opcional, registramos el modelo en un archivo &lt;em&gt;admin.py&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.contrib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UsuarioAndroid&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UsuarioAndroid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; En el archivo &lt;em&gt;views.py&lt;/em&gt; debemos de crear dos vistas, una de ellas será la que nos muestre el formulario por el cual enviaremos los mensajes y la otra será la que nuestra aplicación en &lt;em&gt;android&lt;/em&gt; utilizará internamente para registrar un nuevo dispositivo.&lt;/p&gt;
&lt;p&gt;También incluyo una función llamada &lt;em&gt;"gcm_envia_mensaje"&lt;/em&gt; que envía el mensaje por medio del API de GCM.
Se pueden consultar los parámetros en &lt;a href="http://developer.android.com/google/gcm/server.html#params"&gt;GCM Server: params&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.conf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.shortcuts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render_to_response&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;.models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UsuarioAndroid&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;envia_mensaje&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    Vista que muestra un formulario para seleccionar un usuario al que&lt;/span&gt;
&lt;span class="sd"&gt;    enviaremos un mensaje.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="c1"&gt;# listamos los usuarios registrados&lt;/span&gt;
    &lt;span class="n"&gt;usuarios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UsuarioAndroid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# en caso de que se suministre un usuario como parametro,&lt;/span&gt;
    &lt;span class="c1"&gt;# enviamos un mensaje.&lt;/span&gt;
    &lt;span class="n"&gt;usuario&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;usuario&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usuario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;gcm_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gcm_envia_mensaje&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usuario&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;envia_mensaje.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;registra_usuario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    Registra el android_id de un dispositivo celular.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;android_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;usuario_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;usuario&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UsuarioAndroid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_or_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;android_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;android_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="c1"&gt;# UTIL&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;gcm_envia_mensaje&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;android_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mensaje&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hola&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    Función que recibe el código de un dispositivo como parámetro y le envia un&lt;/span&gt;
&lt;span class="sd"&gt;    mensaje.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;usuario&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UsuarioAndroid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;android_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;android_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;UsuarioAndroid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;El usuario no existe&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://android.googleapis.com/gcm/send&amp;#39;&lt;/span&gt;

    &lt;span class="c1"&gt;# Parámetros a enviar&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;registration_ids&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usuario&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;android_id&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;restricted_package_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GCM_PACKAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;collapse_key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;server&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-server-name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;msg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mensaje&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Authorization&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;key=&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GCM_API_SERVER_KEY&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# iniciamos el request&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;STATUS_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Ok&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;JSON error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Ha fallado la autenticacion&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;501&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Servidor no disponible&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STATUS_CODES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;estado&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;detalle_de_estado&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;texto&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;estado&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ok&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;5.&lt;/em&gt; Añadimos el template html de nuestro formulario, en el archivo &lt;em&gt;envia_mensaje.html&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::html
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;usuarios&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;usuario&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;u&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;usuarios&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;u.android_id&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;u&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;submit&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Enviar mensaje&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;hay&lt;span class="w"&gt; &lt;/span&gt;usuarios&lt;span class="w"&gt; &lt;/span&gt;registrados&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;gcm_response&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;resultado&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Estado:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;gcm_response.estado&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Detalle&lt;span class="w"&gt; &lt;/span&gt;del&lt;span class="w"&gt; &lt;/span&gt;código&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;estado:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;gcm_response.detalle_de_estado&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Respuesta:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;gcm_response.texto&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;6.&lt;/em&gt; Por último, en nuestro archivo &lt;em&gt;settings.py&lt;/em&gt;, no olvidemos registrar nuestra aplicación en la variable &lt;em&gt;INSTALLED_APPS&lt;/em&gt;, asi como añadir las variables de configuración&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; de nuestro servidor y el nombre de nuestro paquete en Android:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
&lt;span class="gh"&gt;#&lt;/span&gt; GCM
GCM_PACKAGE = &amp;#39;com.alexanderae.demo_gcm&amp;#39;
GCM_API_SERVER_KEY = &amp;#39;AIzaSyCLK22vGoi6Gz663oIE9kTXZeTYC5lS4ww&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;El próximo paso será el implementar nuestra aplicación en android.&lt;/p&gt;
&lt;p&gt;Para leer a detalle sobre la implementación de un servidor GCM con django, se puede consultar la &lt;a href="http://developer.android.com/google/gcm/server.html"&gt;documentación&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pueden consultar el código del proyecto de prueba que realicé en un repositorio en &lt;a href="https://bitbucket.org/__alexander__/demo-django-gcm-server"&gt;bitbucket&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Nota:&lt;/em&gt; Este proyecto tiene como finalidad el mostrar cómo escribir un servidor para GCM desde cero, pero para fines más serios, es posible que se desee utilizar algún proyecto como: &lt;a href="https://github.com/bogdal/django-gcm"&gt;django-gcm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;También es posible recibir información desde el dispositivo mediante la misma conexión, pero para este ejemplo solo enviaré la información en un sentido.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Note que en un entorno de producción, las variables de configuración &lt;em&gt;no deben de mostrarse en el archivo settings&lt;/em&gt;, sino que debemos utilizar variables de entorno o algún otro método.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="django"/><category term="android"/><category term="google-cloud-messaging"/></entry><entry><title>Django: procesamiento condicional de vistas</title><link href="https://alexanderae.com/django-procesamiento-condicional-de-vistas.html" rel="alternate"/><published>2014-04-19T00:00:00-05:00</published><updated>2014-04-19T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2014-04-19:/django-procesamiento-condicional-de-vistas.html</id><summary type="html">&lt;p&gt;¿Qué pasá cuando un cliente en su afán por estar siempre al día, comienza a presionar la tecla de actualizar el navegador repetidas veces ?&lt;/p&gt;
&lt;p&gt;Y siendo aún más pesimistas (o tal vez realistas), ¿qué pasa si varios clientes realizan la misma acción una y otra vez posiblemente para ser los primeros en leer una noticia?&lt;/p&gt;
&lt;p&gt;Sucedería que nuestro servidor enviaría una y otra vez el mismo contenido, consumiendo ancho de banda, sin importar que nuestra página no tenga contenido nuevo …&lt;/p&gt;</summary><content type="html">&lt;p&gt;¿Qué pasá cuando un cliente en su afán por estar siempre al día, comienza a presionar la tecla de actualizar el navegador repetidas veces ?&lt;/p&gt;
&lt;p&gt;Y siendo aún más pesimistas (o tal vez realistas), ¿qué pasa si varios clientes realizan la misma acción una y otra vez posiblemente para ser los primeros en leer una noticia?&lt;/p&gt;
&lt;p&gt;Sucedería que nuestro servidor enviaría una y otra vez el mismo contenido, consumiendo ancho de banda, sin importar que nuestra página no tenga contenido nuevo o actualizado.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Etags y Last Modified:&lt;/h3&gt;
&lt;p&gt;Existen dos maneras de indicarle al cliente que nuestro contenido no ha cambiado:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/HTTP_ETag"&gt;&lt;strong&gt;Etags&lt;/strong&gt;&lt;/a&gt;: Son identificadores asignados al contenido de un recurso solicitado. Se utilizan para descubrir si el contenido en cuestión ha cambiado o no.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29"&gt;&lt;strong&gt;Last Modified&lt;/strong&gt;&lt;/a&gt;: Esta cabecera nos brinda la fecha de la última modificación de un recurso, con lo que podriamos realizar un condicional para buscar algun cambio con respecto al contenido que ya disponíamos en caché.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;Django y el procesamiento condicional de vistas&lt;/h3&gt;
&lt;p&gt;Django incorpora algunas herramientas para facilitarnos esta tarea. Estas son:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; &lt;strong&gt;El decorador condition&lt;/strong&gt;: Recibe dos parámetros opcionales los cuales representan a las funciones de cálculo del etag y el atributo last_modified.&lt;/p&gt;
&lt;p&gt;Por ejemplo, para una aplicación del tipo blog, que debe retornar contenido solo si la publicación se ha actualizado (en base al atributo modified):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="c1"&gt;# models.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Publicacion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ultima mod.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Titulo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Slug&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# views.py&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.views.decorators.http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;detalle_last_mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39; Calcula el valor de last_modified &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Publicacion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;


&lt;span class="nd"&gt;@condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_modified_func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;detalle_last_mod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;detalle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39; Vista que retorna una publicación &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Publicacion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;publicacion.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                  &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Para el ejemplo, creé un proyecto de prueba y cargué una publicación en el navegador por primera vez:&lt;/p&gt;
&lt;p&gt;&lt;img alt="request-1" src="/pictures/django-conditional-1.png" title="Request 01"&gt;&lt;/p&gt;
&lt;p&gt;Como se puede observar en los &lt;em&gt;headers&lt;/em&gt; del &lt;em&gt;request&lt;/em&gt;, todo está normal. Pero en el &lt;em&gt;response&lt;/em&gt;, hemos obtenido una cabecera que indica la fecha del last-modified.&lt;/p&gt;
&lt;p&gt;Entonces, si ejecutamos un nuevo request:&lt;/p&gt;
&lt;p&gt;&lt;img alt="request-2" src="/pictures/django-conditional-2.png" title="Request 02"&gt;&lt;/p&gt;
&lt;p&gt;podemos observar que en el &lt;em&gt;request&lt;/em&gt; se añadió una cabecera que dice &lt;em&gt;if-modified-since&lt;/em&gt; que básicamente se pregunta si la página fue actualizada desde la última fecha que recibimos.&lt;/p&gt;
&lt;p&gt;Y debido a que para mi ejemplo, no realicé ninguna modificación, recibimos el estado &lt;em&gt;304&lt;/em&gt; que es un &lt;em&gt;NOT MODIFIED&lt;/em&gt;, así como también el mismo parámetro last-modified.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; &lt;strong&gt;Los decoradores @etag y @last_modified&lt;/strong&gt;: Similares al método anterior, pero estos se concentran en solo una de las dos funciones (etag o last-modified).&lt;/p&gt;
&lt;p&gt;Por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="nv"&gt;@etag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etag_func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;detalle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; &lt;strong&gt;Middleware de procesamiento condicional&lt;/strong&gt; (middleware conditional processing): django incorpora también un middleware que genera etags de manera automática para &lt;em&gt;todas&lt;/em&gt; nuestras vistas, considerando que:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se aplica a todas las vistas&lt;/li&gt;
&lt;li&gt;Para calcular el valor del &lt;em&gt;etag&lt;/em&gt;, se basa en el &lt;em&gt;response&lt;/em&gt; generado por nuestra vista, por lo que si calcular nuestros &lt;em&gt;response&lt;/em&gt; son acciones "pesadas", tal vez nos convenga usar uno de los dos primeros métodos antes indicados.&lt;/li&gt;
&lt;li&gt;Solo son apropiados para pedidos del tipo &lt;em&gt;GET&lt;/em&gt; (no he probado si se refiere a que ignora otros métodos o si es que si nosotros usamos otros métodos, no deberíamos usar este método).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Como siempre, para mayor información, nada como la &lt;a href="https://docs.djangoproject.com/en/1.6/topics/conditional-view-processing/"&gt;documentación oficial&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="django"/><category term="optimizacion"/></entry><entry><title>Python: match vs findall vs search</title><link href="https://alexanderae.com/python-match-vs-findall-vs-search.html" rel="alternate"/><published>2014-01-26T00:00:00-05:00</published><updated>2014-01-26T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2014-01-26:/python-match-vs-findall-vs-search.html</id><summary type="html">&lt;p&gt;En el último par de semanas tuve que desarrollar un sistema web que interactuase con dispositivos GPS &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Uno de los módulos que implementé consistía en una función que tenia que parsear&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; los SMS recibidos, ya que estos eran el medio por el que el dispositivo retornaba los resultados.&lt;/p&gt;
&lt;p&gt;De manera resumida, el proceso que seguía era:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Recibir el SMS&lt;/li&gt;
&lt;li&gt;Comprobar si el SMS coincide con alguna de las expresiones regulares almacenadas&lt;/li&gt;
&lt;li&gt;Si se encuentra alguna coincidencia, se debe …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;En el último par de semanas tuve que desarrollar un sistema web que interactuase con dispositivos GPS &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Uno de los módulos que implementé consistía en una función que tenia que parsear&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; los SMS recibidos, ya que estos eran el medio por el que el dispositivo retornaba los resultados.&lt;/p&gt;
&lt;p&gt;De manera resumida, el proceso que seguía era:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Recibir el SMS&lt;/li&gt;
&lt;li&gt;Comprobar si el SMS coincide con alguna de las expresiones regulares almacenadas&lt;/li&gt;
&lt;li&gt;Si se encuentra alguna coincidencia, se debe ejecutar algun procedimiento en particular según el tipo de mensaje recibido.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;A modo de ejemplo, muestro 2 de las expresiones regulares que utilicé:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;^begin ok&lt;/p&gt;
&lt;p&gt;^Latitude:(?P&lt;lat&gt;[\d.SN]+)([\s]+)Longitude:(?P&lt;lon&gt;[\d.WE]+)([\s]+)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;La primera es la respuesta entregada al inicializar un dispositivo, mientras que la segunda se corresponde a una versión resumida del formato en el que se nos retorna la posición.&lt;/p&gt;
&lt;p&gt;Para saber si uno de los mensajes cumple con algunas de las expresiones regulares podríamos usar los métodos  &lt;a href="http://docs.python.org/2/library/re.html#re.search"&gt;search&lt;/a&gt;, &lt;a href="http://docs.python.org/2/library/re.html#re.match"&gt;match&lt;/a&gt; o &lt;a href="http://docs.python.org/2/library/re.html#re.findall"&gt;findall&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Los 3 métodos los podemos usar dentro de una sentencia &lt;em&gt;if&lt;/em&gt; para saber si se encuentra o no, una coincidencia. Entonces, si por ejemplo tenemos el siguiente SMS:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Latitude:12.013001S Longitude:077.079241W&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;podríamos escribir algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;regex_BEGIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^begin ok&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;regex_POSITION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^Latitude:(?P&amp;lt;lat&amp;gt;[\d\.SN]+)([\s]+)Longitude:(?P&amp;lt;lon&amp;gt;[\d\.WE]+)([\s]+)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Latitude:12.013001S Longitude:077.079241W&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# mensaje para este ejemplo&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;regex_BEGIN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Dispositivo inicializado&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;regex_POSITION&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Lectura de la posicion&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El código anterior lo podríamos reescribir cambiando el &lt;em&gt;match&lt;/em&gt; por un &lt;em&gt;search&lt;/em&gt; &lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;. Entonces, me pregunté cuál de los tres métodos podría ser la diferencia entre los tiempos de ejecución de estos 3 métodos.&lt;/p&gt;
&lt;p&gt;Usando timeit para medir tiempos, mi script indicando los tiempos obtenidos debajo de cada sentencia fue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;timeit&lt;/span&gt;

&lt;span class="c1"&gt;# Primera prueba&lt;/span&gt;

&lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;import re&lt;/span&gt;
&lt;span class="s1"&gt;s = &amp;#39;Latitude:12.013001S Longitude:077.079241W&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;r = re.compile(&amp;#39;^Latitude:(?P&amp;lt;lat&amp;gt;[\d\.SN]+)([\s]+)Longitude:(?P&amp;lt;lon&amp;gt;[\d\.WE]+)&amp;#39;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.match(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1.20556092262&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.search(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1.18701195717&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.findall(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1.58948493004&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;- - -&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# Segunda prueba&lt;/span&gt;

&lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;import re&lt;/span&gt;
&lt;span class="s1"&gt;s = &amp;#39;Latitude:12.013001S Longitude:077.079241W&amp;#39;*100&lt;/span&gt;
&lt;span class="s1"&gt;r = re.compile(&amp;#39;^Latitude:(?P&amp;lt;lat&amp;gt;[\d\.SN]+)([\s]+)Longitude:(?P&amp;lt;lon&amp;gt;[\d\.WE]+)&amp;#39;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.match(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1.18745613098&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.search(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1.19270586967&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.findall(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 49.8060359955&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;- - -&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# Tercera prueba&lt;/span&gt;

&lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;import re&lt;/span&gt;
&lt;span class="s1"&gt;s = &amp;#39;Latitude&amp;#39;*100&lt;/span&gt;
&lt;span class="s1"&gt;r = re.compile(&amp;#39;Latitude&amp;#39;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.match(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 0.517388105392&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.search(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 0.369539022446&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r.findall(s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 11.6244080067&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5&gt;Conclusiones&lt;/h5&gt;
&lt;p&gt;En los 9 casos realicé un millón iteraciones para que la diferencia entre cada método sea notable, por lo que si tan solo tuviera que realizar 1 iteración, posiblemente no habría mucha diferencia entre el método escogido.&lt;/p&gt;
&lt;p&gt;Si bien se observan mejores tiempos con los métodos &lt;em&gt;match&lt;/em&gt; y &lt;em&gt;search&lt;/em&gt;, debido a que hay muchos factores que afectan el tiempo de ejecución, recomendaría que cada uno evaluase el método que mejor se adapte a sus necesidades, aunque como mencioné, posiblemente para casos simples, esto no sería un motivo de preocupación.&lt;/p&gt;
&lt;p&gt;Siempre recordando que:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;" La optimización prematura es la raíz de todos los males"  &lt;em&gt;Donald Knuth&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6&gt;Referencias:&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://stackoverflow.com/questions/12803709/"&gt;re.match vs re.search performance difference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://stackoverflow.com/questions/4901523"&gt;What's a faster operation, re.match/search or str.find?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;De aquellos que se instalan en vehículos motorizados.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Analizar una cadena de texto para interpretar su contenido.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Inclusive por un findall u algún otro método.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="python"/><category term="regexp"/></entry><entry><title>Markdown en django</title><link href="https://alexanderae.com/markdown-en-django.html" rel="alternate"/><published>2013-12-07T10:18:00-05:00</published><updated>2013-12-07T10:18:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-12-07:/markdown-en-django.html</id><summary type="html">&lt;p&gt;Como ya sabemos, a partir de la versión 1.5 de &lt;a href="https://docs.djangoproject.com/"&gt;django&lt;/a&gt;, el módulo &lt;a href="https://docs.djangoproject.com/en/dev/releases/1.5/#django-contrib-markup"&gt;contrib.markup&lt;/a&gt; esta siendo depreciado. Por ello, si buscamos incluir markdown en nuestro proyecto debemos recurrir a paquetes de terceros o crear nuestro propio "template filter" (si deseamos escapar markdown como html), lo cual no es tan complicado.&lt;/p&gt;
&lt;p&gt;Lo primero a necesitar es un paquete que convierta markdown a html. De los dos que probé &lt;a href="https://pypi.python.org/pypi/Markdown"&gt;markdown&lt;/a&gt; y &lt;a href="https://pypi.python.org/pypi/markdown2"&gt;markdown2&lt;/a&gt; me quedo por el primero porque incluye algunas …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Como ya sabemos, a partir de la versión 1.5 de &lt;a href="https://docs.djangoproject.com/"&gt;django&lt;/a&gt;, el módulo &lt;a href="https://docs.djangoproject.com/en/dev/releases/1.5/#django-contrib-markup"&gt;contrib.markup&lt;/a&gt; esta siendo depreciado. Por ello, si buscamos incluir markdown en nuestro proyecto debemos recurrir a paquetes de terceros o crear nuestro propio "template filter" (si deseamos escapar markdown como html), lo cual no es tan complicado.&lt;/p&gt;
&lt;p&gt;Lo primero a necesitar es un paquete que convierta markdown a html. De los dos que probé &lt;a href="https://pypi.python.org/pypi/Markdown"&gt;markdown&lt;/a&gt; y &lt;a href="https://pypi.python.org/pypi/markdown2"&gt;markdown2&lt;/a&gt; me quedo por el primero porque incluye algunas opciones como &lt;em&gt;output_format&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Inicialmente nuestro &lt;a href="https://docs.djangoproject.com/en/dev/howto/custom-template-tags/"&gt;template filter&lt;/a&gt; puede lucir más o menos así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.template.defaultfilters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stringfilter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;markdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;

&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@register&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_safe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@stringfilter&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La función &lt;strong&gt;markdown&lt;/strong&gt;, entre otros, acepta algunos parámetros como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;safe_mode: permite deshabilitar html (a pesar del nombre, no es una garantía de ser un &lt;em&gt;modo seguro&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;html_replacement_text: texto a mostrar cuando safe_mode toma el valor de &lt;em&gt;replace&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;enable_attributes: controla la conversión de atributos html (desactivado cuando safe_mode vale &lt;em&gt;True&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;entre &lt;a href="http://pythonhosted.org/Markdown/reference.html"&gt;otras&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Y si el código markdown será ingresado por usuarios no confiables&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, debemos tener en cuenta que podría insertarse código malicioso.&lt;/p&gt;
&lt;p&gt;Podríamos mejorar un tanto la primera versión de nuestro filtro:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.template.defaultfilters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stringfilter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;markdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;

&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@register&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_safe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@stringfilter&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;safe_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;html5&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;enable_attributes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Este es el template html con el que hice mis pruebas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::jinja

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;my_tags&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Test&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hola&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;áéíóúñ&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;b&amp;gt;bold&amp;lt;/b&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;**test** &amp;lt;b&amp;gt;otro test&amp;lt;/b&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;*cursiva&amp;lt;b&amp;gt;bold&amp;lt;/b&amp;gt;*&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{@onclick=alert(&amp;quot;hi&amp;quot;)}es en serio?&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;mrkdwn&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Posiblemente la última versión que utilicé tampoco sea del todo segura, se pueden ver algunas discusiones sobre el tema aquí:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://stackoverflow.com/a/5359237/1472750"&gt;Best practice for allowing Markdown in Python, while preventing XSS attacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://security.stackexchange.com/a/14674"&gt;How do I use Markdown securely?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y como decía &lt;em&gt;Gene Spafford&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;El único sistema realmente seguro es aquel que está apagado, confinado en un bloque de hormigón y sellado en una habitación forrada de plomo con guardias armados – y aún así tengo mis dudas.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;en un sistema de comentarios por ejemplo&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="django"/><category term="markdown"/></entry><entry><title>PyFPDF</title><link href="https://alexanderae.com/pyfpdf-un-generador-de-archivos-pdf-en-python.html" rel="alternate"/><published>2013-11-03T00:00:00-05:00</published><updated>2013-11-03T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-11-03:/pyfpdf-un-generador-de-archivos-pdf-en-python.html</id><summary type="html">&lt;p&gt;PyFPDF es una librería&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; que genera documentos PDF mediante python. Es un port de &lt;a href="http://www.fpdf.org/"&gt;FPDF&lt;/a&gt;, el cual está escrito en PHP.&lt;/p&gt;
&lt;p&gt;Mientras revisaba el código de este paquete (el cual no es muy complicado de entender, al menos en parte) hice un ejemplo en el que se hace uso del:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;encabezado y pie de página&lt;/li&gt;
&lt;li&gt;propiedades del documento (autor, título)&lt;/li&gt;
&lt;li&gt;manejo de tipografías (tamaño de texto y estilos incluidos)&lt;/li&gt;
&lt;li&gt;una tabla con contenidos de productos, cantidades y precios (similar …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;PyFPDF es una librería&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; que genera documentos PDF mediante python. Es un port de &lt;a href="http://www.fpdf.org/"&gt;FPDF&lt;/a&gt;, el cual está escrito en PHP.&lt;/p&gt;
&lt;p&gt;Mientras revisaba el código de este paquete (el cual no es muy complicado de entender, al menos en parte) hice un ejemplo en el que se hace uso del:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;encabezado y pie de página&lt;/li&gt;
&lt;li&gt;propiedades del documento (autor, título)&lt;/li&gt;
&lt;li&gt;manejo de tipografías (tamaño de texto y estilos incluidos)&lt;/li&gt;
&lt;li&gt;una tabla con contenidos de productos, cantidades y precios (similar a una boleta de compra).&lt;/li&gt;
&lt;li&gt;saltos de linea, celdas, imágenes, etc..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En gran parte tomé como referencia el código de ejemplo incluido en las fuentes del paquete, los cuales se pueden encontrar aquí: &lt;a href="https://code.google.com/p/pyfpdf/"&gt;pyfpdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;El código de mi generador de listas de compra fue el que sigue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;fpdf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FPDF&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FPDF&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_author&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__alexander__&amp;lt;alexander.ayasca.esquives@gmail.com&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Django Store: Boleta #1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alias_nb_pages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pag_total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DroidSans&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DroidSans.ttf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uni&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DroidSans&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DroidSans-Bold.ttf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uni&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Encabezado&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;logo.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Logo&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DroidSans&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Arial bold 15&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Espacio en blanco de 8cm&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Django Store&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Titulo&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Salto de linea&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Pie de pagina&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# margen inferior de 1.5cm&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DroidSans&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# droidSans 8px&lt;/span&gt;
        &lt;span class="c1"&gt;# paginacion&lt;/span&gt;
        &lt;span class="n"&gt;paginado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Pagina &lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s1"&gt; de pag_total&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_no&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paginado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;parse_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csv_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Leemos los items del archivo csv y lo retornamos como un generador&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# recortamos los saltos de linea&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fancy_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Dibuja la tabla de productos y precios&lt;/span&gt;
        &lt;span class="c1"&gt;# colores, ancho de linea y tipografia&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_fill_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_fill_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_text_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_draw_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_line_width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# cabecera de la tabla&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;#  restauramos el color y la tipografia para el contenido principal&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_fill_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;235&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_text_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;#  Data&lt;/span&gt;
        &lt;span class="n"&gt;fill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Solo si tenemos los valores de las 4 columnas en el csv&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;L&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;fill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;
                &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="c1"&gt;# pie de pagina de la tabla&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Total:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;T&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# linea final&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;csv_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;boleta.csv&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Producto&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Cantidad&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;p x u&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Subtotal&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fancy_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;boleta_1.pdf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;F&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Se genera un pdf similar a:&lt;/p&gt;
&lt;p&gt;&lt;img alt="demo-img" src="/pictures/boleta.png"&gt;&lt;/p&gt;
&lt;p&gt;El código anterior, asi como la imagen del logo, tipografías, archivo csv de entrada y el pdf generado se pueden descargar desde &lt;a href="http://owncloud.alexanderae.com/public.php?service=files&amp;amp;t=389c33821500c1f4598db7ddf2540d27"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;También, mientras revisaba el código de pyfpdf decidí realizar algunas anotaciones de los métodos que se pueden usar (ya que la documentación no es muy extensa). Y aunque solo tengo una parte terminada, la estoy colocando en &lt;a href="/pages/pyfpdf.html"&gt;esta página&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Mantenida por Mariano Reingart&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="python"/><category term="pdf"/></entry><entry><title>nginx, uwsgi, django y virtualenv</title><link href="https://alexanderae.com/nginx-uwsgi-django-y-virtualenv.html" rel="alternate"/><published>2013-09-28T10:14:00-05:00</published><updated>2013-09-28T10:14:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-09-28:/nginx-uwsgi-django-y-virtualenv.html</id><summary type="html">&lt;p&gt;Este es un breve tutorial que indica como configurar django en un servidor de producción por medio de &lt;a href="http://nginx.org/"&gt;nginx&lt;/a&gt;, &lt;a href="http://projects.unbit.it/uwsgi/"&gt;uwsgi&lt;/a&gt; y &lt;a href="https://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En mi caso, seguí estas indicaciones en &lt;a href="https://www.webfaction.com/"&gt;webfaction&lt;/a&gt; sin ningún problema.&lt;/p&gt;
&lt;h4&gt;nginx: instalación&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;
&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;
&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;http_ssl_module&lt;/span&gt;
&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;uwsgi&lt;/h4&gt;
&lt;p&gt;instalamos …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Este es un breve tutorial que indica como configurar django en un servidor de producción por medio de &lt;a href="http://nginx.org/"&gt;nginx&lt;/a&gt;, &lt;a href="http://projects.unbit.it/uwsgi/"&gt;uwsgi&lt;/a&gt; y &lt;a href="https://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En mi caso, seguí estas indicaciones en &lt;a href="https://www.webfaction.com/"&gt;webfaction&lt;/a&gt; sin ningún problema.&lt;/p&gt;
&lt;h4&gt;nginx: instalación&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;
&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;
&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;http_ssl_module&lt;/span&gt;
&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;uwsgi&lt;/h4&gt;
&lt;p&gt;instalamos uwsgi&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    pip install uwsgi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;creamos un archivo de prueba:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
&lt;span class="gh"&gt;#&lt;/span&gt; test.py
def application(env, start_response):
    start_response(&amp;#39;200 OK&amp;#39;, [(&amp;#39;Content-Type&amp;#39;,&amp;#39;text/html&amp;#39;)])
    return &amp;quot;Hola mundo cruel&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;lanzamos uwsgi&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; indicando el archivo que creamos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    uwsgi --http :16517 --wsgi-file test.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y si todo va bien.. podemos continuar&lt;/p&gt;
&lt;h4&gt;django y virtualenv&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/h4&gt;
&lt;p&gt;creamos un entorno virtual, instalamos django y creamos un proyecto de prueba.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    mkvirtualenv django
    pip install django
    cd ~/projects
    # creamos un proyecto de prueba
    django-admin startproject myapp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;para que el entorno virtual sea reconocido adecuadamente, editamos el archivo wsgi.py de nuestro proyecto en django para que quede de la siguiente manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# wsgi.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;site&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;addsitedir&lt;/span&gt;
&lt;span class="n"&gt;addsitedir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/home/username/.envs/django_1_5/lib/python2.7/site-packages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/home/username/projects/myapp/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;/home/username/projects/myapp/myapp/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.core.wsgi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_wsgi_application&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DJANGO_SETTINGS_MODULE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;settings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_wsgi_application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y podemos probar uwsgi con nuestro proyecto en django:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    uwsgi --http :16517  --wsgi-file /home/username/projects/myapp/myapp/wsgi.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Si todo bien hasta ahora, podemos configurar nginx para que interactue con uwsgi.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uwsgi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h4&gt;nginx: configuración&lt;/h4&gt;
&lt;p&gt;editamos el archivo nginx.conf para que quede similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conf&lt;/span&gt;
&lt;span class="nx"&gt;worker_processes&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;error_log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;worker_connections&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;default_type&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;octet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log_format&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;$&lt;/span&gt;&lt;span class="nx"&gt;remote_addr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;remote_user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;time_local&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$request&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;$&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;body_bytes_sent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$http_referer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$http_user_agent&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$http_x_forwarded_for&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;access_log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;sendfile&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;keepalive_timeout&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;16517&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;nginx1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webfactional&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uwsgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;uwsgi_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;///home/username/projects/myapp/myapp.sock;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;ejecutamos el worker de uwsgi:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    uwsgi --master --workers 1 --wsgi-file /home/username/projects/myapp/myapp/wsgi.py --uwsgi-socket /home/username/projects/myapp/myapp.sock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;iniciamos nginx:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    ~/opt/nginx/sbin/nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y con esto concluye lo básico. Si me doy un tiempo, ampliaré esta entrada con lo necesario para que nginx maneje los archivos static y media de nuestro proyecto en django así como también el añadir a un gestor de procesos como supervisor para que maneje los workers de uwsgi.&lt;/p&gt;
&lt;h5&gt;Información adicional:&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://uwsgi.readthedocs.org/en/latest/tutorials/Django_and_nginx.html"&gt;Setting up Django and your web server with uWSGI and nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/uwsgi/"&gt;How to use Django with uWSGI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/widoyo/4406049"&gt;Deploy Django-Nginx-uwsgi pada web faction&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Note que estoy usando &lt;a href="http://virtualenvwrapper.readthedocs.org/en/latest/"&gt;virtualenvwrapper&lt;/a&gt; para administrar el entorno virtual (mkvirtualenv para crear el entorno virtual).&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;si estamos usando &lt;em&gt;webfaction&lt;/em&gt; debimos de haber creado, por medio de su &lt;a href="https://my.webfaction.com/applications"&gt;panel&lt;/a&gt;, una aplicación personalizada y utilizado el puerto que se nos asigno para la prueba con uwsgi o con nginx (en mi caso el 16517)&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="nginx"/><category term="django"/><category term="webfaction"/><category term="uwsgi"/></entry><entry><title>Owncloud en webfaction</title><link href="https://alexanderae.com/owncloud-en-webfaction.html" rel="alternate"/><published>2013-09-21T11:00:00-05:00</published><updated>2013-09-21T11:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-09-21:/owncloud-en-webfaction.html</id><summary type="html">&lt;p&gt;Después de probar owncloud en mi pc, en la cual utilizo &lt;a href="http://www.chakra-project.org/"&gt;chakra linux&lt;/a&gt;, intenté subirlo a webfaction, en donde tuve un pequeño problema.&lt;/p&gt;
&lt;p&gt;Los pasos que seguí para instalar owncloud en webfaction (incluida la solución a mi problema) fueron:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;En el panel de webfaction:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Registramos un nuevo dominio: owncloud.midominio.com&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Registramos una nueva aplicación del tipo: &lt;strong&gt;Static/CGI/PHP-5.x&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Registramos un nuevo website en el que linkeamos el dominio y la aplicación.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creamos una nueva base de datos …&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;Después de probar owncloud en mi pc, en la cual utilizo &lt;a href="http://www.chakra-project.org/"&gt;chakra linux&lt;/a&gt;, intenté subirlo a webfaction, en donde tuve un pequeño problema.&lt;/p&gt;
&lt;p&gt;Los pasos que seguí para instalar owncloud en webfaction (incluida la solución a mi problema) fueron:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;En el panel de webfaction:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Registramos un nuevo dominio: owncloud.midominio.com&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Registramos una nueva aplicación del tipo: &lt;strong&gt;Static/CGI/PHP-5.x&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Registramos un nuevo website en el que linkeamos el dominio y la aplicación.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creamos una nueva base de datos con su respectivo usuario (solo lo he probado con MySQL)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accedemos por ssh al servidor:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Descargamos owncloud:&lt;/p&gt;
&lt;p&gt;wget http://download.owncloud.org/community/owncloud-5.0.x.tar.bz2&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extraemos los archivos al directorio de nuestra aplicación: &lt;em&gt;~/webapps/owncloud&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cambiamos los permisos&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; de algunos directorios y archivos:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;permisos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;directorios&lt;/span&gt;
&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nx"&gt;webapps&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;owncloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xargs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setfacl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;apache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;
&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nx"&gt;webapps&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;owncloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xargs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setfacl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;permisos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archivos&lt;/span&gt;
&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nx"&gt;webapps&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;owncloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xargs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setfacl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;apache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nx"&gt;webapps&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;owncloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xargs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setfacl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Hasta este punto, ya somos capaces de acceder por medio del navegador a la url de nuestra aplicación y comenzar a realizar la configuración (primer usuario, conexión a la base de datos..)&lt;/p&gt;
&lt;p&gt;Pero si intentamos loguearnos desde algún cliente de escritorio o en mi caso, con la aplicación en android, obtendremos un error de credenciales.&lt;/p&gt;
&lt;p&gt;Por suerte (para mí), este error ya le había sucedido a otra persona&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;, la cual publicó su caso. Al parecer &lt;strong&gt;owncloud&lt;/strong&gt; utiliza mod_rewrite (un módulo de apache) para obtener el usuario y el password de las cabeceras HTTP cuando nos logueamos. Y en webfaction, este mecanismo esta desactivado.&lt;/p&gt;
&lt;p&gt;Entonces, lo que hay que hacer es:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Añadir al archivo .htacces de &lt;em&gt;owncloud&lt;/em&gt; una regla que redireccione la url inicial a una que contenga los parámetros de login y remover el header de Autorización que ya no usaremos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;RewriteCond %{HTTP:Authorization} ^Basic.*
RewriteRule ^(.*) $1?Authorization=%{HTTP:Authorization} [QSA,C]
RequestHeader unset Authorization
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Luego, para que &lt;em&gt;owncloud&lt;/em&gt; sepa que hacer con estos nuevos parámetros, hay que editar el archivo &lt;em&gt;~/webapps/owncloud/lib/base.php&lt;/em&gt;, y en la función init() añadir:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;

&lt;span class="c1"&gt;//sett http auth headers for Webfaction workaround&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Basic\s+(.*)$/i&amp;#39;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base64_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;PHP_AUTH_USER&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strip_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;PHP_AUTH_PW&lt;/span&gt;&lt;span class="p"&gt;&amp;#39;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strip_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;$password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y con ello ya estaría todo listo!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Actualización: 23/09/2013&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Owncloud necesita ejecutar tareas de fondo periódicamente (como buscar nuevas noticias si se usa la aplicación &lt;em&gt;news&lt;/em&gt;), para lo cual se cuenta con &lt;a href="http://doc.owncloud.org/server/5.0/admin_manual/configuration/background_jobs.html"&gt;3 opciones&lt;/a&gt;: Ajax, webcron y cron.&lt;/p&gt;
&lt;p&gt;En mi caso, utilizo cron, el cual para usarse en webfaction necesita que editemos el archivo de cron con el comando &lt;strong&gt;crontab -e&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Y añadiendo una entrada del tipo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="gs"&gt;*/20  *&lt;/span&gt;  &lt;span class="gs"&gt;*  *&lt;/span&gt;  * /usr/local/bin/php54 -f ~/webapps/owncloud/cron.php &amp;gt; $HOME/cron.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;con lo que ejecutaremos el cron.php cada 20 minutos y mantendremos un archivo log llamado cron.log.&lt;/p&gt;
&lt;p&gt;Nota: es posible que prefiramos usar algun otro editor al que se usa por defecto para manipular el archivo cron. Lo podemos cambiar ajustando la variable &lt;em&gt;EDITOR&lt;/em&gt;, por ejemplo: &lt;strong&gt;export EDITOR=nano&lt;/strong&gt;&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Visto en los &lt;a href="http://community.webfaction.com/questions/13195/setup-owncloud"&gt;foros de webfaction&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Matthias Blaicher publicó la solución en su &lt;a href="http://www.blaicher.com/2012/07/fixing-authentication-of-owncloud-for-webfaction/"&gt;blog&lt;/a&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="owncloud"/><category term="webfaction"/></entry><entry><title>Optimización de imágenes: Archivos PNG</title><link href="https://alexanderae.com/optimizacion-de-imagenes-archivos-png.html" rel="alternate"/><published>2013-07-07T20:00:00-05:00</published><updated>2013-07-07T20:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-07-07:/optimizacion-de-imagenes-archivos-png.html</id><summary type="html">&lt;h4&gt;Archivos PNG&lt;/h4&gt;
&lt;p&gt;Un problema recurrente al manejar este formato de archivos puede ser el hecho de que ocupen una mayor cantidad de espacio en disco y al ser junto a los archivos JPEG uno de los dos formatos más usados en el entorno web esto se puede traducir en mayor tiempo de carga para nuestros sitios web.&lt;/p&gt;
&lt;p&gt;Como tal vez ya sepamos, los &lt;a href="http://en.wikipedia.org/wiki/Portable_Network_Graphics"&gt;PNG&lt;/a&gt; son un formato de imágenes que soportan compresión sin pérdidas&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. Para entender el como funciona …&lt;/p&gt;</summary><content type="html">&lt;h4&gt;Archivos PNG&lt;/h4&gt;
&lt;p&gt;Un problema recurrente al manejar este formato de archivos puede ser el hecho de que ocupen una mayor cantidad de espacio en disco y al ser junto a los archivos JPEG uno de los dos formatos más usados en el entorno web esto se puede traducir en mayor tiempo de carga para nuestros sitios web.&lt;/p&gt;
&lt;p&gt;Como tal vez ya sepamos, los &lt;a href="http://en.wikipedia.org/wiki/Portable_Network_Graphics"&gt;PNG&lt;/a&gt; son un formato de imágenes que soportan compresión sin pérdidas&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. Para entender el como funciona la &lt;em&gt;optimización&lt;/em&gt; de las imágenes en este formato, debemos tomar en cuenta al proceso de &lt;em&gt;compresión&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;Compresión:&lt;/h4&gt;
&lt;p&gt;A modo de resumen, el archivo &lt;em&gt;png&lt;/em&gt; se comprime en dos fases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Filtrado: Una serie de filtros son escogidos para facilitar la compresión en la siguiente fase. Se utiliza un método de filtrado para ayudar a la predicción del color del pixel en base a sus vecinos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;El algoritmo de compresión utilizado en la segunda fase se denomina &lt;a href="http://en.wikipedia.org/wiki/DEFLATE_(algorithm)"&gt;deflate&lt;/a&gt; el cual es el mismo algoritmo utilizado por la libreria &lt;a href="http://en.wikipedia.org/wiki/Zlib"&gt;zlib&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Para mayor información, consulte la especificación de la norma &lt;a href="http://tools.ietf.org/html/rfc2083"&gt;ISO&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;Optimización:&lt;/h4&gt;
&lt;p&gt;La optimización de los archivos png se puede realizar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eliminando los bloques auxiliares de la cabecera, los cuales pueden corresponder a información de los valores gama, el color de fondo, entre otros&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;Utilizando una paleta de colores reducida (&lt;a href="http://en.wikipedia.org/wiki/Indexed_color"&gt;paleta indexada&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Optimizando los filtros linea a linea&lt;/li&gt;
&lt;li&gt;Optimizando la compresión mediante el método deflate.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;Herramientas de optimización&lt;/h4&gt;
&lt;p&gt;De las múltiples posibilidades, decidí probar 3: pngcrush, optipng y pngquant:&lt;/p&gt;
&lt;h5&gt;PNGCrush&lt;/h5&gt;
&lt;p&gt;Proyecto opensource que se ejecuta por medio de la línea de comandos. Itera sobre los filtros y los parámetros usados por el método de compresión deflate, realizando la compresión para cada combinación posible y de este modo escoger la mejor alternativa.&lt;/p&gt;
&lt;p&gt;Site: &lt;a href="http://pmt.sourceforge.net/pngcrush/index.html"&gt;pngcrush&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modo de Uso:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    pngcrush -rem allb origen.png pngcrush.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h5&gt;OptiPNG&lt;/h5&gt;
&lt;p&gt;Inspirado en base a pngcrush. Diseñado para ser mucho más veloz. A diferencia de &lt;em&gt;pngcrush&lt;/em&gt;, &lt;em&gt;optipng&lt;/em&gt; realiza las pruebas en memoria y escribe solo el resultado óptimo en el disco.&lt;/p&gt;
&lt;p&gt;Site: &lt;a href="http://optipng.sourceforge.net/"&gt;optipng&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modo de Uso:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    optipng -o 2 origen.png -out optipng.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h5&gt;PNGQuant&lt;/h5&gt;
&lt;p&gt;A diferencia de &lt;em&gt;pngcrush&lt;/em&gt; y &lt;em&gt;optipng&lt;/em&gt;, &lt;strong&gt;pngquant&lt;/strong&gt; reduce el número de colores a utilizar (a 256 o menos). Por lo que si bien podemos lograr un tamaño mucho menor, es posible que se produzca una pérdida en la calidad.&lt;/p&gt;
&lt;p&gt;Sitio: &lt;a href="http://pngquant.org/"&gt;pngquant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modo de Uso:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    pngquant origen.png --ext -pngquant.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ejemplo: &lt;a href="http://tinypng.org/"&gt;tinypng.org&lt;/a&gt; (optimizador online que usa pngquant)&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;Comparativa&lt;/h4&gt;
&lt;p&gt;Comparando resultados para dos capturas de pantalla en formato png (las capturas están alojadas en google drive):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Captura de pantalla del juego need for speed:&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Archivo&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tNTlPMmo5Vkk3R2c/edit?usp=sharing"&gt;nfs.png&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tWUhtUl9abjU2ZVE/edit?usp=sharing"&gt;pngcrush&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tc3Q5dDNYbi1vY2c/edit?usp=sharing"&gt;optipng&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tVlJDWGpHX0U5YjA/edit?usp=sharing"&gt;pngquant&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peso&lt;/td&gt;
&lt;td&gt;2 MB&lt;/td&gt;
&lt;td&gt;1.3 MB&lt;/td&gt;
&lt;td&gt;1.3 MB&lt;/td&gt;
&lt;td&gt;412 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Captura de pantalla de la terminal:&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Archivo&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tNTlPMmo5Vkk3R2c/edit?usp=sharing"&gt;consola.png&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tb1lzaXFQcnA3Wlk/edit?usp=sharing"&gt;pngcrush&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tRHZDSy1RZXRFRHc/edit?usp=sharing"&gt;optipng&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://docs.google.com/file/d/0B9sAAyxUlH2tb1lzaXFQcnA3Wlk/edit?usp=sharing"&gt;pngquant&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peso&lt;/td&gt;
&lt;td&gt;126.1 KB&lt;/td&gt;
&lt;td&gt;105.0 KB&lt;/td&gt;
&lt;td&gt;104.3 KB&lt;/td&gt;
&lt;td&gt;55.6 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h4&gt;Conclusiones&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Aunque en la comparativa tan solo se aprecia el &lt;em&gt;espacio en disco&lt;/em&gt; final, se debería de tomar en cuenta el &lt;em&gt;tiempo de procesamiento&lt;/em&gt; de cada herramienta.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Si bien &lt;strong&gt;pngquant&lt;/strong&gt; obtiene un tamaño considerablemente menor al original, se debe tener cuidado con su uso, ya que trabaja reduciendo la paleta de colores por lo que puede conllevar una pérdida en la calidad. (Para las dos imágenes mostradas el efecto es inapreciable)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Si mantener la calidad original es una prioridad, se recomienda el uso de &lt;strong&gt;optipng&lt;/strong&gt;. Y en caso de que nos podamos permitir el reducir "levemente" la calidad&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt; podemos considerar el uso de &lt;strong&gt;pngquant&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4&gt;Referencias&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;optipng: &lt;a href="http://optipng.sourceforge.net/pngtech/optipng.html"&gt;A guide to PNG optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;debianfacil.wordpress.com: &lt;a href="http://debianfacil.wordpress.com/2008/03/18/optimizando-y-reduciendo-imagenes-png/"&gt;optimizando-y-reduciendo-imagenes-png&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.libpng.org/pub/png/"&gt;Portable Network Graphics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://manpages.ubuntu.com/manpages/raring/man1/pngcrush.1.html"&gt;Manpages Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://linux.die.net/man/1/optipng"&gt;manual optipng&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Se denomina algoritmo de compresión sin pérdida a cualquier procedimiento de codificación que tenga como objetivo representar cierta cantidad de información utilizando u ocupando un espacio menor, siendo posible una reconstrucción exacta de los datos originales.
Véase: &lt;a href="http://en.wikipedia.org/wiki/Lossless_data_compression"&gt;lossless data compression&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Consulte &lt;a href="http://en.wikipedia.org/wiki/Portable_Network_Graphics#Ancillary_chunks"&gt;PNG: Bloques auxiliares&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Inapreciable para imágenes con pocos colores(&amp;lt;=256)&amp;#160;&lt;a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="optimizacion"/><category term="imagenes"/></entry><entry><title>Django Apps: uuslug</title><link href="https://alexanderae.com/django-apps-uuslug.html" rel="alternate"/><published>2013-06-23T23:00:00-05:00</published><updated>2013-06-23T23:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-06-23:/django-apps-uuslug.html</id><summary type="html">&lt;p&gt;Una de las características de &lt;strong&gt;&lt;a href="https://djangoproject.com"&gt;django&lt;/a&gt;&lt;/strong&gt; es el &lt;a href="https://docs.djangoproject.com/en/1.5/intro/overview/#design-your-urls"&gt;diseño elegante&lt;/a&gt; de las urls a diferencia de como otros frameworks o lenguajes puedan manejar este aspecto. Por ejemplo con asp o php podríamos tener&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;www.test.com/post.asp?u=142

www.test.com/post.php?u=142
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Mientras que en &lt;em&gt;django&lt;/em&gt; podríamos tener sin problema, urls amigables como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;www.test.com/post/142
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Nota: debemos considerar que utilizar id's en nuestras urls no es recomendable:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Hay quien opina que …&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;Una de las características de &lt;strong&gt;&lt;a href="https://djangoproject.com"&gt;django&lt;/a&gt;&lt;/strong&gt; es el &lt;a href="https://docs.djangoproject.com/en/1.5/intro/overview/#design-your-urls"&gt;diseño elegante&lt;/a&gt; de las urls a diferencia de como otros frameworks o lenguajes puedan manejar este aspecto. Por ejemplo con asp o php podríamos tener&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;www.test.com/post.asp?u=142

www.test.com/post.php?u=142
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Mientras que en &lt;em&gt;django&lt;/em&gt; podríamos tener sin problema, urls amigables como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;www.test.com/post/142
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Nota: debemos considerar que utilizar id's en nuestras urls no es recomendable:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Hay quien opina que facilita ataques por inyección sql. Aunque en django tenemos ese punto cubierto al utilizar correctamente los &lt;a href="https://docs.djangoproject.com/en/1.5/topics/security/#sql-injection-protection"&gt;querysets&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;También hay quienes creen que afecta al &lt;a href="https://en.wikipedia.org/wiki/Search_engine_optimization"&gt;SEO&lt;/a&gt;. En un &lt;a href="http://googlewebmastercentral.blogspot.com/2008/09/dynamic-urls-vs-static-urls.html"&gt;artículo de google&lt;/a&gt;, nos explican a más detalle el problema en cuestión.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A mi parecer el motivo más importante es por el tema de la &lt;a href="http://es.wikipedia.org/wiki/Usabilidad"&gt;usabilidad&lt;/a&gt;. Para el usuario las urls sencillas son más fáciles de copiar, recordar y también el encontrar lo que se busca. Tal y como vi en una respuesta de &lt;a href="http://stackoverflow.com/a/910741/1472750"&gt;stackoverflow&lt;/a&gt;, entre estas dos urls:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;www.test.com/page.php?u=85583&lt;/p&gt;
&lt;p&gt;www.test.com/solution-to-your-problem.php&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;un usuario tendería a escoger la segunda url por motivos que resultan obvios.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Entonces después de esta no tan breve introducción.. ¿cómo realizamos esto con django?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Django&lt;/strong&gt;, en su módulo &lt;em&gt;utils&lt;/em&gt; dispone de la función &lt;a href="https://docs.djangoproject.com/en/1.5/ref/utils/#django.utils.text.slugify"&gt;slugify&lt;/a&gt; para generar slugs&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; y en su módulo &lt;em&gt;models&lt;/em&gt; tenemos a &lt;a href="https://docs.djangoproject.com/en/1.5/ref/models/fields/#slugfield"&gt;SlugField&lt;/a&gt; para autogenerar un campo de este tipo.&lt;/p&gt;
&lt;p&gt;El problema aparece cuando por ejemplo generamos los campos &lt;em&gt;slug&lt;/em&gt; en base a el título de una noticia, la cual para el ejemplo se titula: &lt;em&gt;Explosión&lt;/em&gt; y pasado cierto tiempo aparece otra noticia llamada del mismo modo.&lt;/p&gt;
&lt;p&gt;La función que genera slug's por defecto en django tiene los dos siguientes problemas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No genera slugs para términos con caracteres no ascii, como lo son las tildes.&lt;/li&gt;
&lt;li&gt;No toma en cuenta que el término base pueda aparecer varias veces y al generar el mismo slug puede crearse un conflicto al acceder a la url.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El paquete que utilizo para solventar este inconveniente es &lt;a href="https://github.com/un33k/django-uuslug"&gt;django-uuslug&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Instalación&lt;/h4&gt;
&lt;p&gt;Tan sencillo como siempre:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install django-uulug
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;¿Cómo usar?&lt;/h4&gt;
&lt;p&gt;Para generar un slug cualquiera, podemos usar su propia función &lt;em&gt;slugify&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;uuslug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;slugify&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;This is a test ---&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;this-is-a-test&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;explosión&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;explosion&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y si deseamos autogener un slug único en un modelo, por ejemplo para el caso de las noticias:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;uuslug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuslug&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Noticia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__unicode__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuslug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Noticia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y con ello, para noticias con el mismo título se añade un número al final del slug para que queden como: noticia, noticia-1, etc.&lt;/p&gt;
&lt;h4&gt;urls&lt;/h4&gt;
&lt;p&gt;En nuestras urls tendremos algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
url(r&amp;#39;^noticia/(?P&amp;lt;slug&amp;gt;[-\w]+)$&amp;#39;, &amp;#39;views.noticia&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;para finalmente el poder utilizar las url's:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;noticia/explosion&lt;/p&gt;
&lt;p&gt;noticia/explosion-1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Si bien es el comportamiento por defecto, es posible user url's amigables en otros sistemas con alguno que otro hack. En wikipedia podremos revisar un artículo más extenso sobre &lt;a href="https://en.wikipedia.org/wiki/Clean_URL"&gt;url's amigables&lt;/a&gt; el cual incluye algunos ejemplos.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Un slug es un término periodístico que se refiere a los nombres o etiquetas cortas para algo.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="django"/><category term="django-apps"/></entry><entry><title>MongoDB: Autenticación y Autorización</title><link href="https://alexanderae.com/mongodb-autenticacion-autorizacion.html" rel="alternate"/><published>2013-06-02T00:00:00-05:00</published><updated>2013-06-02T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-06-02:/mongodb-autenticacion-autorizacion.html</id><summary type="html">&lt;p&gt;Una de las buenas prácticas en cuanto al aspecto de la seguridad en &lt;a href="http://mongodb.org/"&gt;mongodb&lt;/a&gt; corresponde al requerir autenticación para poder acceder a sus instancias.&lt;/p&gt;
&lt;p&gt;MongoDB provee soporte para el manejo de autenticación (uso de credenciales de acceso) y autorización (uso de roles).&lt;/p&gt;
&lt;p&gt;Podemos activar la autenticación de dos maneras:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Creando un usuario administrador y luego activamos la autenticación.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Activando la autenticación y creando un administrador&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A más detalle:&lt;/p&gt;
&lt;h5&gt;Creando un usuario administrador y luego activamos la autenticación&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Iniciamos &lt;strong&gt;mongod&lt;/strong&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1 …&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;Una de las buenas prácticas en cuanto al aspecto de la seguridad en &lt;a href="http://mongodb.org/"&gt;mongodb&lt;/a&gt; corresponde al requerir autenticación para poder acceder a sus instancias.&lt;/p&gt;
&lt;p&gt;MongoDB provee soporte para el manejo de autenticación (uso de credenciales de acceso) y autorización (uso de roles).&lt;/p&gt;
&lt;p&gt;Podemos activar la autenticación de dos maneras:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Creando un usuario administrador y luego activamos la autenticación.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Activando la autenticación y creando un administrador&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A más detalle:&lt;/p&gt;
&lt;h5&gt;Creando un usuario administrador y luego activamos la autenticación&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Iniciamos &lt;strong&gt;mongod&lt;/strong&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; (por defecto la autorización está desactivada)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accedemos a la interfaz interactiva (&lt;a href="http://docs.mongodb.org/manual/reference/program/mongo/"&gt;mongo&lt;/a&gt;)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;MongoDB shell version: 2.4.3
connecting to: test
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cambiamos la base de datos de test a la de administración:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt; use admin
switched to db admin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creamos un usuario administrador (aquel que cuenta con uno de los dos siguientes roles: &lt;em&gt;userAdmin&lt;/em&gt; o &lt;em&gt;userAdminAnyDatabase&lt;/em&gt;)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;db.addUser({user:&amp;quot;admin&amp;quot;, pwd: &amp;quot;admin&amp;quot;, roles: [&amp;quot;userAdminAnyDatabase&amp;quot;]}&lt;/span&gt;
&lt;span class="k"&gt;...&lt;/span&gt; )
{
        &amp;quot;user&amp;quot; : &amp;quot;admin&amp;quot;,
        &amp;quot;pwd&amp;quot; : &amp;quot;7c67ef13bbd4cae106d959320af3f704&amp;quot;,
        &amp;quot;roles&amp;quot; : [
                &amp;quot;userAdminAnyDatabase&amp;quot;
        ],
        &amp;quot;_id&amp;quot; : ObjectId(&amp;quot;51ac0cca34cad99e8cff6092&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Con lo anterior, podemos reiniciar &lt;em&gt;mongod&lt;/em&gt; con la autenticación activada:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mongod --auth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A modo de ejemplo, supongamos que disponemos de la base de datos Hospital y deseamos añadir un objeto a la colección pacientes (considerando que hemos activado la autenticación):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Hospital&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;switched&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Hospital&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;.&lt;span class="nv"&gt;pacientes&lt;/span&gt;.&lt;span class="nv"&gt;insert&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;{&lt;span class="s2"&gt;&amp;quot;nombre&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;alexander&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edad&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;}&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;authorized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Hospital&lt;/span&gt;.&lt;span class="nv"&gt;pacientes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Observamos que no disponemos permisos, asi que primero nos loguearemos en admin, cambiaremos a la bbdd Hospital y crearemos un nuevo usuario para esta otra bbdd:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;    &amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;use admin&lt;/span&gt;
    switched to db admin
&lt;span class="k"&gt;    &amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;db.auth(&amp;quot;admin&amp;quot;, &amp;quot;admin&amp;quot;)&lt;/span&gt;
    1
&lt;span class="k"&gt;    &amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;use Hospital&lt;/span&gt;
    switched to db Hospital
&lt;span class="k"&gt;    &amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;db.addUser(&amp;quot;doctor&amp;quot;, &amp;quot;doctor&amp;quot;)&lt;/span&gt;
    {
        &amp;quot;user&amp;quot; : &amp;quot;doctor&amp;quot;,
        &amp;quot;readOnly&amp;quot; : false,
        &amp;quot;pwd&amp;quot; : &amp;quot;f830c9f104077d9b115e22fd26269824&amp;quot;,
        &amp;quot;_id&amp;quot; : ObjectId(&amp;quot;51ac13b73d47965438d38b65&amp;quot;)
    }
&lt;span class="k"&gt;    &amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;db.auth(&amp;quot;doctor&amp;quot;, &amp;quot;doctor&amp;quot;)&lt;/span&gt;
    1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por defecto este usuario tiene permisos de escritura, como observamos aquí:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;    &amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;db.pacientes.insert({&amp;quot;nombre&amp;quot;: &amp;quot;alexander&amp;quot;, &amp;quot;edad&amp;quot;: 23})&lt;/span&gt;
&lt;span class="k"&gt;    &amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;db.pacientes.find()&lt;/span&gt;
    { &amp;quot;_id&amp;quot; : ObjectId(&amp;quot;51ac13cd3d47965438d38b66&amp;quot;), &amp;quot;nombre&amp;quot; : &amp;quot;alexander&amp;quot;, &amp;quot;edad&amp;quot; : 23 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;Note que para añadir usuarios con roles específicos, debemos usar la sintaxis:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &amp;gt; db.addUser( { user: &amp;quot;&amp;lt;username&amp;gt;&amp;quot;, pwd: &amp;quot;&amp;lt;password&amp;gt;&amp;quot;,
    ... roles: [ &amp;quot;rol1&amp;quot;, &amp;quot;rol2&amp;quot; ] } )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En donde los roles pueden ser: read, readWrite, dbAdmin, userAdmin.. entre otros. Para más información consulte la &lt;a href="http://docs.mongodb.org/manual/reference/user-privileges/"&gt;documentación&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;¿Y el segundo método?&lt;/p&gt;
&lt;p&gt;El segundo método se aprovecha de la &lt;a href="http://docs.mongodb.org/manual/tutorial/add-user-administrator/#authenticate-with-full-administrative-access-via-localhost"&gt;excepción localhost&lt;/a&gt;: Si no existen usuarios para la base de datos de &lt;em&gt;admin&lt;/em&gt;, podemos conectarnos con permisos completos por medio de la interfaz en localhost.&lt;/p&gt;
&lt;p&gt;Y una vez accedido al sistema, los pasos para crear usuarios son los que ya se mostraron antes.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="http://docs.mongodb.org/manual/reference/program/mongod/"&gt;mongod&lt;/a&gt; es el proceso demonio primario de mongodb, controla las solicitudes de datos, administra formatos de datos y realiza varias tareas de administración.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="mongodb"/></entry><entry><title>Supervisor</title><link href="https://alexanderae.com/supervisor.html" rel="alternate"/><published>2013-05-28T00:00:00-05:00</published><updated>2013-05-28T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-05-28:/supervisor.html</id><summary type="html">&lt;p&gt;&lt;a href="http://supervisord.org/"&gt;Supervisor&lt;/a&gt; es un sistema del tipo cliente/servidor que nos facilita la administración de procesos en sistemas tipo unix.&lt;/p&gt;
&lt;p&gt;Consta de 2 componentes:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;supervisord&lt;/dt&gt;
&lt;dd&gt;Es quien actua como servidor y se encarga de iniciar los programas asociados, asi como responder a las peticiones de comandos de los clientes y reiniciar subprocesos crasheados o terminados.
  Utiliza un archivo de configuración el cual por defecto reside en &lt;em&gt;/etc/supervisord.conf&lt;/em&gt;&lt;/dd&gt;
&lt;dt&gt;supervisorctl&lt;/dt&gt;
&lt;dd&gt;Es por medio de quien el usuario interactua con supervisord. Con …&lt;/dd&gt;&lt;/dl&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://supervisord.org/"&gt;Supervisor&lt;/a&gt; es un sistema del tipo cliente/servidor que nos facilita la administración de procesos en sistemas tipo unix.&lt;/p&gt;
&lt;p&gt;Consta de 2 componentes:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;supervisord&lt;/dt&gt;
&lt;dd&gt;Es quien actua como servidor y se encarga de iniciar los programas asociados, asi como responder a las peticiones de comandos de los clientes y reiniciar subprocesos crasheados o terminados.
  Utiliza un archivo de configuración el cual por defecto reside en &lt;em&gt;/etc/supervisord.conf&lt;/em&gt;&lt;/dd&gt;
&lt;dt&gt;supervisorctl&lt;/dt&gt;
&lt;dd&gt;Es por medio de quien el usuario interactua con supervisord. Con él, es posible obtener el estado, detener o iniciar los subprocesos administrados.&lt;/dd&gt;
&lt;/dl&gt;
&lt;hr&gt;
&lt;h5&gt;Instalación y uso&lt;/h5&gt;
&lt;p&gt;Lo &lt;a href="http://supervisord.org/installing.html"&gt;instalamos&lt;/a&gt; con:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo pip install supervisor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Supervisor utiliza un archivo que contiene sus parámetros de configuración, por defecto ubicado en &lt;em&gt;/etc/supervisord.conf&lt;/em&gt;.
En caso de que no exista o deseemos copiar el archivo por defecto para utilizarlo desde otra ubicación, podemos ejecutar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;echo_supervisord_conf &amp;gt; /etc/supervisord.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El archivo de configuración luce similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;unix_http_server&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;supervisord&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;logfile&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;supervisord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;logfile_maxbytes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;logfile_backups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;loglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pidfile&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;supervisord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;supervisorctl&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;serverurl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nl"&gt;unix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;///&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rpcinterface:supervisor&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rpcinterface_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;rpcinterface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;make_main_rpcinterface&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y, para cada aplicación a controlar debemos añadir una sección &lt;em&gt;[program:program-name]&lt;/em&gt;, como por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    [program:mongodb]
    directory=/home/username/apps/mongodb-linux-x86_64-2.2.3
    command=/usr/bin/mongod --dbpath /home/username/apps/mongodb-linux-x86_64-2.2.3/data --logpath /home/username/apps/mongodb-linux-x86_64-2.2.3/log/mongodb.log --config /etc/mongodb.conf --logappend --auth
    autorestart=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;El ejemplo anterior añade una entrada de configuración para &lt;a href="http://www.mongodb.org/"&gt;mongodb&lt;/a&gt;, se pueden ver otros ejemplos &lt;a href="http://supervisord.org/subprocess.html#examples-of-program-configurations"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Entonces, podemos iniciar supervisor mediante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;supervisord
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y administrar los procesos con &lt;em&gt;supervisorctl&lt;/em&gt;, usando mongodb como ejemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Listar: supervisorctl&lt;/li&gt;
&lt;li&gt;Iniciar: supervisorctl start mongodb&lt;/li&gt;
&lt;li&gt;Detener: supervisorctl stop mongodb&lt;/li&gt;
&lt;li&gt;Reiniciar: supervisorctl restart mongodb&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En nuestro ejemplo, si iniciamos mongod con &lt;em&gt;supervisor&lt;/em&gt; y luego obtenemos su PID (id del proceso)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ps -aux|grep mongod
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;para poder terminarlo manualmente (asumiendo que su PID sea 2871):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kill 2871
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;observaremos que debido a que configuramos la entrada &lt;em&gt;[program:]&lt;/em&gt; con el valor de &lt;em&gt;autorestart=true&lt;/em&gt;, &lt;strong&gt;supervisor&lt;/strong&gt; se encargará de reiniciar nuestro proceso automaticamente (lo podemos comprobar volviendo a realizar un filtro de los procesos activos).&lt;/p&gt;
&lt;p&gt;Con lo cual ya tenemos varios excelentes motivos por los que usar esta herramienta.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Algunos buenos artículos sobre supervisor que encontré son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://edvanbeinum.com/how-to-install-and-configure-supervisord"&gt;How to install and configure Supervisord&lt;/a&gt;: Instalación y configuración básica&lt;/li&gt;
&lt;li&gt;&lt;a href="http://zerokspot.com/weblog/2012/06/17/sitemanagement-with-supervisord/"&gt;Managing site processes with Supervisord&lt;/a&gt;:&lt;/li&gt;
&lt;li&gt;&lt;a href="http://tech.cueup.com/blog/2013/03/08/running-daemons/"&gt;Daemon Showdown: Upstart vs. Runit vs. Systemd vs. Circus vs. God&lt;/a&gt;: comparativa con otros gestores de procesos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y no puede faltar, la documentación del proyecto: &lt;a href="http://supervisord.org/"&gt;docs&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="sysadmin"/><category term="linux"/></entry><entry><title>hghooks</title><link href="https://alexanderae.com/hghooks.html" rel="alternate"/><published>2013-05-19T19:00:00-05:00</published><updated>2013-05-19T19:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-05-19:/hghooks.html</id><summary type="html">&lt;h5&gt;¿Qué es un hook?&lt;/h5&gt;
&lt;p&gt;En &lt;a href="http://mercurial.selenic.com/"&gt;mercurial&lt;/a&gt;, al parecer también en git, y tal vez en algún otro scv&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; se le denomina hook a una acción programada para lanzarse al ocurrir algún evento en nuestro repositorio. Por ejemplo en &lt;em&gt;mercurial&lt;/em&gt; podemos controlar los eventos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;commit: después de terminar un commit&lt;/li&gt;
&lt;li&gt;precommit: antes de iniciar un commit&lt;/li&gt;
&lt;li&gt;update: después de que un update o merge halla finalizado en el repositorio local.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;entre otros ..&lt;/p&gt;
&lt;h5&gt;hghooks&lt;/h5&gt;
&lt;p&gt;hghooks es un conjunto de hooks para …&lt;/p&gt;</summary><content type="html">&lt;h5&gt;¿Qué es un hook?&lt;/h5&gt;
&lt;p&gt;En &lt;a href="http://mercurial.selenic.com/"&gt;mercurial&lt;/a&gt;, al parecer también en git, y tal vez en algún otro scv&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; se le denomina hook a una acción programada para lanzarse al ocurrir algún evento en nuestro repositorio. Por ejemplo en &lt;em&gt;mercurial&lt;/em&gt; podemos controlar los eventos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;commit: después de terminar un commit&lt;/li&gt;
&lt;li&gt;precommit: antes de iniciar un commit&lt;/li&gt;
&lt;li&gt;update: después de que un update o merge halla finalizado en el repositorio local.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;entre otros ..&lt;/p&gt;
&lt;h5&gt;hghooks&lt;/h5&gt;
&lt;p&gt;hghooks es un conjunto de hooks para usar con &lt;em&gt;mercurial&lt;/em&gt;. Nos ofrece de los siguientes módulos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;validación de código python por medio de &lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;pep8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;validación de código python por medio de pyflakes&lt;/li&gt;
&lt;li&gt;checkeo de sentencias pdm en python&lt;/li&gt;
&lt;li&gt;integración con track&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En mi caso, lo utilizo para validar código python según estilos pep8 antes de realizar un commit.&lt;/p&gt;
&lt;h5&gt;Instalación y Uso:&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Instalamos pep8 y hghook&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo easy_install pep8 hghooks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;En el archivo .hg/hgrc de nuestro repositorio en el que activaremos el hook, añadimos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[hooks]&lt;/span&gt;
&lt;span class="na"&gt;pretxncommit.pep8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:hghooks.code.pep8hook&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Listo, ahora cada vez que intentemos realizar un commit, se validará si antes nuestro código cumple con los estilos pep8&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Nota:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Para ignorar alguna regla de pep8, agregamos unas lineas como la siguientes a nuestro archivo &lt;a href="http://www.selenic.com/mercurial/hgrc.5.html"&gt;hgrc&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pep8&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;ignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E501&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E128&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Referencias:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;↳ Mercurial: The Definitive Guide: &lt;a href="http://hgbook.red-bean.com/read/handling-repository-events-with-hooks.html"&gt;Chapter 10 Handling repository events with hooks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;↳ &lt;a href="https://pypi.python.org/pypi/hghooks/"&gt;hghooks&lt;/a&gt;&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Sistema de control de versiones&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="python"/><category term="mercurial"/></entry><entry><title>Integrar flask-admin y flask-login</title><link href="https://alexanderae.com/integrar-flask-admin-y-flask-login.html" rel="alternate"/><published>2013-05-19T15:00:00-05:00</published><updated>2013-05-19T15:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-05-19:/integrar-flask-admin-y-flask-login.html</id><summary type="html">&lt;p&gt;Para quien aun no esté enterado, &lt;a href="http://flask.pocoo.org/"&gt;&lt;strong&gt;Flask&lt;/strong&gt;&lt;/a&gt; es un microframework web para &lt;strong&gt;python&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Flask cuenta con varias &lt;a href="http://flask.pocoo.org/extensions/"&gt;extensiones&lt;/a&gt;, de entre las cuales &lt;a href="https://github.com/mrjoes/flask-admin"&gt;flask-admin&lt;/a&gt; nos provee de una interfaz administrativa y &lt;a href="https://github.com/maxcountryman/flask-login"&gt;flask-login&lt;/a&gt; se encarga de manejar un sistema de autenticación para nuestras vistas.&lt;/p&gt;
&lt;p&gt;El problema aparece cuando necesitamos que la interfaz de administración incluya un sistema de autenticación (nos referimos a un login). En la &lt;a href="https://flask-admin.readthedocs.org/en/latest/quickstart/#authentication"&gt;documentación&lt;/a&gt; de flask-admin nos indican:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By default, administrative interface is visible to everyone, as Flask-Admin does …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;Para quien aun no esté enterado, &lt;a href="http://flask.pocoo.org/"&gt;&lt;strong&gt;Flask&lt;/strong&gt;&lt;/a&gt; es un microframework web para &lt;strong&gt;python&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Flask cuenta con varias &lt;a href="http://flask.pocoo.org/extensions/"&gt;extensiones&lt;/a&gt;, de entre las cuales &lt;a href="https://github.com/mrjoes/flask-admin"&gt;flask-admin&lt;/a&gt; nos provee de una interfaz administrativa y &lt;a href="https://github.com/maxcountryman/flask-login"&gt;flask-login&lt;/a&gt; se encarga de manejar un sistema de autenticación para nuestras vistas.&lt;/p&gt;
&lt;p&gt;El problema aparece cuando necesitamos que la interfaz de administración incluya un sistema de autenticación (nos referimos a un login). En la &lt;a href="https://flask-admin.readthedocs.org/en/latest/quickstart/#authentication"&gt;documentación&lt;/a&gt; de flask-admin nos indican:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By default, administrative interface is visible to everyone, as Flask-Admin does not make any assumptions about authentication system you’re using.&lt;/p&gt;
&lt;p&gt;If you want to control who can access administrative views and who can not, derive from the administrative view class and implement is_accessible method.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;O dicho de otro modo, flask-admin no incluye ningún sistema de autenticación ni tampoco está restringido a algún paquete en especial para encargarse de esta tarea.&lt;/p&gt;
&lt;p&gt;Lo que sí nos indican es que es posible implementar un método is_accessible como sigue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
class MyView(BaseView):
    def is_accessible(self):
        return login.current_user.is_authenticated()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con ello lograremos que solo los usuarios autenticados puedan acceder a dicha vista admininistrativa, pero aquellos usuarios que no se han logueado obtendrán un error 404.&lt;/p&gt;
&lt;p&gt;Lo ideal sería que al no estar logueados, se nos redirigiese hacia la página del login, pero el método anterior se limita a mostrarnos una pantalla de error.&lt;/p&gt;
&lt;p&gt;Otra manera de integrar el login y el admin sería utilizando una vista personalizada. Por defecto la vista principal del admin se ve como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AdminIndexView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdminIndexView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;@admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Entonces, podríamos editar la vista por defecto para que al no detectar al usuario como logueado, se nos redirija al login:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# admin.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_for&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask.ext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;views&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MyAdminIndexView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdminIndexView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;is_auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_authenticated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;admin_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index_view&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MyAdminIndexView&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y con ello obtendríamos lo esperado.&lt;/p&gt;
&lt;p&gt;Notar que nos faltaría encargarnos del acceso a las vistas internas y también del añadir un link para finalizar nuestra sesión.&lt;/p&gt;</content><category term="blog"/><category term="python"/><category term="flask"/></entry><entry><title>Jcanvas</title><link href="https://alexanderae.com/jcanvas.html" rel="alternate"/><published>2013-05-02T09:10:00-05:00</published><updated>2013-05-02T09:10:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-05-02:/jcanvas.html</id><summary type="html">&lt;p&gt;&lt;a href="http://calebevans.me/projects/jcanvas/"&gt;Jcanvas&lt;/a&gt; es un plugin para jquery que nos permite interactuar con el &lt;a href="http://es.wikipedia.org/wiki/Canvas_(HTML)"&gt;canvas&lt;/a&gt; de html5.&lt;/p&gt;
&lt;p&gt;Entre sus características principales, resaltan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API Sencilla que permite el manejo de figuras geométricas, imágenes, máscaras, capas, animaciones..&lt;/li&gt;
&lt;li&gt;Funciona en navegadores móbiles y de escritorio (aunque no lo he probado en ningún móbil)&lt;/li&gt;
&lt;li&gt;Ligero (~25kB)&lt;/li&gt;
&lt;li&gt;OpenSource&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Instalación&lt;/h4&gt;
&lt;p&gt;Se requiere añadir tanto jquery como el propio plugin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;jquery.min.js&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;jcanvas.min.js&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Uso&lt;/h4&gt;
&lt;p&gt;Se pueden observar 3 ejemplos sencillos …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://calebevans.me/projects/jcanvas/"&gt;Jcanvas&lt;/a&gt; es un plugin para jquery que nos permite interactuar con el &lt;a href="http://es.wikipedia.org/wiki/Canvas_(HTML)"&gt;canvas&lt;/a&gt; de html5.&lt;/p&gt;
&lt;p&gt;Entre sus características principales, resaltan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API Sencilla que permite el manejo de figuras geométricas, imágenes, máscaras, capas, animaciones..&lt;/li&gt;
&lt;li&gt;Funciona en navegadores móbiles y de escritorio (aunque no lo he probado en ningún móbil)&lt;/li&gt;
&lt;li&gt;Ligero (~25kB)&lt;/li&gt;
&lt;li&gt;OpenSource&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Instalación&lt;/h4&gt;
&lt;p&gt;Se requiere añadir tanto jquery como el propio plugin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;jquery.min.js&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;jcanvas.min.js&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Uso&lt;/h4&gt;
&lt;p&gt;Se pueden observar 3 ejemplos sencillos &lt;a href="http://labs.alexanderae.com/jcanvas/"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; El primer ejemplo nos muestra el como añadir algunas figuras geométricas:&lt;/p&gt;
&lt;p&gt;&lt;img alt="demo-01" src="/pictures/jcanvas-01.png" title="Jcanvas - demo 01"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTML&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::html
&lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;canvas1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;480&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;320&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;JS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Primero asignamos el canvas a una variable. Luego, podemos invocar a los métodos requeridos para dibujar figuras geométricas, como &lt;em&gt;drawArc&lt;/em&gt; para arcos indicando las propiedades deseadas mediante un objeto en &lt;a href="http://es.wikipedia.org/wiki/JSON"&gt;json&lt;/a&gt;. Es posible incluso concatenar los métodos.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#canvas1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawArc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rgba(12,12,59,0.6)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawArc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawArc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#285714&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Podemos también, graficar elipses y rectángulos; asi como superponerlos, usar rgba, entre otros.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;jquery&lt;/span&gt;
&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;canvas1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;drawEllipse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rgba(108, 96, 256, 0.5)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;190&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;strokeWidth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;drawEllipse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rgba(36, 144, 54, 0.5)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;190&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;strokeWidth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;canvas1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;drawRect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#d5d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fromCenter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; En el segundo ejemplo podemos observar el manejo de capas o grupos de las mismas:&lt;/p&gt;
&lt;p&gt;&lt;img alt="demo-02" src="/pictures/jcanvas-02.png" title="Jcanvas - demo 02"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Considerando, el alto y ancho del canvas asi como el tamaño de la cuadrícula, la graficamos mediante rectas, las cuales guardamos como capas bajo el mismo grupo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;
&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#canvas2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cuadricula&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawLine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cuadricula&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;strokeStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#000&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;strokeWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawLine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cuadricula&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;strokeStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#000&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;strokeWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por medio de un botón, mostramos u ocultamos el grupo de capas.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mostrar&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ocultar&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;show_hide_grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#show_hide_grid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grid_visible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;show_hide_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid_visible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;show_hide_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mostrar cuadricula&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLayerGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuadricula&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawLayers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;grid_visible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;show_hide_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ocultar cuadricula&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLayerGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cuadricula&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawLayers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;grid_visible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; El tercer ejemplo muestra una esfera rebotando en el 'piso' por medio de una sencilla animación.&lt;/p&gt;
&lt;p&gt;&lt;img alt="demo-03" src="/pictures/jcanvas-03.png" title="Jcanvas - demo 03"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Creamos la esfera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#canvas3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawArc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;esfera&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#797&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Creamos la animación^1 y la iniciamos. Notar que hemos concatenado 2 animaciones, una para la caida al finalizar se ejecuta la subida. La función es recursiva, por lo que nunca termina.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rebote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;canvas3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animateLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;esfera&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;310&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;easeInQuad&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animateLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;esfera&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;easeOutQuad&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rebote&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inicializa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;
&lt;span class="n"&gt;rebote&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h4&gt;Notas adicionales&lt;/h4&gt;
&lt;p&gt;Para mayor información se puede visitar la &lt;a href="http://calebevans.me/projects/jcanvas/docs/"&gt;documentación&lt;/a&gt; del plugin. Del mismo modo, en su página se cuenta con un &lt;a href="http://calebevans.me/projects/jcanvas/sandbox/"&gt;sandbox&lt;/a&gt; para &lt;em&gt;probar sin romper nada&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Página del proyecto en &lt;a href="https://github.com/caleb531/jcanvas"&gt;github&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://labs.alexanderae.com/jcanvas/"&gt;Sitio&lt;/a&gt; de las demos mostradas&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Para el efecto de caida y subida se uso el plugin jquery: &lt;a href="http://gsgd.co.uk/sandbox/jquery/easing/"&gt;easing&lt;/a&gt; . Usando como referencia &lt;a href="http://easings.net/es"&gt;esta guía&lt;/a&gt; para saber que función usar.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="front-end"/><category term="html"/><category term="js"/><category term="jquery"/><category term="canvas"/></entry><entry><title>SqlAlchemy: ORM para python - I</title><link href="https://alexanderae.com/sqlalchemy-orm-para-python-1.html" rel="alternate"/><published>2013-04-28T16:27:00-05:00</published><updated>2013-04-28T16:27:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-04-28:/sqlalchemy-orm-para-python-1.html</id><summary type="html">&lt;h5&gt;¿Qué es?&lt;/h5&gt;
&lt;p&gt;SQLAlchemy es un &lt;a href="http://es.wikipedia.org/wiki/Mapeo_objeto-relacional"&gt;ORM&lt;/a&gt; para python. Incluye soporte para SQLite, MySQL, PostgreSQL, Oracle, MS SQL, entre otros.&lt;/p&gt;
&lt;h5&gt;¿Qué es un ORM?&lt;/h5&gt;
&lt;p&gt;Sus siglas significan &lt;em&gt;Object-Relational mapping&lt;/em&gt;, o en español: &lt;em&gt;Mapeo Objeto-relacional&lt;/em&gt;. Es una técnica que permite convertir entre los tipos de datos usados en los lenguajes de programación orientados a objetos y los tipos de datos de algun sistema de base de datos relacional.&lt;/p&gt;
&lt;h5&gt;Instalación&lt;/h5&gt;
&lt;p&gt;Si bien podemos usar &lt;a href="https://pypi.python.org/pypi/setuptools"&gt;setuptools&lt;/a&gt; (easy_install), recomiendo instalarlo mediante &lt;a href="https://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    pip install sqlalchemy …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;h5&gt;¿Qué es?&lt;/h5&gt;
&lt;p&gt;SQLAlchemy es un &lt;a href="http://es.wikipedia.org/wiki/Mapeo_objeto-relacional"&gt;ORM&lt;/a&gt; para python. Incluye soporte para SQLite, MySQL, PostgreSQL, Oracle, MS SQL, entre otros.&lt;/p&gt;
&lt;h5&gt;¿Qué es un ORM?&lt;/h5&gt;
&lt;p&gt;Sus siglas significan &lt;em&gt;Object-Relational mapping&lt;/em&gt;, o en español: &lt;em&gt;Mapeo Objeto-relacional&lt;/em&gt;. Es una técnica que permite convertir entre los tipos de datos usados en los lenguajes de programación orientados a objetos y los tipos de datos de algun sistema de base de datos relacional.&lt;/p&gt;
&lt;h5&gt;Instalación&lt;/h5&gt;
&lt;p&gt;Si bien podemos usar &lt;a href="https://pypi.python.org/pypi/setuptools"&gt;setuptools&lt;/a&gt; (easy_install), recomiendo instalarlo mediante &lt;a href="https://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    pip install sqlalchemy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Para comprobar que se haya realizado la instalación, podemos acceder a la consola interactiva de python y escribir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sql_alchemy&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__version__&lt;/span&gt;
&lt;span class="mf"&gt;0.8.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;más información sobre la instalación &lt;a href="http://docs.sqlalchemy.org/en/rel_0_8/intro.html#installation"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h5&gt;Ejemplo de uso&lt;/h5&gt;
&lt;p&gt;Para ilustrar&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; la idea, desarrollaré el modelo de datos para un sistema que se encarga de administrar registros de libros, autores, y etiquetas (o temas, para los libros).&lt;/p&gt;
&lt;p&gt;El esquema&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; del modelo es el siguiente:&lt;/p&gt;
&lt;p&gt;&lt;img alt="modelo-libros" src="/pictures/sqlalchemy-modelo-libros.png" title="Modelo de datos"&gt;&lt;/p&gt;
&lt;p&gt;Consiste en las tablas Libro, Autor y Etiqueta principalmente.
Las tablas intermediarias &lt;em&gt;etiqueta_libro&lt;/em&gt; y &lt;em&gt;autor_libro&lt;/em&gt; se utilizan para expresar la relación de &lt;em&gt;muchos a muchos&lt;/em&gt; entre las otras tablas.&lt;/p&gt;
&lt;p&gt;Primero, debemos mapear el modelo o esquema de la base de datos por medio de sqlalchemy, para lo cual escribí lo siguiente en un archivo al que llamé models.py:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="c1"&gt;# -- coding: utf-8 --&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy.ext.declarative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;declarative_base&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy.orm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sqlite:///libros.db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;declarative_base&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# relaciones muchos a muchos&lt;/span&gt;
&lt;span class="n"&gt;etiqueta_libro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;etiqueta_libro&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libro_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libro.id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;etiqueta_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;etiqueta.id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;autor_libro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;autor_libro&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libro_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libro.id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;autor_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;autor.id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Libro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;libro&amp;#39;&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fecha_publicacion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;isbn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;etiquetas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Etiqueta&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secondary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;etiqueta_libro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;autores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Autor&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secondary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;autor_libro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fecha_publicacion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;titulo&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fecha_publicacion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fecha_publicacion&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isbn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isbn&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;etiqueta&amp;#39;&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;autor&amp;#39;&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt;

&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La explicación va mas o menos así:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Importamos los módulos necesarios&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Realizamos la conexión a la base de datos, en este caso corresponde a sqlite, por medio de un archivo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapeamos las tablas como clases, utilizando los tipos de datos de sqlalchemy para expresar los diferentes tipos de datos de sqlite: Integer, String, Date..&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Añadimos las relaciones entre las tablas: uno a muchos, muchos a muchos..&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Al final, usamos la sentencia:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Base.metadata.create_all(engine)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;para que al correr el script, se genere la base de datos.&lt;/p&gt;
&lt;p&gt;corremos el script mediante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    python models.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con ello, observaremos que se creó el archivo "libros.db" el cual contiene nuestra base de datos.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;El acceso, inserción o modificación de datos, lo podemos realizar de la siguiente manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;

&lt;span class="c1"&gt;# -- coding: utf-8 --&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy.orm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sessionmaker&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Libro&lt;/span&gt;

&lt;span class="c1"&gt;# conexion&lt;/span&gt;
&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sqlite:///libros.db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# sesion&lt;/span&gt;
&lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sessionmaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# insertamos autores&lt;/span&gt;
&lt;span class="n"&gt;autor_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Patrick Rothfuss&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;autor_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Autor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fred Saberhagen&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;lista_autores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autor_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;autor_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lista_autores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# insertamos etiquetas&lt;/span&gt;
&lt;span class="n"&gt;etiqueta_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Etiqueta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;aventuras&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etiqueta_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# insertamos libros&lt;/span&gt;
&lt;span class="n"&gt;libro_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;El nombre del fuego&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2009&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;978-8401337208&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;libro_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etiquetas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etiqueta_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;libro_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autores&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autor_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libro_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# realizando una consulta&lt;/span&gt;
&lt;span class="n"&gt;libro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Libro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;978-8401337208&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;libro&lt;/span&gt;

&lt;span class="c1"&gt;# alterando uno de los atributos&lt;/span&gt;
&lt;span class="n"&gt;libro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titulo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;El nombre del viento&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;libro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El código anterior, se encuentra comentado, indicando los pasos realizados; como la conexión, inserción o modificación de la información.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;El siguiente ejemplo no busca ser un tutorial completo, sino solamente ejemplificar el funcionamiento básico de sqlalchemy. Para información más amplia puede guiarse de la &lt;a href="http://docs.sqlalchemy.org/en/rel_0_8/"&gt;documentación&lt;/a&gt; o del siguiente tutorial publicado en &lt;a href="http://python.dzone.com/articles/simple-sqlalchemy-07-08"&gt;dzone&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;El esquema fue realizado con &lt;a href="https://code.google.com/p/wwwsqldesigner/"&gt;wwwsqldesigner&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="python"/><category term="base-de-datos"/><category term="sqlalchemy"/></entry><entry><title>Programar backup de Mysql en Windows</title><link href="https://alexanderae.com/programar-backup-de-mysql-en-windows.html" rel="alternate"/><published>2013-04-14T14:45:00-05:00</published><updated>2013-04-14T14:45:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-04-14:/programar-backup-de-mysql-en-windows.html</id><summary type="html">&lt;p&gt;Tal y como mencioné en la entrada anterior, tuve que instalar django con &lt;strong&gt;mysql&lt;/strong&gt; como base de datos en windows server. Además, se tuvo que usar un script para realizar copias automáticas de la base de datos.&lt;/p&gt;
&lt;h4&gt;Script&lt;/h4&gt;
&lt;p&gt;El script, el cual encontré &lt;a href="http://www.oskar.org/blog/2012/jan/scheduled-backup-of-mysql-databases-in-windows"&gt;aquí&lt;/a&gt; y modifiqué levemente fue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;@echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;off&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mySqlPath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nl"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;wamp&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;mysql5&lt;/span&gt;&lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dbUser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;usuario_backup&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dbPassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;mi_contraseña&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dbName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mi_base_de_datos&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=%&lt;/span&gt;&lt;span class="n"&gt;dbName&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nc"&gt;date&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="nc"&gt;date&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="nc"&gt;date&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~-&lt;/span&gt;&lt;span class="mi"&gt;10 …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Tal y como mencioné en la entrada anterior, tuve que instalar django con &lt;strong&gt;mysql&lt;/strong&gt; como base de datos en windows server. Además, se tuvo que usar un script para realizar copias automáticas de la base de datos.&lt;/p&gt;
&lt;h4&gt;Script&lt;/h4&gt;
&lt;p&gt;El script, el cual encontré &lt;a href="http://www.oskar.org/blog/2012/jan/scheduled-backup-of-mysql-databases-in-windows"&gt;aquí&lt;/a&gt; y modifiqué levemente fue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;@echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;off&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mySqlPath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nl"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;wamp&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;mysql5&lt;/span&gt;&lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dbUser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;usuario_backup&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dbPassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;mi_contraseña&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dbName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mi_base_de_datos&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=%&lt;/span&gt;&lt;span class="n"&gt;dbName&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nc"&gt;date&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="nc"&gt;date&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="nc"&gt;date&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nl"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;dbName&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;

&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;dump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;dbName&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^%&lt;/span&gt;&lt;span class="k"&gt;path&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;
&lt;span class="ss"&gt;&amp;quot;%mySqlPath%\bin\mysqldump.exe&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;dbUser&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;dbPassword&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;dbName&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;%path%\%file%&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note que podemos configurar el formato del nombre de salida del archivo (set file) o la ubicación de las copias (set path) a nuestro gusto.&lt;/p&gt;
&lt;h4&gt;Programando la tarea&lt;/h4&gt;
&lt;p&gt;Windows cuenta con la utilidad llamada "&lt;a href="http://en.wikipedia.org/wiki/At_(Windows)"&gt;at&lt;/a&gt;" (Task scheduler o Programador de tareas) que será la que se usará:&lt;/p&gt;
&lt;p&gt;Podemos listar las tareas con el comando: "at" e insertar la la tarea del siguiente modo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;at 23:30 /every:M,T,W,Th,F,S,Su C:\backup\scripts\mysql-backup.bat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con lo que lograremos que nuestro script se ejecute todos los días a las 23:30.&lt;/p&gt;</content><category term="blog"/><category term="mysql"/><category term="sysadmin"/><category term="windows"/></entry><entry><title>Django, MySQL y Apache en Windows Server</title><link href="https://alexanderae.com/django-mysql-apache-en-windows-server.html" rel="alternate"/><published>2013-04-11T00:00:00-05:00</published><updated>2013-04-11T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-04-11:/django-mysql-apache-en-windows-server.html</id><summary type="html">&lt;p&gt;Hasta hace poco, no había tenido la necesidad de configurar django en un entorno en producción en windows, por suerte para mi, fue un tanto menos complicado de lo esperado. Lo que a mi me tocó usar fue Python 2.7, Django 1.4, WAMP (Apache + MySQL), todo ello en Windows Server. A continuación, un resumen de lo que hice:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Instalamos la versión de &lt;em&gt;python&lt;/em&gt; requerida (por ejemplo la 2.7.4), la cual podemos obtener de &lt;a href="http://python.org/download/"&gt;python.org …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hasta hace poco, no había tenido la necesidad de configurar django en un entorno en producción en windows, por suerte para mi, fue un tanto menos complicado de lo esperado. Lo que a mi me tocó usar fue Python 2.7, Django 1.4, WAMP (Apache + MySQL), todo ello en Windows Server. A continuación, un resumen de lo que hice:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1.&lt;/em&gt; Instalamos la versión de &lt;em&gt;python&lt;/em&gt; requerida (por ejemplo la 2.7.4), la cual podemos obtener de &lt;a href="http://python.org/download/"&gt;python.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2.&lt;/em&gt; Instalamos &lt;em&gt;setuptools&lt;/em&gt;, una herramienta de gestión de paquetes para python. Lo podemos descargar desde &lt;a href="https://pypi.python.org/pypi/setuptools#downloads"&gt;pypi&lt;/a&gt;.
No olvidar que debemos de escoger la versión adecuada para nuestra versión de python instalada.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;3.&lt;/em&gt; Añadir al path el directorio de python y sus scripts, por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;Python27&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nt"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;Python27&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nt"&gt;Scripts&lt;/span&gt;&lt;span class="o"&gt;;..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Podemos comprobar lo realizado hasta aquí, ingresando a la ventana de comandos (cmd):&lt;/p&gt;
&lt;p&gt;Al escribir python y presionar enter, se debe de mostrar la consola interactiva de python. Para salir, presione &lt;em&gt;ctrl + z + enter&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Al escribir easy_install se nos debe de comunicar que nos faltó indicar la url o ruta del archivo a instalar.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;4.&lt;/em&gt; Instalamos &lt;a href="https://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt; (otro gestor de paquetes de python) mediante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;easy_install pip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;5.&lt;/em&gt; Instalamos django y las dependencias de nuestro proyecto por medio de pip, por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install &amp;quot;django&amp;lt;=1.4.99&amp;quot;
pip install &amp;quot;django-uuslug&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;6.&lt;/em&gt; En caso de requerir &lt;a href="http://www.pythonware.com/products/pil/"&gt;PIL&lt;/a&gt; (Python Imaging Library) en nuestro proyecto, se recomienda utilizar &lt;a href="http://python-imaging.github.io/Pillow/"&gt;Pillow&lt;/a&gt; (un fork de PIL mucho más amigable que su antecesor) como reemplazo. Puede descargar el instalador para windows desde &lt;a href="https://pypi.python.org/pypi/Pillow/2.0.0"&gt;pypi&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;7.&lt;/em&gt; Descargamos e instalamos &lt;a href="http://www.wampserver.com/en/"&gt;WAMP Server&lt;/a&gt;. Note que se requiere como dependencia a &lt;a href="http://www.microsoft.com/download/en/details.aspx?id=13523"&gt;Microsoft Visual C++ 2010 Redistributable&lt;/a&gt; (observe que puede descargar la versión x86 o la de 64 bits). Una vez instalado wamp, podemos cargar nuestra base de datos por medio de &lt;em&gt;phpMyAdmin&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;8.&lt;/em&gt; Descargamos e instalamos el conector de python para mysql, lo podemos descargar desde &lt;a href="http://sourceforge.net/projects/mysql-python/"&gt;sourceforge&lt;/a&gt;.
Note que debemos escoger la versión que corresponda a nuestro python instalado.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;9.&lt;/em&gt; Descargamos nuestro proyecto en django mediante &lt;a href="https://filezilla-project.org/"&gt;filezilla&lt;/a&gt; o cualquier otra herramienta.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;10.&lt;/em&gt; Configuramos la base de datos en el archivo &lt;a href="https://docs.djangoproject.com/en/dev/topics/settings/"&gt;settings.py&lt;/a&gt;. Para comprobar si el proyecto inicia en modo "desarrollo", podemos abrir el cmd y escribir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ir a la ruta &lt;a href="http://127.0.0.1:8000"&gt;http://127.0.0.1:8000&lt;/a&gt; y observar si el sitio web se visualiza de forma correcta.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;11.&lt;/em&gt; Configuramos la ruta para el directorio static con el parámetro:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;STATIC_ROOT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;recolectamos los archivos static:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python manage.py collectstatic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;12.&lt;/em&gt; Configuramos el archivo wsgi para que quede similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;C:\proyecto\www&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C:\Python27\Lib\site-packages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DJANGO_SETTINGS_MODULE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;settings&amp;#39;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;django.core.handlers.wsgi&lt;/span&gt;

&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wsgi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WSGIHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;13.&lt;/em&gt; Descargamos el módulo &lt;a href="https://code.google.com/p/modwsgi/"&gt;mod_wsgi&lt;/a&gt; para apache, el cual permite conectar el mismo con nuestras aplicaciones en python que soporten &lt;a href="http://wsgi.readthedocs.org/en/latest/what.html"&gt;wsgi&lt;/a&gt;.
Una vez descargada la versión correspondiente a nuestro python, renombramos el archivo a &lt;strong&gt;mod_wsgi.so&lt;/strong&gt; y lo copiamos a la carpeta &lt;em&gt;modules&lt;/em&gt; de nuestro apache, la cual puede tener una ruta similar a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;C:\wamp\bin\apache\apache2.2.22\modules
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;14.&lt;/em&gt; Configuramos apache, por medio del archivo &lt;em&gt;httpd.conf&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Añadimos el módulo mod_wsgi&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;LoadModule wsgi_module modules/mod_wsgi.so&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Añadimos el archivo wsgi y la configuración para los directorio static y media:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Alias&lt;span class="w"&gt; &lt;/span&gt;/media/&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;C:\proyecto\www\media\&amp;quot;
Alias&lt;span class="w"&gt; &lt;/span&gt;/static/&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;C:\proyecto_static\&amp;quot;

&lt;span class="nt"&gt;&amp;lt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;C:\proyecto\www\media\&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Order&lt;span class="w"&gt; &lt;/span&gt;deny,allow
&lt;span class="w"&gt;    &lt;/span&gt;Allow&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;all
&lt;span class="nt"&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;C:\proyecto_static\&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Order&lt;span class="w"&gt; &lt;/span&gt;allow,deny
&lt;span class="w"&gt;    &lt;/span&gt;Allow&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;all
&lt;span class="nt"&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;


WSGIScriptAlias&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;C:\proyecto\www\wsgi.py&amp;quot;

WSGIPythonPath&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;C:\proyecto\www&amp;quot;

&lt;span class="nt"&gt;&amp;lt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;quot;C:\proyecto\www&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wsgi.py&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Order&lt;span class="w"&gt; &lt;/span&gt;deny,allow
&lt;span class="w"&gt;    &lt;/span&gt;Allow&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;all
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Files&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Reiniciamos apache, y al acceder al localhost deberíamos poder visualizar nuestro proyecto.&lt;/p&gt;</content><category term="blog"/><category term="django"/><category term="windows-server"/></entry><entry><title>Acceso SSH sin password</title><link href="https://alexanderae.com/acceso-ssh-sin-password.html" rel="alternate"/><published>2013-03-24T12:00:00-05:00</published><updated>2013-03-24T12:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-03-24:/acceso-ssh-sin-password.html</id><summary type="html">&lt;p&gt;Introducir una contraseña cada vez que necesitamos iniciar una conexión &lt;a href="http://es.wikipedia.org/wiki/Secure_Shell"&gt;&lt;strong&gt;ssh&lt;/strong&gt;&lt;/a&gt; con el servidor, puede ser algo incómodo&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Afortunadamente, revisando sobre ello, encontré que es posible acceder mediante un método alternativo: llaves ssh&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Para ello, supongamos que tenemos 2 pc's a las que llamaremos &lt;em&gt;A&lt;/em&gt; y &lt;em&gt;B&lt;/em&gt;, con los usuarios &lt;em&gt;a&lt;/em&gt; y &lt;em&gt;b&lt;/em&gt; respectivamente.&lt;/p&gt;
&lt;p&gt;Lo que se desea es poder acceder desde la pc A hacia B con el usuario b. Para lo cual seguiremos los siguientes pasos …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Introducir una contraseña cada vez que necesitamos iniciar una conexión &lt;a href="http://es.wikipedia.org/wiki/Secure_Shell"&gt;&lt;strong&gt;ssh&lt;/strong&gt;&lt;/a&gt; con el servidor, puede ser algo incómodo&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Afortunadamente, revisando sobre ello, encontré que es posible acceder mediante un método alternativo: llaves ssh&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Para ello, supongamos que tenemos 2 pc's a las que llamaremos &lt;em&gt;A&lt;/em&gt; y &lt;em&gt;B&lt;/em&gt;, con los usuarios &lt;em&gt;a&lt;/em&gt; y &lt;em&gt;b&lt;/em&gt; respectivamente.&lt;/p&gt;
&lt;p&gt;Lo que se desea es poder acceder desde la pc A hacia B con el usuario b. Para lo cual seguiremos los siguientes pasos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;En la pc 'A' generamos una llave pública y privada sin la frase de contraseña.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nv"&gt;@A&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keygen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsa&lt;/span&gt;
&lt;span class="n"&gt;Generating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;save&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_rsa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;Created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/home/a/.ssh&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;again&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;Your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;identification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;been&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;been&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="nl"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="nl"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nl"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="nl"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nl"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nl"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nl"&gt;jj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nl"&gt;k0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="nl"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="nl"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nv"&gt;@A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creamos el directorio &lt;em&gt;~/.ssh&lt;/em&gt; en &lt;em&gt;B&lt;/em&gt;, considerando que puede ya existir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nv"&gt;@A&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nv"&gt;@B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nv"&gt;@B&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Añadimos nuestra llave pública creada en &lt;em&gt;A&lt;/em&gt; al archivo &lt;em&gt;ssh/authorized_keys&lt;/em&gt; en &lt;em&gt;B&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nv"&gt;@A&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nv"&gt;@B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cat &amp;gt;&amp;gt; .ssh/authorized_keys&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nv"&gt;@B&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;Nota:&lt;/h5&gt;
&lt;p&gt;Es posible que necesitemos ajustar los permisos del directorio &lt;em&gt;.ssh&lt;/em&gt; y el archivo &lt;em&gt;.ssh/authorized_keys&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez listo lo anterior,podríamos acceder por ssh al servidor B sin necesidad de escribir nuestra contraseña.&lt;/p&gt;
&lt;hr&gt;
&lt;h5&gt;Actualización&lt;/h5&gt;
&lt;p&gt;Un método mucho más simple para copiar nuestra clave ssh al servidor al que accederemos es mediante el comando &lt;em&gt;ssh-copy-id&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_rsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="nv"&gt;@host&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lo cual resulta mucho más veloz que el método anterior.&lt;/p&gt;
&lt;hr&gt;
&lt;h5&gt;Referencias&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;SSH login without password: &lt;a href="http://linuxproblem.org/art_9.html"&gt;linuxproblem.org&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ssh - authorized_keys HOWTO: &lt;a href="http://www.eng.cam.ac.uk/help/jpmg/ssh/authorized_keys_howto.html"&gt;universidad de cambridge&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Acceso a máquinas usando ssh sin contraseña: &lt;a href="http://www.uv.es/~sto/articulos/BEI-2003-01/ssh_np.html"&gt;universidad de valencia&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;en especial cuando la contraseña es una larga cadena de caracteres inmemorizables y son varios los servidores con los que se trabaja.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/index.php/SSH_Keys"&gt;ssh keys&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="linux"/><category term="sysadmin"/></entry><entry><title>Memcached</title><link href="https://alexanderae.com/memcached.html" rel="alternate"/><published>2013-03-19T22:03:00-05:00</published><updated>2013-03-19T22:03:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-03-19:/memcached.html</id><summary type="html">&lt;p&gt;&lt;a href="http://memcached.org/"&gt;Memcached&lt;/a&gt; es un sistema distribuido de caché en memoria orientado a objetos.&lt;/p&gt;
&lt;h5&gt;¿Qué quiere decir?, ¿cuáles son sus características?&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;distribuido&lt;/em&gt;: &lt;strong&gt;memcached&lt;/strong&gt; puede instalarse en varios servidores a la vez que pueden ser configurados para actuar como una sola entidad sin comunicarse entre si.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;en memoria&lt;/em&gt;: mantiene los fragmentos de información en la memoria RAM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;orientado a objetos&lt;/em&gt;: almacena los items en pares del tipo clave-valor basándose en &lt;a href="http://es.wikipedia.org/wiki/Tabla_hash"&gt;tablas hash&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Implementado en el servidor y en el  cliente&lt;/em&gt;: el cliente sabe …&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://memcached.org/"&gt;Memcached&lt;/a&gt; es un sistema distribuido de caché en memoria orientado a objetos.&lt;/p&gt;
&lt;h5&gt;¿Qué quiere decir?, ¿cuáles son sus características?&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;distribuido&lt;/em&gt;: &lt;strong&gt;memcached&lt;/strong&gt; puede instalarse en varios servidores a la vez que pueden ser configurados para actuar como una sola entidad sin comunicarse entre si.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;en memoria&lt;/em&gt;: mantiene los fragmentos de información en la memoria RAM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;orientado a objetos&lt;/em&gt;: almacena los items en pares del tipo clave-valor basándose en &lt;a href="http://es.wikipedia.org/wiki/Tabla_hash"&gt;tablas hash&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Implementado en el servidor y en el  cliente&lt;/em&gt;: el cliente sabe como enviar items al servidor, cuando no se puede contactar al servidor y como solicitar items del servidor. El servidor sabe como almacenar, retornar o expirar los items según las solicitudes del cliente.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h5&gt;Instalación&lt;/h5&gt;
&lt;p&gt;Podemos instalarlo mediante el gestor de paquetes de nuestro sistema operativo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    apt-get install memcached

    pacman -S memcache

    yum install memcached
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;o compilándolo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    wget http://memcached.googlecode.com/files/memcached-1.4.x.tar.gz
    tar xfz memcached-1.4.x.tar.gz
    cd memcached-1.4.x/
    ./configure
    make &amp;amp;&amp;amp; make install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;podemos comprobar la ruta del comando mediante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    which memcached
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;lo que nos debería retornal algo así como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    /usr/local/bin/memcached
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y si algo falla, siempre podemos consultar la &lt;a href="https://code.google.com/p/memcached/wiki/NewStart"&gt;wiki&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h5&gt;Ejecución&lt;/h5&gt;
&lt;p&gt;Podemos ejecutar el comando &lt;strong&gt;memcached&lt;/strong&gt; con algunas opciones como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    memcached -d -p 11211 -m 128 -c 1024 -P $HOME/memcached.pid
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;donde:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;d: permite correr memcached como un &lt;a href="http://es.wikipedia.org/wiki/Demonio_(inform%C3%A1tica)"&gt;demonio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;p: indica el puerto, por defecto es 11211&lt;/li&gt;
&lt;li&gt;m: cantidad de memoria en MB máxima a usar&lt;/li&gt;
&lt;li&gt;c: número máximo de conexiones simultáneas&lt;/li&gt;
&lt;li&gt;P: ubicación del archivo en el que se almacenará el PID del proceso, solo es válido cuando se ejecuta como un demonio.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;se puede leer el manual, como siempre, por medio de:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    man memcached
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Para comprobar que se esté ejecutando, podemos usar telnet, lo que nos devuelve algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    telnet localhost 11211
    Trying 127.0.0.1...
    Connected to localhost.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;que indica que se ha conectado a memcached por medio del puerto que especificamos.&lt;/p&gt;
&lt;hr&gt;
&lt;h5&gt;Memcached y Systemd&lt;/h5&gt;
&lt;p&gt;Como adicional, el archivo de configuración y scripts necesarios para administrar memcached mediante &lt;a href="http://chakra.sourceforge.net/wiki/index.php/Systemd"&gt;systemd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;memcached.config:&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    PORT=&amp;quot;11211&amp;quot;
    USER=&amp;quot;user&amp;quot;
    MAXCONN=&amp;quot;512&amp;quot;
    CACHESIZE=&amp;quot;32&amp;quot;
    PID=&amp;quot;/home/user/.memcached.pid&amp;quot;
    OPTIONS=&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;memcached.service:&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memcached&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;daemon&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sysconfig&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;memcached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;memcached&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="n"&gt;CACHESIZE&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="n"&gt;MAXCONN&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="n"&gt;PID&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;OPTIONS&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ExecStop&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;kill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cat ${PID}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="n"&gt;PID&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Install&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;con ello, solo tendríamos que ejecutar, según convenga:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    systemctl start memcached
    systemctl stop memcached
    systemctl status memcached
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y con ello, tenemos lista la configuración del servidor de memcached. Lo siguiente es configurar un cliente, y a empezar a usar la caché!&lt;/p&gt;</content><category term="blog"/><category term="memcached"/><category term="optimizacion"/></entry><entry><title>Una Historia de Caché</title><link href="https://alexanderae.com/una-historia-de-cache.html" rel="alternate"/><published>2013-03-17T12:20:00-05:00</published><updated>2013-03-17T12:20:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-03-17:/una-historia-de-cache.html</id><summary type="html">&lt;p&gt;Dos valerosos aventureros, un desarrollador de software y un administrador de sistemas, emprendieron un viaje.
Ellos hacen sitios web. Sitios con servidores web y bases de datos. Los usuarios de todo el internet interactuan con los servidores web y les piden que hagan páginas para ellos. Los servidores web consultan a las bases de datos por la información necesaria para construir las páginas. El desarrollador escribe código y el admininistrador añade servidores web y servidores de base de datos.&lt;/p&gt;
&lt;p&gt;Un …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Dos valerosos aventureros, un desarrollador de software y un administrador de sistemas, emprendieron un viaje.
Ellos hacen sitios web. Sitios con servidores web y bases de datos. Los usuarios de todo el internet interactuan con los servidores web y les piden que hagan páginas para ellos. Los servidores web consultan a las bases de datos por la información necesaria para construir las páginas. El desarrollador escribe código y el admininistrador añade servidores web y servidores de base de datos.&lt;/p&gt;
&lt;p&gt;Un dia, el administrador se dio cuenta de que su base de datos estaba enferma! Vomitando bilis y materia roja por todas partes! El administrador notó que tenía fiebre, una carga promedio del 20%!&lt;/p&gt;
&lt;p&gt;El desarrollador le consulta al administrador: "bueno, ¿qué podemos hacer?".
El administrador responde: "He oído acerca de una gran herramienta llamada &lt;a href="http://memcached.org/"&gt;&lt;strong&gt;memcached&lt;/strong&gt;&lt;/a&gt;. Realmente ayudó a LiveJournal".&lt;/p&gt;
&lt;p&gt;"Esta bien, probémosla" dice el desarrollador.&lt;/p&gt;
&lt;p&gt;Nuestro valeroso administrador observa sus servidores web, de los cuales tiene seis. Decide usar tres de ellos para correr el servidor de memcached. El administrador añade un gigabyte de RAM para cada servidor, e inicia el servidor de memcached con un límite de 1GB cada uno. Entonces, consigue tres instancias de memcached, cada una de las cuales puede almacenar hasta 1GB de datos. Entonces el desarrollador y el administrador retroceden un paso y admiran su glorioso &lt;em&gt;memcached&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;"¿Ahora qué?" dicen, "no esta haciendo NADA!". Las instancias de memcached no estan interactuando con nada y ciertamente no tienen ningun dato. Y ahora la base de datos tiene una carga del 25%!&lt;/p&gt;
&lt;p&gt;Nuestro aventuroso desarrollador coge el manual de la libreria del cliente de &lt;em&gt;pecl/memcached&lt;/em&gt;, el cual nuestro valeroso adminitrador ayudó amablemente a instalar sobre los seis servidores web.
"No temas", él dice. "Tengo una gran idea!". Él toma las direcciones IP y los números de puertos de las 3 instancias de memcached y las añade a un array en php.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::html-php
$MEMCACHE_SERVERS = array(
    &amp;quot;10.1.1.1&amp;quot;, //web1
    &amp;quot;10.1.1.2&amp;quot;, //web2
    &amp;quot;10.1.1.3&amp;quot;, //web3
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Entonces el hace un objeto, al cual ingeniósamente llama '$memcache'.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;memcache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Memcache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;MEMCACHE_SERVERS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;memcache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora el desarrollador piensa. Él piensa y piensa y piensa. "Lo tengo!", dice.
"Hay una sección en la página inicial que corre un SELECT * FROM InmensaTabla WHERE timestamp&amp;gt; lastweek ORDER BY timestamp ASC LIMIT 50000; y esto toma cinco segundos". "POngámoslo en memcached". Entonces él envuelve su código para el SELECT y usa un objeto de memcached. Su código consulta:&lt;/p&gt;
&lt;p&gt;Están los resultados de este SELECT en memcached?. Si no lo están, ejecuta la consulta, toma los resultados y AÑÁDELOS a memcached! . Algo como esto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::html-php
$huge_data_for_front_page = $memcache-&amp;gt;get(&amp;quot;huge_data_for_front_page&amp;quot;);
if($huge_data_for_front_page === false){
    $huge_data_for_front_page = array();
    $sql = &amp;quot;SELECT * FROM hugetable WHERE timestamp &amp;gt; lastweek ORDER BY timestamp ASC LIMIT 50000&amp;quot;;
    $res = mysql_query($sql, $mysql_connection);
    while($rec = mysql_fetch_assoc($res)){
        $huge_data_for_frong_page[] = $rec;
    }
    // cache para 10 minutos
    $memcache-&amp;gt;set(&amp;quot;huge_data_for_front_page&amp;quot;, $huge_data_for_front_page, 0, 600);
}

// use $huge_data_for_front_page como desee
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El desarrollador escribe código. El administrador transpira. BAM! . La carga de la base de datos ha bajado al 10%. El sitio web está muy rápido ahora. Ahora el administrador desconcertado, se pregunta. "¿Qué DEMONIOS ha pasado?". "He puesto gráficos en los servidores de memcached! He usado &lt;a href="http://www.cacti.net/"&gt;&lt;strong&gt;cacti&lt;/strong&gt;&lt;/a&gt;, y esto es lo que veo! El tráfico se dirige hacia una de las instancias de memcached, pero hice tres :(". Entonces, el administrador rápidamente aprende el protocolo ascii y realiza el comando &lt;em&gt;telnet&lt;/em&gt; hacia el puerto 11211 sobre cada instancia y se pregunta:&lt;/p&gt;
&lt;p&gt;¿Hey, 'get huge_data_for_front_page' estas allí?&lt;/p&gt;
&lt;p&gt;La primera instancia no responde ...&lt;/p&gt;
&lt;p&gt;La segunda instancia no responde ...&lt;/p&gt;
&lt;p&gt;La tercera sin embargo, retorna una gran cantidad de datos en su sesión de telnet! Allí están los datos! Solo una de las instancias tiene la clave de la cache que el desarrollador creó.&lt;/p&gt;
&lt;p&gt;Intrigado, consulta en la lista de correo. Ellos responden al unísono, "Es un sistema distribuido de caché! Es lo que hace!". Pero ¿qué es lo que significa?. Aún confundido y un poco asustado por su vida, el administrador le consulta al desarrollador si es posible añadir a la caché algunas cosas más. "Veamos que sucede. Somos gente curiosa", dice el administrador.&lt;/p&gt;
&lt;p&gt;"Bueno, hay otro query que no es lento, pero se ejecuta 100 veces por segundo. Tal vez nos pueda ayudar", dice el desarrollador. Entonces el envuelve el código como lo hizo antes. Efectivamente, la carga del servidor se reduce a un 8%! .&lt;/p&gt;
&lt;p&gt;Entonces el desarrollador escribé más y más código sobre cosas que pueden ser añadidas a la caché. El usa nuevas técnicas. "Encontré esto en la lista de correos y en la sección de preguntas frecuente", dice.
La carga de la base de datos baja: 7%, 5% , 3%, 2%, 1%!&lt;/p&gt;
&lt;p&gt;"Esta bien", dice el administrador de sistemas, "probemos de nuevo!". Ahora el observa los gráficos. TODAS las instancias de memcached estan corriendo! Todas estan recibiendo pedidos! Esto es genial! Todas son usadas!&lt;/p&gt;
&lt;p&gt;Entonces nuevamente, el toma las llaves que el desarrollador usó y las observa en los servidores de memcached. 'get this_key' 'get that_key'. Pero cada vez que el lo hace, solo la encuentra sobre una de las instancias! . Ahora ¿POR QUÉ debería suceder esto? piensa él. Y pasa toda la noche intrigado. Qué tonto! ¿No deseas que todas las llaves esten en todas las instancias?&lt;/p&gt;
&lt;p&gt;"Pero, espera", el piensa, "Le otorgué a cada instancia de memcached 1 gigabyte de memoria, eso significa, que en total, puedo tener en caché hasta TRES gigabytes de mi base de datos, en vez de solo UNA!". "Esto es grandioso!", piensa. "Esto me ahorrará una tonelada de dinero". Brad Fitzpatrick te amo! .&lt;/p&gt;
&lt;p&gt;"Pero .., el siguiente problema es otra cosa que me desconcierta, este servidor web que tengo aquí y esta corriendo una instancia de memcached es viejo, esta enfermo y necesita ser actualizado. Pero para hacerlo, tengo que apagarlo! ¿Qué le pasará a mi pobre cluster de memcached?. Averigüémoslo" dice él, y apaga el servidor. Ahora observa sus gráficos. "Oh, no, la carga de la bbdd, ha subido. Ya no es de 1%, es del 2%, pero aun es tolerable". Todas las demás instancias aún reciben tráfico. Entonces devolvamos el servidor de vuelta, y pongamos a memcached de vuelta al trabajo. Después de unos minutos, la carga de la bbdd vuelve nuevamente a bajar al 1%, donde debería estar".&lt;/p&gt;
&lt;p&gt;"La caché se restauró a si misma! Ahora lo entiendo. Si no está disponible, solo significa que algunas de mis peticiones se perdieron. Pero no es suficiente como para matarme. Eso es muy dulce".&lt;/p&gt;
&lt;p&gt;Entonces el desarrollador de software y el administrador de sistemas continuaron construyendo sitios web. Ellos continuaron usando la caché. Cuando tienen dudas consultan la lista de correos o leen las preguntas frecuentes nuevamente. Ellos observan sus gráficos. &lt;em&gt;Y todos vivieronn felices para siempre.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Autor: &lt;a href="https://twitter.com/dormando"&gt;Dormando&lt;/a&gt; via IRC. Editado por Brian Moon. Edición adicional por Emufarmers.&lt;/p&gt;
&lt;p&gt;Esta historia ha sido ilustrada por el comic online &lt;a href="http://toblender.com/tag/memcached/"&gt;TOBlender.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Encontré esta historia mientras revisaba la &lt;a href="https://code.google.com/p/memcached/wiki/TutorialCachingStory"&gt;wiki&lt;/a&gt; de &lt;em&gt;memcached&lt;/em&gt;. Me gustó tanto que me animé a realizar la traducción anterior, la cual por cierto tal vez no sea fiel a la original, pero hice mi mejor intento!&lt;/p&gt;</content><category term="blog"/><category term="memcached"/><category term="optimizacion"/><category term="base-de-datos"/></entry><entry><title>PEP 8 - Guía de Estilo para Python</title><link href="https://alexanderae.com/pep8-guia-de-estilo-para-python.html" rel="alternate"/><published>2013-03-03T17:56:00-05:00</published><updated>2013-03-03T17:56:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-03-03:/pep8-guia-de-estilo-para-python.html</id><summary type="html">&lt;p&gt;Desde que empezamos&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; a escribir código y conforme progresamos, adquirimos ciertas pautas que definen la manera en que expresamos nuestras ideas en el lenguaje que manejemos, ya sea javascript, python, html u otro. Esto define nuestro &lt;em&gt;estilo&lt;/em&gt;, y toma en cuenta la manera en la que indentamos&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;, definimos nombres de variables y funciones, el proceso de realizar o no comentarios de codigo entre otros.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h5&gt;¿Por qué es importante adoptar un estilo?&lt;/h5&gt;
&lt;p&gt;Por muchos motivos:&lt;/p&gt;
&lt;p&gt;Es probable que el …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Desde que empezamos&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; a escribir código y conforme progresamos, adquirimos ciertas pautas que definen la manera en que expresamos nuestras ideas en el lenguaje que manejemos, ya sea javascript, python, html u otro. Esto define nuestro &lt;em&gt;estilo&lt;/em&gt;, y toma en cuenta la manera en la que indentamos&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;, definimos nombres de variables y funciones, el proceso de realizar o no comentarios de codigo entre otros.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h5&gt;¿Por qué es importante adoptar un estilo?&lt;/h5&gt;
&lt;p&gt;Por muchos motivos:&lt;/p&gt;
&lt;p&gt;Es probable que el código deba ser mantenido, ya sea por nosotros u otras personas:&lt;/p&gt;
&lt;p&gt;En caso de que seamos nosotros los responsables, no recordaremos por siempre que hacia tal o cual función o para que implementamos alguna clase. El solo hecho de añadir comentarios, mantener un orden de los métodos, clases y funciones, indentar y lo más importante: &lt;em&gt;el haber sido consistentes&lt;/em&gt;  puede ayudar en gran medida.&lt;/p&gt;
&lt;p&gt;Al trabajar en equipo esto se hace mucho más notorio. Si todos los miembros siguen unas líneas generales, aunque no se conozca con exactitud el "que hace" de algún bloque de código, al seguir un estilo el proyecto se hace legible y permite que el &lt;em&gt;"dolor"&lt;/em&gt; al revisarlo sea menor.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;¿Qué sucede cuando tienes que revisar código ajeno y observas inconsistencias en el estilo del código?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Puede que tengas que revisar el archivo operaciones.py y te encuentres con algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#convierto la variable &amp;#39;a&amp;#39; a entero&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#convierto la variable &amp;#39;b&amp;#39; a entero&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;multiplica&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;aunque podría haber sido peor&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;, a primera vista me parecería que el que escribiera algo así, es &lt;em&gt;inconsistente&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;¿Era necesario incluir los comentarios?&lt;/li&gt;
&lt;li&gt;Mientras en la función suma se indentó con 2 espacios, en las funciones siguientes se usaron.&lt;/li&gt;
&lt;li&gt;El espaciado entre variables y operadores tambien cambia según avanzamos.&lt;/li&gt;
&lt;li&gt;Inclusive la forma en la que definimos a las funciones, tanto para &lt;em&gt;suma&lt;/em&gt; y &lt;em&gt;resta&lt;/em&gt;, convertimos las variables a enteros en una línea aparte; pero para la multiplicación, lo hicimos todo en una sola ĺínea.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;¿Por donde comenzar?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Como siempre, no tenemos que inventar algún estándar para nuestro grupo de trabajo o para nosotros mismos&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt;, es muy probable que ya exista alguna convención para nuestro lenguaje preferido y tan solo debamos de revisarla y adoptarla&lt;sup id="fnref:5"&gt;&lt;a class="footnote-ref" href="#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt;, por ejemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSS: &lt;a href="https://github.com/styleguide/css"&gt;guia de estilos para css de github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;HTML - CSS: &lt;a href="http://google-styleguide.googlecode.com/svn/trunk/htmlcssguide.xml"&gt;guia de estilos para html y css de google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Javascript: &lt;a href="http://contribute.jquery.org/style-guide/js/"&gt;guia de estilos para javascript de jquery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ruby: &lt;a href="https://github.com/bbatsov/ruby-style-guide"&gt;guia de estilos para ruby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h5&gt;PEP 8&lt;/h5&gt;
&lt;p&gt;En python existe lo que se llaman PEP's o &lt;em&gt;Python Enhancement Proposals&lt;/em&gt;&lt;sup id="fnref:6"&gt;&lt;a class="footnote-ref" href="#fn:6"&gt;6&lt;/a&gt;&lt;/sup&gt;, una de las cuales, PEP 8&lt;sup id="fnref:7"&gt;&lt;a class="footnote-ref" href="#fn:7"&gt;7&lt;/a&gt;&lt;/sup&gt; está dedicada a la recopilación de los estándares seguidos por los desarrolladores de python a la hora de escribir código python para la librería estandar.&lt;/p&gt;
&lt;p&gt;Entre las convenciones principales, tenemos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Usar 4 espacios para indentar&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Es posible usar solo tabulaciones u ocho espacios para código antiguo que haya sido escrito así. &lt;em&gt;Por ningún motivo se han de mezclar espacios y tabulaciones&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Limitar los tamaños de línea a &lt;em&gt;79 caracteres como máximo&lt;/em&gt;, si bien se puede continuar líneas largas con el símbolo '&lt;em&gt;\&lt;/em&gt;', es recomendable el uso de paréntesis, por ejemplo en vez de:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
def __init__(self, first, second, third, fourth, fifth, sixth):
    output = first + second + third + fourth \
        + fifth + sixth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;podríamos hacer uso de los paréntesis:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
def __init__(self, first, second, third, fourth, fifth, sixth):
    output = (first + second + third + fourth
        + fifth + sixth)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Se deben separ las funciones de nivel superior y las clases con dos líneas en blanco, mientras que los métodos dentro de clases los podemos separar con una sola línea. También se pueden usar líneas en blanco dentro de las funciones para separ bloques que guardan cierta correlación lógica.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Las sentencias de &lt;em&gt;import&lt;/em&gt; deben de estar generalmente separadas una en cada línea, por ejemplo:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;Incorrecto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;Correcto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
            &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;Se&lt;/span&gt; &lt;span class="n"&gt;permite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Las sentencias &lt;em&gt;import&lt;/em&gt; deben de estar siempre en la parte superior del archivo agrupadas de la siguiente manera:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Libreria estándar&lt;/li&gt;
&lt;li&gt;Librerías de terceros&lt;/li&gt;
&lt;li&gt;import's de la aplicación local&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Usar espacios alrededor de los operadores aritméticos&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;No usar espacios alrededor del signo igual cuando se encuentre en un listado de argumentos de una función&lt;/em&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
Incorrecto: def suma(a = 0, b = 0):
Correcto:   def suma(a=0, b=0):
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Los comentarios que contradicen al código son &lt;em&gt;peores&lt;/em&gt; que cuando no existen comentarios!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No se deben de realizar comentarios obvios&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No se deben comparar booleanos mediante &lt;em&gt;==&lt;/em&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::&lt;span class="nv"&gt;python&lt;/span&gt;
&lt;span class="nv"&gt;Incorrecto&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;valido&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;True&lt;/span&gt;:
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nv"&gt;pass&lt;/span&gt;
&lt;span class="nv"&gt;Correcto&lt;/span&gt;:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;valido&lt;/span&gt;:
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nv"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pueden observar la lista completa en las referencias y como adicional:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://coding.smashingmagazine.com/2012/10/25/why-coding-style-matters/"&gt;¿Por qué el estilo de Programación importa?&lt;/a&gt;: un artículo en inglés de smashing magazine.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://python.net/~goodger/projects/pycon/2007/idiomatic/presentation.html"&gt;Code Like a Pythonista&lt;/a&gt;: Presentación en la que se dan varios tips para programar en python, incluidos algunos ya vistos en el PEP8.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Y no digo: 'desde que aprendimos', porque el aprendizaje es continuo y nunca terminamos de aprender.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;considerando que indenten.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;algún extremista (nótese que no me refiero a mí) podría recomendar el enviar a la hoguera al que escribio aquello.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;aunque se puede hacer si le dedicamos el esfuerzo necesario.&amp;#160;&lt;a class="footnote-backref" href="#fnref:4" title="Jump back to footnote 4 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;lo que no significa que sigamos la convención con los ojos cerrados y sin dudar, lo correcto sería extraer las partes que creamos convenientes y aplicarlas. Aunque si estamos iniciando, lo recomendable es seguir las normas y con la experiencia, opinar si debemos o no seguirlas.&amp;#160;&lt;a class="footnote-backref" href="#fnref:5" title="Jump back to footnote 5 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;son documentos que proveen información a la comunidad de python o describen nuevas carácterísticas de python, su proceso de desarrollo o su entorno. más información &lt;a href="http://www.python.org/dev/peps/pep-0001/#what-is-a-pep"&gt;aquí&lt;/a&gt;. Y un listado alternativo de los peps &lt;a href="http://www.peps.io/"&gt;aquí&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:6" title="Jump back to footnote 6 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;pep8 en &lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;inglés&lt;/a&gt; y una versión traducida al &lt;a href="http://mundogeek.net/traducciones/guia-estilo-python.htm"&gt;español&lt;/a&gt; por Raúl González Duque.&amp;#160;&lt;a class="footnote-backref" href="#fnref:7" title="Jump back to footnote 7 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="python"/><category term="desarrollo-de-software"/></entry><entry><title>Limpiar o resetear formularios en JS</title><link href="https://alexanderae.com/clear-reset-form-js.html" rel="alternate"/><published>2013-02-28T00:00:00-05:00</published><updated>2013-02-28T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-02-28:/clear-reset-form-js.html</id><summary type="html">&lt;p&gt;Fragmentos de código para limpiar o resetear formularios en JS. Note que "limpiar" borrará todo el contenido del formulario mientras que "resetear" restaurará los valores iniciales.&lt;/p&gt;
&lt;h4&gt;Reset form&lt;/h4&gt;
&lt;p&gt;Para resetear formularios existe un &lt;a href="http://www.w3schools.com/jsref/met_form_reset.asp"&gt;método&lt;/a&gt; de los forms:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::javascript
document.getElementById(&amp;quot;test-form&amp;quot;).reset();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Clear form&lt;/h4&gt;
&lt;p&gt;Para limpiar formularios en cambio, tenemos que escribir nuestra propia función, como por ejemplo&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clearForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;oForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Fragmentos de código para limpiar o resetear formularios en JS. Note que "limpiar" borrará todo el contenido del formulario mientras que "resetear" restaurará los valores iniciales.&lt;/p&gt;
&lt;h4&gt;Reset form&lt;/h4&gt;
&lt;p&gt;Para resetear formularios existe un &lt;a href="http://www.w3schools.com/jsref/met_form_reset.asp"&gt;método&lt;/a&gt; de los forms:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::javascript
document.getElementById(&amp;quot;test-form&amp;quot;).reset();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Clear form&lt;/h4&gt;
&lt;p&gt;Para limpiar formularios en cambio, tenemos que escribir nuestra propia función, como por ejemplo&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clearForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;oForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;textarea&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;hidden&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;radio&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;select-one&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;select-multi&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y la llamamos mediante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::javascript
clearForm(form);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;demo&lt;/h4&gt;
&lt;p&gt;A modo de ejemplo, puede visitar el siguiente &lt;a href="http://labs.alexanderae.com/clear-reset-form/"&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;El código se puede visualizar en el directorio 'clear-reset-form' del &lt;a href="https://bitbucket.org/__alexander__/alexander-ae-site-static-demos"&gt;repo&lt;/a&gt;&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Visto en: &lt;a href="http://www.javascript-coder.com/javascript-form/javascript-reset-form.phtml"&gt;javascript-coder.com&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="js"/></entry><entry><title>Posición absoluta y Relativa de un click en javascript</title><link href="https://alexanderae.com/posicion-de-click-en-js.html" rel="alternate"/><published>2013-02-24T00:00:00-05:00</published><updated>2013-02-24T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-02-24:/posicion-de-click-en-js.html</id><summary type="html">&lt;p&gt;Mientras intentaba desarrollar un plugin para jquery tuve un inconveniente, el cómo hacer para obtener la posición relativa de un evento click hacia su contenedor.&lt;/p&gt;
&lt;p&gt;Realizando algunas búsquedas en google y leyendo los diversos artículos pude obtener una idea del cómo estaba el panorama. Por ejemplo, revisando el tutorial de &lt;strong&gt;jquery&lt;/strong&gt; que versa sobre el &lt;a href="http://docs.jquery.com/Tutorials:Mouse_Position"&gt;cálculo de la posición del mouse&lt;/a&gt; podemos destacar algunos puntos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;pageX&lt;/em&gt;&lt;sup id="fnref:pageX"&gt;&lt;a class="footnote-ref" href="#fn:pageX"&gt;1&lt;/a&gt;&lt;/sup&gt; y &lt;em&gt;pageY&lt;/em&gt; son dos propiedades de todos los eventos javascript que nos indican …&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Mientras intentaba desarrollar un plugin para jquery tuve un inconveniente, el cómo hacer para obtener la posición relativa de un evento click hacia su contenedor.&lt;/p&gt;
&lt;p&gt;Realizando algunas búsquedas en google y leyendo los diversos artículos pude obtener una idea del cómo estaba el panorama. Por ejemplo, revisando el tutorial de &lt;strong&gt;jquery&lt;/strong&gt; que versa sobre el &lt;a href="http://docs.jquery.com/Tutorials:Mouse_Position"&gt;cálculo de la posición del mouse&lt;/a&gt; podemos destacar algunos puntos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;pageX&lt;/em&gt;&lt;sup id="fnref:pageX"&gt;&lt;a class="footnote-ref" href="#fn:pageX"&gt;1&lt;/a&gt;&lt;/sup&gt; y &lt;em&gt;pageY&lt;/em&gt; son dos propiedades de todos los eventos javascript que nos indican la posición absoluta del puntero respecto a la esquina izquierda de todo documento (no solo de la parte visible de la página).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Para realizar el cálculo de la posición relativa de un click nos muestran el siguiente ejemplo:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;
&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#special&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pageX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetLeft&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pageY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetTop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#status2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;lo que consiste en el cálculo de la posición absoluta del click menos la posición absoluta del contenedor.&lt;/p&gt;
&lt;h4&gt;stackoverflow&lt;/h4&gt;
&lt;p&gt;En mi búsqueda del como poder realizar esto, di con algunos enfoques interesantes en &lt;a href="http://stackoverflow.com/"&gt;stackoverflow&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;Por ejemplo, había quien recomendaba realizar un cálculo iterativo de las distancias relativas de cada contenedor con su padre:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;
&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;absolutePos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;curleft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;curtop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetParent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;curleft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetLeft&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;curtop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetTop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetParent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;curleft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;curtop&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note que &lt;em&gt;offsetLeft&lt;/em&gt;&lt;sup id="fnref:offsetLeft"&gt;&lt;a class="footnote-ref" href="#fn:offsetLeft"&gt;2&lt;/a&gt;&lt;/sup&gt; calcula la distancia del elemento actual en relación a su contenedor o padre.&lt;/p&gt;
&lt;p&gt;Y también quien sugería una &lt;a href="http://stackoverflow.com/a/5776220/1472750"&gt;mejora&lt;/a&gt; introduciendo los valores del &lt;em&gt;margin&lt;/em&gt;, &lt;em&gt;padding&lt;/em&gt; y &lt;em&gt;border&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getNumericStyleProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPropertyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;element_position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetLeft&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetTop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb nb-Type"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;borderTop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getNumericStyleProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;border-top-width&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;borderLeft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getNumericStyleProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;border-left-width&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;borderTop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;borderLeft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paddingTop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getNumericStyleProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;padding-top&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paddingLeft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getNumericStyleProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;padding-left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paddingTop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paddingLeft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marginTop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getNumericStyleProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;margin-top&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marginLeft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getNumericStyleProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;margin-left&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marginTop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marginLeft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offsetParent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y por último, la solución a mi problema. Al parecer existen un par de propiedades de los eventos que indican la posición relativa al contenedor en el que fueron realizados: &lt;strong&gt;offsetX&lt;/strong&gt;&lt;sup id="fnref:offsetX"&gt;&lt;a class="footnote-ref" href="#fn:offsetX"&gt;3&lt;/a&gt;&lt;/sup&gt; y offsetY.
Por lo que para nuestros propósitos, bastaría con algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::jquery
$(&amp;#39;selector&amp;#39;).click(function(ev){
    x = ev.offsetX;
    y = ev.offsetY;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;y todo parecería perfecto .. exceptuando por un &lt;em&gt;'insignificante'&lt;/em&gt; problema, &lt;strong&gt;offsetX&lt;/strong&gt; no está definido&lt;sup id="fnref:offsetX-firefox"&gt;&lt;a class="footnote-ref" href="#fn:offsetX-firefox"&gt;4&lt;/a&gt;&lt;/sup&gt; en Firefox.
Pero menos mal que si revisamos en &lt;a href="http://stackoverflow.com/q/12704686/1472750"&gt;stackoverflow&lt;/a&gt; nuevamente, podremos encontrar alguna solución, como por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::&lt;span class="nv"&gt;jquery&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ev&lt;/span&gt;.&lt;span class="nv"&gt;offsetX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;undefined&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;para&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;firefox&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ev&lt;/span&gt;.&lt;span class="nv"&gt;pageX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;offset&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;.&lt;span class="nv"&gt;left&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ev&lt;/span&gt;.&lt;span class="nv"&gt;pageY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;offset&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;.&lt;span class="nv"&gt;top&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
}
&lt;span class="k"&gt;else&lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;chrome&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ev&lt;/span&gt;.&lt;span class="nv"&gt;offsetX&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ev&lt;/span&gt;.&lt;span class="nv"&gt;offsetY&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;demo&lt;/h4&gt;
&lt;p&gt;A modo de &lt;a href="http://labs.alexanderae.com/posicion-relativa-de-un-click/"&gt;ejemplo&lt;/a&gt;, armé una &lt;a href="http://labs.alexanderae.com/posicion-relativa-de-un-click/"&gt;demo&lt;/a&gt; en la que se trata de dibujar un punto de 2x2 en la posición en la que se realiza un click tal y como muestra la siguiente captura:&lt;/p&gt;
&lt;p&gt;&lt;img alt="demo" src="/pictures/posicion-de-un-click-demo.png" title="Demo"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Notas:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;En el ejemplo se comparan el método encontrado en la documentación de jquery así como la solución que utilicé, imprimiendo ambos resultados en la consola.&lt;/li&gt;
&lt;li&gt;Observando que si bien en chrome ambos cálculos coinciden, en firefox hay una diferencia de 4px posiblemente al margin que le di al body (requeriría un mayor análisis).&lt;/li&gt;
&lt;li&gt;Para dibujar el punto solo se utilizó la solución que expuse como final.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="consola del firebug en firefox" src="/pictures/posicion-de-un-click-demo-firefox.png" title="Consola en firefox"&gt;&lt;/p&gt;
&lt;p&gt;Código en bitbucket del ejemplo: &lt;a href="https://bitbucket.org/__alexander__/alexander-ae-site-static-demos"&gt;demo&lt;/a&gt; (el directorio es: posicion-relativa-de-un-click)&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:pageX"&gt;
&lt;p&gt;Mozilla developers: &lt;a href="https://developer.mozilla.org/en-US/docs/DOM/event.pageX"&gt;pageX&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:pageX" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:offsetLeft"&gt;
&lt;p&gt;Mozilla developers: &lt;a href="https://developer.mozilla.org/en-US/docs/DOM/element.offsetLeft"&gt;offsetLeft&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:offsetLeft" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:offsetX"&gt;
&lt;p&gt;Especificación de la propiedad: &lt;a href="http://www.w3.org/TR/cssom-view/#dom-mouseevent-offsetx"&gt;offsetX&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:offsetX" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:offsetX-firefox"&gt;
&lt;p&gt;&lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69787"&gt;reporte&lt;/a&gt; del bug en bugzilla&amp;#160;&lt;a class="footnote-backref" href="#fnref:offsetX-firefox" title="Jump back to footnote 4 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="jquery"/><category term="js"/></entry><entry><title>Jquery StickyBox</title><link href="https://alexanderae.com/jquery-stickybox.html" rel="alternate"/><published>2013-02-17T00:00:00-05:00</published><updated>2013-02-17T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-02-17:/jquery-stickybox.html</id><summary type="html">&lt;p&gt;Plugin para jquery que genera bloques flotantes que siguen al usuario cuando se desplaza la página. Utiliza posicionamiento absoluto para reajustar la posición en base a los estilos css iniciales.&lt;/p&gt;
&lt;p&gt;Por ejemplo, se podría usar en los siguientes casos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Para realizar carritos de compras que muestran información de la compra actual&lt;/li&gt;
&lt;li&gt;En caso de querer añadir botones sociales (facebook, twitter..)&lt;/li&gt;
&lt;li&gt;Para generar menús flotantes&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Requerimientos e Instalación&lt;/h4&gt;
&lt;p&gt;El plugin requiere tanto de &lt;a href="http://jquery.com/"&gt;jquery&lt;/a&gt; como de &lt;a href="http://gsgd.co.uk/sandbox/jquery/easing/"&gt;jquery easing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Los scripts &lt;strong&gt;js …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Plugin para jquery que genera bloques flotantes que siguen al usuario cuando se desplaza la página. Utiliza posicionamiento absoluto para reajustar la posición en base a los estilos css iniciales.&lt;/p&gt;
&lt;p&gt;Por ejemplo, se podría usar en los siguientes casos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Para realizar carritos de compras que muestran información de la compra actual&lt;/li&gt;
&lt;li&gt;En caso de querer añadir botones sociales (facebook, twitter..)&lt;/li&gt;
&lt;li&gt;Para generar menús flotantes&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Requerimientos e Instalación&lt;/h4&gt;
&lt;p&gt;El plugin requiere tanto de &lt;a href="http://jquery.com/"&gt;jquery&lt;/a&gt; como de &lt;a href="http://gsgd.co.uk/sandbox/jquery/easing/"&gt;jquery easing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Los scripts &lt;strong&gt;js&lt;/strong&gt; requeridos son:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://code.jquery.com/jquery-1.8.3.min.js&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;http://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;js/stickysidebar.jquery.min.js&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por ejemplo, el código &lt;strong&gt;html&lt;/strong&gt; para un carrito de compras flotante podría ser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;aside&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sidebar&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;basket&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Carrito&lt;span class="w"&gt; &lt;/span&gt;de&lt;span class="w"&gt; &lt;/span&gt;compras&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;items&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;zero-items&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;E inicializaríamos el plugin mediante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;jquery&lt;/span&gt;
&lt;span class="o"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#sidebar&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;stickySidebar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;easing&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;easeInOutQuad&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En su &lt;a href="http://www.profilepicture.co.uk/sticky-sidebar-jquery-plugin/"&gt;sitio web&lt;/a&gt; podremos ver con un poco de más de detalle los parámetros disponibles y un par de ejemplos del mismo.&lt;/p&gt;
&lt;p&gt;A modo de práctica yo repliqué el primero de sus ejemplos (carrito de compras) con algunos detalles adicionales, el cual puede verse &lt;a href="http://labs.alexanderae.com/html-js-css/sticky-sidebar-jquery-plugin/"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="demo-img" src="/pictures/sticky-sidebar-jquery-plugin.png" title="Captura de pantalla"&gt;&lt;/p&gt;
&lt;p&gt;Código en github del plugin: &lt;a href="https://github.com/p-m-p/jQuery-Stickybox"&gt;sticky-sidebar-jquery-plugin&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Código en bitbucket de la demo que realizé: &lt;a href="https://bitbucket.org/__alexander__/alexander-ae-site-static-demos"&gt;demo&lt;/a&gt; (el directorio es: sticky-sidebar-jquery-plugin)&lt;/p&gt;</content><category term="blog"/><category term="jquery"/><category term="js"/></entry><entry><title>Piu Avanti</title><link href="https://alexanderae.com/piu-avanti.html" rel="alternate"/><published>2013-02-03T00:00:00-05:00</published><updated>2013-02-03T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-02-03:/piu-avanti.html</id><summary type="html">&lt;p&gt;&lt;a href="http://es.wikipedia.org/wiki/Pedro_Bonifacio_Palacios"&gt;Pedro Bonifacio Palacios&lt;/a&gt;, poeta argentino conocido por varios pseudónimos siendo &lt;strong&gt;Almafuerte&lt;/strong&gt; con el cual destacó.
A mi parecer, sus cinco sonetos Avanti! son su mejor obra, de entre los cuales prefiero el segundo que es:&lt;/p&gt;
&lt;h4&gt;Piu Avanti&lt;/h4&gt;
&lt;p&gt;No te des por vencido, ni aun vencido,
no te sientas esclavo, ni aun esclavo;
trémulo de pavor, piénsate bravo,
y arremete feroz, ya mal herido.&lt;/p&gt;
&lt;p&gt;Ten el tesón del clavo enmohecido
que ya viejo y ruin, vuelve a ser clavo;
no la …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://es.wikipedia.org/wiki/Pedro_Bonifacio_Palacios"&gt;Pedro Bonifacio Palacios&lt;/a&gt;, poeta argentino conocido por varios pseudónimos siendo &lt;strong&gt;Almafuerte&lt;/strong&gt; con el cual destacó.
A mi parecer, sus cinco sonetos Avanti! son su mejor obra, de entre los cuales prefiero el segundo que es:&lt;/p&gt;
&lt;h4&gt;Piu Avanti&lt;/h4&gt;
&lt;p&gt;No te des por vencido, ni aun vencido,
no te sientas esclavo, ni aun esclavo;
trémulo de pavor, piénsate bravo,
y arremete feroz, ya mal herido.&lt;/p&gt;
&lt;p&gt;Ten el tesón del clavo enmohecido
que ya viejo y ruin, vuelve a ser clavo;
no la cobarde estupidez del pavo
que amaina su plumaje al primer ruido.&lt;/p&gt;
&lt;p&gt;Procede como Dios que nunca llora;
o como Lucifer, que nunca reza;
o como el robledal, cuya grandeza
necesita del agua y no la implora...&lt;/p&gt;
&lt;p&gt;Que muerda y vocifere vengadora,
ya rodando en el polvo, tu cabeza!&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:piu-avanti"&gt;
&lt;p&gt;Los demás poemas los podemos leer &lt;a href="http://www.fmmeducacion.com.ar/Escritos/Poesias/avanti.htm"&gt;aquí&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:piu-avanti" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="poesia"/></entry><entry><title>Django Apps: admin_honeypot</title><link href="https://alexanderae.com/django-apps-admin_honeypot.html" rel="alternate"/><published>2013-01-27T22:50:00-05:00</published><updated>2013-01-27T22:50:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-01-27:/django-apps-admin_honeypot.html</id><summary type="html">&lt;p&gt;En django, por defecto se acostumbra a usar la url &lt;strong&gt;/admin/&lt;/strong&gt; como medio de acceso a la interfaz de administración. Sucede que hay quien conociendo esto intenta acceder a dicha url probando algunos usuarios/claves comunes, inclusive por medio de un ataque fuerza bruta con alguna herramienta.&lt;/p&gt;
&lt;p&gt;Una medida sencilla con la que podremos filtrar algunos intentos no autorizados de acceso es cambiando esta url por defecto de &lt;strong&gt;/admin/&lt;/strong&gt; a alguna otra por ejemplo a &lt;strong&gt;/mi_nuevo_admin/&lt;/strong&gt;&lt;sup id="fnref:debug"&gt;&lt;a class="footnote-ref" href="#fn:debug"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Pero tal vez …&lt;/p&gt;</summary><content type="html">&lt;p&gt;En django, por defecto se acostumbra a usar la url &lt;strong&gt;/admin/&lt;/strong&gt; como medio de acceso a la interfaz de administración. Sucede que hay quien conociendo esto intenta acceder a dicha url probando algunos usuarios/claves comunes, inclusive por medio de un ataque fuerza bruta con alguna herramienta.&lt;/p&gt;
&lt;p&gt;Una medida sencilla con la que podremos filtrar algunos intentos no autorizados de acceso es cambiando esta url por defecto de &lt;strong&gt;/admin/&lt;/strong&gt; a alguna otra por ejemplo a &lt;strong&gt;/mi_nuevo_admin/&lt;/strong&gt;&lt;sup id="fnref:debug"&gt;&lt;a class="footnote-ref" href="#fn:debug"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Pero tal vez aún así queremos saber si alguien intentó acceder a la url por defecto. Allí es donde &lt;a href="http://django-admin-honeypot.readthedocs.org/en/latest/"&gt;django-admin-honeypot&lt;/a&gt; nos puede ayudar.&lt;/p&gt;
&lt;p&gt;Esta aplicación mantiene la url por defecto (/admin) así como la nueva activas. Y en caso de que alguien intente acceder a la url equivocada, nos enviará una notificación por email advirtiendonos del suceso.&lt;/p&gt;
&lt;h4&gt;Instalación&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Requerimos instalar el paquete, lo podemos realizar por medio del clásico pip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install django-admin-honeypot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Añadido admin_honeypot a nuestra lista de aplicaciones instaladas:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
INSTALLED_APPS = (
    ...

    &amp;#39;admin_honeypot&amp;#39;,

    ...
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Actualizamos nuestras url's (urls.py):&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;::python
urlpatterns = patterns(&amp;#39;&amp;#39;
    ...
    url(r&amp;#39;^admin/&amp;#39;, include(&amp;#39;admin_honeypot.urls&amp;#39;)),
    url(r&amp;#39;^mi_nuevo_admin/&amp;#39;, include(admin.site.urls)),
    ...
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Notas adicionales&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Podemos ajustar la variable &lt;strong&gt;ADMIN_HONEYPOT_EMAIL_ADMINS&lt;/strong&gt; a &lt;strong&gt;True&lt;/strong&gt; para permitir que la aplicación envíe un email a los &lt;a href="https://docs.djangoproject.com/en/1.5/ref/settings/#admins"&gt;administradores&lt;/a&gt; del sitio cada vez que se produzca un error de acceso a la falsa url. (Por defecto está en &lt;strong&gt;False&lt;/strong&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Los intentos de acceso errados son almacenados en la base de datos, siendo los permisos de eliminación de los mismos negados a todos los usuarios&lt;sup id="fnref:delete-login"&gt;&lt;a class="footnote-ref" href="#fn:delete-login"&gt;2&lt;/a&gt;&lt;/sup&gt;. Esto impide que cualquier usuario 'no autorizado' borre sus huellas.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si bien solo esta aplicación nos ayuda con parte del problema, es posible que debamos buscar otros complementos/alternativas para mejorar la seguridad del sistema.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:debug"&gt;
&lt;p&gt;Note que esto no nos sería de mucha utilidad si tenemos la variable &lt;a href="https://docs.djangoproject.com/en/1.5/ref/settings/#debug"&gt;debug&lt;/a&gt; como &lt;strong&gt;True&lt;/strong&gt; mientras estamos en producción.&amp;#160;&lt;a class="footnote-backref" href="#fnref:debug" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:delete-login"&gt;
&lt;p&gt;&lt;a href="http://django-admin-honeypot.readthedocs.org/en/latest/manual/faq.html#why-can-t-i-delete-login-attempts-from-the-django-admin"&gt;Why can’t I delete login attempts from the Django admin?&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:delete-login" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="django"/><category term="django-apps"/><category term="seguridad"/></entry><entry><title>Django Apps: South</title><link href="https://alexanderae.com/django-apps-south.html" rel="alternate"/><published>2013-01-25T18:50:00-05:00</published><updated>2013-01-25T18:50:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-01-25:/django-apps-south.html</id><summary type="html">&lt;p&gt;Se me ha dado por iniciar una revisión de algunas de las aplicaciones que uso en conjunto con &lt;a href="https://www.djangoproject.com/"&gt;django&lt;/a&gt;, lo que me permitirá de algún modo no solo el transmitir conocimiento, sino también afianzar lo que sé, ya que para redactar los artículos deberé de leer un tanto de la documentación (a modo de repaso).&lt;/p&gt;
&lt;p&gt;Iniciaré con &lt;a href="http://south.aeracode.org/"&gt;south&lt;/a&gt;, una aplicación que nos permite realizar migraciones de datos en nuestros proyectos/aplicaciones.&lt;/p&gt;
&lt;h4&gt;¿Qué es una migración?&lt;/h4&gt;
&lt;p&gt;Se refiere al cambio del …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Se me ha dado por iniciar una revisión de algunas de las aplicaciones que uso en conjunto con &lt;a href="https://www.djangoproject.com/"&gt;django&lt;/a&gt;, lo que me permitirá de algún modo no solo el transmitir conocimiento, sino también afianzar lo que sé, ya que para redactar los artículos deberé de leer un tanto de la documentación (a modo de repaso).&lt;/p&gt;
&lt;p&gt;Iniciaré con &lt;a href="http://south.aeracode.org/"&gt;south&lt;/a&gt;, una aplicación que nos permite realizar migraciones de datos en nuestros proyectos/aplicaciones.&lt;/p&gt;
&lt;h4&gt;¿Qué es una migración?&lt;/h4&gt;
&lt;p&gt;Se refiere al cambio del esquema de la base de datos de una versión a otra. Por ejemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;El añadir un nuevo modelo(tabla) a nuestra aplicación.&lt;/li&gt;
&lt;li&gt;El añadir o remover un campo de nuestro modelo.&lt;/li&gt;
&lt;li&gt;El cambiar uno de los atributos de alguna tabla por ejemplo de null=False a null=True&lt;/li&gt;
&lt;li&gt;Como nos indican en la documentación, para south una migración incluye el crear el esquema inicial de nuestra base de datos.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;¿Por qué debo usar south?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;south puede leer automáticamente los cambios que hemos realizado en nuestra base de datos y crear con un simple comando ingresado la migración necesaria. (en caso de conflictos, requerirá intervención del usuario por medio de algún mensaje)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Al igual que el ORM  de &lt;strong&gt;Django&lt;/strong&gt;, es independiente del motor de base de datos a usar, esto quiere decir que no obtendremos problemas si usamos sqlite, mysql o postgresql. A la fecha&lt;sup id="fnref:database-api"&gt;&lt;a class="footnote-ref" href="#fn:database-api"&gt;1&lt;/a&gt;&lt;/sup&gt;, el soporte para Microsoft SQL Server y Oracle estan en fase beta y alfa respectivamente.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Permite manejar cada aplicación de manera independiente, es decir, podemos escoger que aplicaciones serán manejadas por south y cuales lo serán por medio del ya conocido 'syncdb'.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;South esta diseñado para detectar y advertirnos sobre conflictos introducidos por algún colaborador del proyecto mediante el sistema de control de versiones.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Instalación&lt;/h4&gt;
&lt;p&gt;Existen muchas maneras de instalar south, por medio de easy_install, pip, &lt;a href="http://south.readthedocs.org/en/0.7.6/installation.html#using-mercurial"&gt;clonando&lt;/a&gt; la última versión del repositorio en mercurial, alguno de los &lt;a href="http://south.readthedocs.org/en/0.7.6/installation.html#using-downloadable-archives"&gt;paquetes descargables&lt;/a&gt; que nos otorgan o tal vez mediante el gestor de paquetes de nuestra distribución linux preferida (en el caso de que se incluyese south allí).&lt;/p&gt;
&lt;p&gt;Mi método preferido&lt;sup id="fnref:south-install"&gt;&lt;a class="footnote-ref" href="#fn:south-install"&gt;2&lt;/a&gt;&lt;/sup&gt; es por medio de &lt;a href="http://www.pip-installer.org/en/latest/"&gt;pip&lt;/a&gt; ya que lo uso en conjunto con los entornos virtuales de &lt;a href="http://www.virtualenv.org/en/latest/"&gt;virtualenv&lt;/a&gt;. Entonces por medio de pip, lo instalamos por medio de la siguiente instrucción:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install south
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y para nuestro proyecto en django añadimos &lt;strong&gt;'south',&lt;/strong&gt; a la relación de aplicaciones instaladas (INSTALLED_APPS).&lt;/p&gt;
&lt;h4&gt;Tutorial&lt;/h4&gt;
&lt;p&gt;En la documentación de south podemos encontrar un &lt;a href="http://south.readthedocs.org/en/latest/tutorial/index.html#tutorial"&gt;tutorial&lt;/a&gt; muy detallado sobre el uso de south, a continuación, un muy breve resumen:&lt;/p&gt;
&lt;h5&gt;Un nuevo proyecto en django con south&lt;/h5&gt;
&lt;p&gt;Por ejemplo, supongamos que estamos desarrollando un nuevo proyecto al que llamaremos ingeniosamente southTest con la aplicación testapp, la cual está enfocada a la administración de libros de una biblioteca.&lt;/p&gt;
&lt;p&gt;Uno de los modelos de nuestra aplicación es el modelo libro, que contiene el título y el autor ambos como campos de texto.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lo primero a realizar es crear la primera migración de nuestra aplicación, lo cual podemos realizar con el comando&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./manage.py schemamigration testapp --initial
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Con esto se debe de haber creado un directorio en nuestra aplicación conteniendo la migración inicial, tal y como se muestra en la siguiente imagen:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="proyecto: estructura" src="/pictures/django-south-02.png" title="Estructura del proyecto"&gt;&lt;/p&gt;
&lt;p&gt;Antes de realizar la migración debemos de sincronizar la base de datos. Debido a que existe una migración para la aplicación 'testapp', el comando &lt;em&gt;syncdb&lt;/em&gt; creará solo las tablas que están fuera de nuestra aplicación y nos indicará también cuales son las aplicaciones que estan o no administradas por south.&lt;/p&gt;
&lt;p&gt;&lt;img alt="proyecto-syncdb" src="/pictures/django-south-03.png" title="syncdb"&gt;&lt;/p&gt;
&lt;p&gt;y tal como nos indica. ejecutamos la orden:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    ./manage.py migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;con lo que se crea la única tabla de nuestro aplicación, la cual podemos observar desde el admin de django (en caso de haber registrado nuestro modelo)&lt;/p&gt;
&lt;p&gt;&lt;img alt="admin: Libro" src="/pictures/django-south-01.png" title="admin: libro"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Después de ingresar algunos libros, nos piden que la aplicación también deba de ser capaz de registrar los capítulos por cada libro, por lo que añadimos el modelo correspondiente:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="proyecto-models" src="/pictures/django-south-04.png" title="models.py"&gt;&lt;/p&gt;
&lt;p&gt;creamos la migración con una variación del comando que usamos por primera vez y ejecutamos la migración:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    ./manage.py schemamigration testapp --auto
    ./manage.py migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;a partir de este momento, cada nuevo cambio en el esquema de nuestra aplicación requerirá generalmente la ejecución de los dos comandos anteriores.&lt;/p&gt;
&lt;p&gt;Con esto doy por terminado&lt;sup id="fnref:repo"&gt;&lt;a class="footnote-ref" href="#fn:repo"&gt;3&lt;/a&gt;&lt;/sup&gt; este exageradamente pequeño tutorial, que lo que pretende es mostrar el uso de los comandos básicos de south, y que generalmente son los únicos que usaremos (espero realizar en algún momento una segunda parte con un uso más complejo de south).&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:database-api"&gt;
&lt;p&gt;&lt;a href="http://south.readthedocs.org/en/latest/databaseapi.html#database-specific-issues"&gt;South Database API&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:database-api" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:south-install"&gt;
&lt;p&gt;pip junto a easy_install son los métodos recomendados de instalación.&amp;#160;&lt;a class="footnote-backref" href="#fnref:south-install" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:repo"&gt;
&lt;p&gt;&lt;a href="https://bitbucket.org/__alexander__/test-django-south"&gt;Repositorio&lt;/a&gt; en bitbucket del proyecto de ejemplo&amp;#160;&lt;a class="footnote-backref" href="#fnref:repo" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="django"/><category term="django-apps"/><category term="base-de-datos"/></entry><entry><title>Resetear password de PostgreSQL</title><link href="https://alexanderae.com/recuperar-password-de-postgresql.html" rel="alternate"/><published>2013-01-12T10:27:00-05:00</published><updated>2013-01-12T10:27:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-01-12:/recuperar-password-de-postgresql.html</id><summary type="html">&lt;p&gt;Recurrentemente he necesitado cambiar la contraseña del usuario '&lt;em&gt;postgres&lt;/em&gt;' del postgresql, por lo cual incluyo aquí una nota&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; para mi mismo y para a quien esto le pueda ayudar:&lt;/p&gt;
&lt;p&gt;Pasos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Abrimos una terminal y accedemos como usuario postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo su postgres
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ejecutamos &lt;a href="http://www.postgresql.org/docs/9.2/static/app-psql.html"&gt;psql&lt;/a&gt; para acceder a la terminal interactiva de postgresql:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;psql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ahora escribimos esta sentencia SQL que nos permitirá asignar una nueva contraseña al usuario postgres:&lt;/p&gt;
&lt;p&gt;alter user postgres with password 'nueva_contraseña';&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Salimos de psql con:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\q
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cerramos …&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;Recurrentemente he necesitado cambiar la contraseña del usuario '&lt;em&gt;postgres&lt;/em&gt;' del postgresql, por lo cual incluyo aquí una nota&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; para mi mismo y para a quien esto le pueda ayudar:&lt;/p&gt;
&lt;p&gt;Pasos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Abrimos una terminal y accedemos como usuario postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo su postgres
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ejecutamos &lt;a href="http://www.postgresql.org/docs/9.2/static/app-psql.html"&gt;psql&lt;/a&gt; para acceder a la terminal interactiva de postgresql:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;psql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ahora escribimos esta sentencia SQL que nos permitirá asignar una nueva contraseña al usuario postgres:&lt;/p&gt;
&lt;p&gt;alter user postgres with password 'nueva_contraseña';&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Salimos de psql con:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\q
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cerramos nuestra sesión como postgres:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;y listo, contraseña cambiada.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Visto en el blog de &lt;a href="http://johansoft.blogspot.com/2007/09/cambiar-contrasea-de-usuario-postgres.html"&gt;Johan Hernandez&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="postgresql"/><category term="base-de-datos"/></entry><entry><title>Twitter Bootstrap vs Foundation 3</title><link href="https://alexanderae.com/bootstrap-vs-foundation.html" rel="alternate"/><published>2013-01-01T11:47:00-05:00</published><updated>2013-01-01T11:47:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2013-01-01:/bootstrap-vs-foundation.html</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;Artículo desactualizado&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hace ya un buen tiempo que llevo usando &lt;a href="http://twitter.github.com/bootstrap/"&gt;bootstrap&lt;/a&gt; para maquetar uno que otro sitio, sobre todo por la facilidad que te otorga el tener una base sobre la que comenzar. Recientemente&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; he probado &lt;a href="http://foundation.zurb.com/"&gt;Foundation&lt;/a&gt;, sobre todo porque siempre es bueno conocer las alternativas. Y a pesar de que tan solo lo he usado un par de de días, podría decir que en algunos aspectos puede ser mucho más conveniente, que el usar bootstrap.&lt;/p&gt;
&lt;p&gt;Si bien podría …&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;Artículo desactualizado&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hace ya un buen tiempo que llevo usando &lt;a href="http://twitter.github.com/bootstrap/"&gt;bootstrap&lt;/a&gt; para maquetar uno que otro sitio, sobre todo por la facilidad que te otorga el tener una base sobre la que comenzar. Recientemente&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; he probado &lt;a href="http://foundation.zurb.com/"&gt;Foundation&lt;/a&gt;, sobre todo porque siempre es bueno conocer las alternativas. Y a pesar de que tan solo lo he usado un par de de días, podría decir que en algunos aspectos puede ser mucho más conveniente, que el usar bootstrap.&lt;/p&gt;
&lt;p&gt;Si bien podría realizar aquí una comparativa de las bondades (a mi parecer) de cada uno de estos proyectos, ¿por qué repetir lo que ya esta hecho? (DRY&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;
&lt;p&gt;Lo que sí haré, será enumerar algunos buenos artículos sobre la comparación entre estos frameworks e incluir algun breve comentario.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://responsive.vermilion.com/compare.php"&gt;Responsive CSS Framework Comparison&lt;/a&gt;: Aquí, por medio de varias tablas comparativas, se nos muestran diversos aspectos de Foundation(v2, v3), Bootstrap v2 y Skeleton. Resulta excelente para visualizar varios aspectos técnicos de cada uno de los dos.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://nicinabox.com/blog/why-i-use-foundation-over-bootstrap/"&gt;Why I Use Foundation Over Bootstrap&lt;/a&gt;: Muestra el punto de vista del autor respecto al por qué usa Foundation en vez de Bootstrap. Y al final se nos recuerda, que ambos frameworks no son excluyentes, sino que podemos coger lo mejor de cada uno y construir nuestra propia quimera.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ivanmendoza.net/diseno-web-2/foundation-vs-bootstrap-round-1"&gt;Foundation vs Bootstrap:Round 1&lt;/a&gt;: Otro punto de vista que concluye con el uso de &lt;strong&gt;Foundation&lt;/strong&gt; para el caso de que gustemos tener un mayor control sobre la interfaz y &lt;strong&gt;Bootstrap&lt;/strong&gt; para cuando deseemos tener un set de elementos gráficos predefinidos y no esperemos realizar grandes cambios a los mismos.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ui-cloud.com/ui-cage-match-bootstrap-2-2-1-vs-foundation-3-2/"&gt;UI Cage Match: Bootstrap 2.2.1 vs. Foundation 3.2&lt;/a&gt;: Una última comparativa, muy reciente por cierto (22/12/2012). El autor realiza incluso una maqueta y la construye por medio de cada una de estas herramientas. Nos muestra las diferencias generales entre los elementos UI, complementos js y la cuadrícula.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En mi caso, prefiero &lt;strong&gt;Foundation&lt;/strong&gt; por el facil manejo que me da sobre la cuadrícula, porque las clases me parecen más intuitivas y me da un mayor control sobre los estilos (&lt;em&gt;lo cual a mi parecer es excelente&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Tal vez cuando desarrolle una mayor cantidad de sitios con Foundation pueda dar una más completa opinion, después de todo ambos frameworks son muy buenos y todo depende de nuestras necesidades, y como dicen:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;'No hay herramienta mala, sino herramienta en malas manos'&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Para el diseño de este sitio&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Principio &lt;a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself"&gt;'Don't repeat yourself'&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="front-end"/><category term="css"/></entry><entry><title>Configurando Pelican en webfaction</title><link href="https://alexanderae.com/configurando-pelican-en-webfaction.html" rel="alternate"/><published>2012-12-25T15:38:00-05:00</published><updated>2012-12-25T15:38:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2012-12-25:/configurando-pelican-en-webfaction.html</id><summary type="html">&lt;p&gt;Ya que este sitio se encuentra alojado en webfaction y es generado gracias a &lt;a href="http://docs.getpelican.com/en/latest/" title="pelican static site generator"&gt;Pelican&lt;/a&gt;, me pareció que debía de realizar una breve explicación de los pasos que seguí para configurarlo.&lt;/p&gt;
&lt;p&gt;Por la naturaleza del contenido, la aplicación que necesitamos crear debe ser del tipo estática. En webfaction podemos crear aplicaciones estáticas, entre las que tenemos 2 clases principalmente:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Static/CGI/PHP: permiten interpretar scripts CGI y aplicaciones PHP.&lt;/li&gt;
&lt;li&gt;Static only: sirve exclusivamente archivos estáticos lo que se traduce en …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;Ya que este sitio se encuentra alojado en webfaction y es generado gracias a &lt;a href="http://docs.getpelican.com/en/latest/" title="pelican static site generator"&gt;Pelican&lt;/a&gt;, me pareció que debía de realizar una breve explicación de los pasos que seguí para configurarlo.&lt;/p&gt;
&lt;p&gt;Por la naturaleza del contenido, la aplicación que necesitamos crear debe ser del tipo estática. En webfaction podemos crear aplicaciones estáticas, entre las que tenemos 2 clases principalmente:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Static/CGI/PHP: permiten interpretar scripts CGI y aplicaciones PHP.&lt;/li&gt;
&lt;li&gt;Static only: sirve exclusivamente archivos estáticos lo que se traduce en un menor consumo de memoria&lt;sup id="fnref:webfaction-static"&gt;&lt;a class="footnote-ref" href="#fn:webfaction-static"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Para nuestro caso, nos bastará con la del tipo &lt;em&gt;static only&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="webfaction static image" src="/pictures/webfaction-new-app-static.png" title="webfaction new static app"&gt;&lt;/p&gt;
&lt;p&gt;Una vez creada la aplicación, debemos de configurar nuestro sitio para generar los archivos respectivos en el directorio asignado, el cual estará ubicado por ejemplo en: &lt;em&gt;$HOME/webapps/static_app&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pelican&lt;/strong&gt; maneja los parámetros de &lt;em&gt;producción&lt;/em&gt; de nuestro proyecto en el archivo llamado 'publishconf.py'. Para lo anterior, debemos de incluir la siguiente linea:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;OUTPUT_PATH = &amp;#39;/home/&amp;lt;nombre_de_usuario&amp;gt;/webapps/static_app&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;donde static_app corresponde a nuestra aplicación estática creada.&lt;/p&gt;
&lt;p&gt;despues de configurar nuestra aplicación&lt;sup id="fnref:virtualenv"&gt;&lt;a class="footnote-ref" href="#fn:virtualenv"&gt;3&lt;/a&gt;&lt;/sup&gt; podemos ejecutar el comando:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make publish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;que colectará los archivos generados en el directorio indicado.&lt;/p&gt;
&lt;p&gt;Aunque muy posiblemente exista alguna otra mejor manera, de momento es la que uso; y por lo que veo, a diferencia de configurar algún otro tipo de aplicación&lt;sup id="fnref:django"&gt;&lt;a class="footnote-ref" href="#fn:django"&gt;2&lt;/a&gt;&lt;/sup&gt; en webfaction, configurar pelican resulta exageradamente sencillo.&lt;/p&gt;
&lt;p&gt;Ahora lo que sigue, al menos para mí, será el aprender a usar &lt;a href="http://docs.fabfile.org/en/1.5/"&gt;fabric&lt;/a&gt; para automatizar la tarea de publicar entradas.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:webfaction-static"&gt;
&lt;p&gt;&lt;a href="http://docs.webfaction.com/software/static.html"&gt;http://docs.webfaction.com/software/static.html&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:webfaction-static" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:django"&gt;
&lt;p&gt;como django, que incluye creación de una base de datos, de la aplicación django propiamente dicha y la que maneja los &lt;em&gt;static&lt;/em&gt; y &lt;em&gt;media&lt;/em&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:django" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:virtualenv"&gt;
&lt;p&gt;he asumido que se tiene un entorno (posiblemente con virtualenv) en el que hemos instalado la pelican y los requerimientos adicionales de nuestra aplicación.&amp;#160;&lt;a class="footnote-backref" href="#fnref:virtualenv" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="pelican"/><category term="webfaction"/><category term="back-end"/></entry><entry><title>Hola Mundo ..</title><link href="https://alexanderae.com/hola-mundo.html" rel="alternate"/><published>2012-12-24T00:00:00-05:00</published><updated>2012-12-24T00:00:00-05:00</updated><author><name>__alexander__</name></author><id>tag:alexanderae.com,2012-12-24:/hola-mundo.html</id><summary type="html">&lt;p&gt;Hola mundo .. nuevamente ..&lt;/p&gt;
&lt;p&gt;Desde siempre me agradó escribir, asi que en algún momento inicié un &lt;a href="http://sudokill.blogspot.com/"&gt;blog&lt;/a&gt;, el cual estuvo alojado en blogger. El contenido fue variado: soluciones a problemas que aparecían en mi pc, apuntes sobre cosas nuevas aprendidas sobre Python o Linux, alguna que otra anotación para no olvidar .. entre otros.&lt;/p&gt;
&lt;p&gt;Ahora el blog estará en webfaction, y será generado mediante &lt;a href="http://pelican.readthedocs.org/en/latest/"&gt;pelican&lt;/a&gt;, un fantástico generador de sitios estáticos el cual conocí por medio del &lt;a href="http://joedicastro.com/category/blog.html"&gt;blog&lt;/a&gt; de Joe di castro …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hola mundo .. nuevamente ..&lt;/p&gt;
&lt;p&gt;Desde siempre me agradó escribir, asi que en algún momento inicié un &lt;a href="http://sudokill.blogspot.com/"&gt;blog&lt;/a&gt;, el cual estuvo alojado en blogger. El contenido fue variado: soluciones a problemas que aparecían en mi pc, apuntes sobre cosas nuevas aprendidas sobre Python o Linux, alguna que otra anotación para no olvidar .. entre otros.&lt;/p&gt;
&lt;p&gt;Ahora el blog estará en webfaction, y será generado mediante &lt;a href="http://pelican.readthedocs.org/en/latest/"&gt;pelican&lt;/a&gt;, un fantástico generador de sitios estáticos el cual conocí por medio del &lt;a href="http://joedicastro.com/category/blog.html"&gt;blog&lt;/a&gt; de Joe di castro, los cuales recomiendo por cierto.&lt;/p&gt;
&lt;p&gt;Aunque &lt;strong&gt;pelican&lt;/strong&gt; tiene una utilidad que permite importar entradas de blogger, decidí que 'empezaría de cero', aunque claro tal vez copie alguna que otra entrada del sitio anterior que considere de utilidad. Si bien trataré de ser constante en las publicaciones .. uno nunca sabe.&lt;/p&gt;
&lt;p&gt;Para terminar .. con esta publicación, un poco de código&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;  (solo porque me gusta el cómo se ve coloreado):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;lifetime&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;each_day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lifetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;carpe_diem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Visto en el manual de &lt;a href="http://joedicastro.com/pages/markdown.html#header2"&gt;markdown&lt;/a&gt; de Joe.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="blog"/><category term="informativo"/></entry></feed>