<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fabian Anguiano</title>
    <description>The latest articles on DEV Community by Fabian Anguiano (@thetrebelcc).</description>
    <link>https://dev.to/thetrebelcc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F311064%2F07d787d0-069a-4f1b-9d1e-2644aaf5e784.jpeg</url>
      <title>DEV Community: Fabian Anguiano</title>
      <link>https://dev.to/thetrebelcc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thetrebelcc"/>
    <language>en</language>
    <item>
      <title>How to Programmatically Cancel MRP Records in Odoo</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Thu, 04 Apr 2024 21:50:35 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/how-to-programmatically-cancel-mrp-records-in-odoo-k14</link>
      <guid>https://dev.to/thetrebelcc/how-to-programmatically-cancel-mrp-records-in-odoo-k14</guid>
      <description>&lt;h1&gt;
  
  
  How to Programmatically Cancel MRP Records in Odoo
&lt;/h1&gt;

&lt;p&gt;Sometimes, it's necessary to cancel specific manufacturing orders (MRPs) due to various reasons such as production issues, changes in demand, or material shortages. This article demonstrates how to programmatically cancel MRP records in Odoo using Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;Before proceeding, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Odoo instance accessible via network&lt;/li&gt;
&lt;li&gt;Administrative rights or API access credentials&lt;/li&gt;
&lt;li&gt;Python environment with &lt;code&gt;xmlrpc.client&lt;/code&gt; and &lt;code&gt;requests&lt;/code&gt; libraries installed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Disable SSL Verification
&lt;/h2&gt;

&lt;p&gt;In a development or testing environment, you might encounter SSL verification issues. The first part of the code disables SSL verification to avoid these problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ssl&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;_create_unverified_https_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_create_unverified_context&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Legacy Python that doesn't verify HTTPS certificates by default
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Handle target environment that doesn't support HTTPS verification
&lt;/span&gt;    &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_create_default_https_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_create_unverified_https_context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Disabling SSL verification in a production environment is not recommended due to security risks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Authenticate with Odoo
&lt;/h2&gt;

&lt;p&gt;You need to authenticate with the Odoo server to access its API. Replace the &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;db&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt; with your Odoo server's details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;xmlrpc.client&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_db_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://your_odoo_server_ip:8069&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_default_context&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;check_hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&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;verify_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CERT_NONE&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;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{}/xmlrpc/2/object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;allow_none&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_create_unverified_context&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{}/xmlrpc/2/common&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;allow_none&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_create_unverified_context&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&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;h2&gt;
  
  
  Step 3: Cancel MRP Records
&lt;/h2&gt;

&lt;p&gt;The following code searches for MRP records in the state 'to_close', cancels the associated picking records, and then cancels the MRP records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mrp_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mrp.production&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;to_close&lt;/span&gt;&lt;span class="sh"&gt;'&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;pick&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mrp_active&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="n"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;picking_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&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;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stock.picking&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action_cancel&lt;/span&gt;&lt;span class="sh"&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;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Error canceling picking:&lt;/span&gt;&lt;span class="sh"&gt;'&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mrp.production&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;action_cancel&lt;/span&gt;&lt;span class="sh"&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;pick&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
        &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mrp.production&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="sh"&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;pick&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cancel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MRP record cancelled:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Error canceling MRP record:&lt;/span&gt;&lt;span class="sh"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Automating the cancellation of MRP records in Odoo can save time and reduce errors, especially when dealing with multiple records. The approach outlined above provides a basic framework for interacting with Odoo's API, which you can expand based on your specific requirements. Always test your scripts in a development environment before applying them to production to avoid unintended consequences.&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>python</category>
    </item>
    <item>
      <title>Intro to Python's "Lock" w/ Flask</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Tue, 12 Dec 2023 23:41:36 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/intro-to-pythons-lock-w-flask-2o88</link>
      <guid>https://dev.to/thetrebelcc/intro-to-pythons-lock-w-flask-2o88</guid>
      <description>&lt;h1&gt;
  
  
  Mastering Concurrency in Flask with Python's &lt;code&gt;Lock&lt;/code&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the dynamic world of web development, Python's Flask framework stands out for its simplicity and flexibility. However, as with any web framework, Flask applications can run into concurrency issues, especially when multiple requests try to modify shared resources simultaneously. This article delves into one such challenge and demonstrates how Python's &lt;code&gt;Lock&lt;/code&gt; mechanism provides an elegant solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge of Concurrent Requests
&lt;/h2&gt;

&lt;p&gt;Consider a Flask application receiving data through an API endpoint. This data might be processed and stored, or used to modify existing records in a database. Now, imagine multiple requests hitting the endpoint at the same time. Without proper handling, these concurrent requests could lead to race conditions, where the outcome depends on the sequence or timing of the requests. Such scenarios can result in data corruption, duplication of records, or other unintended consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Python's &lt;code&gt;Lock&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To manage such concurrency issues, Python offers a synchronization mechanism called &lt;code&gt;Lock&lt;/code&gt; in its &lt;code&gt;threading&lt;/code&gt; module. A lock allows only one thread to access a particular block of code at a time, effectively preventing other threads from executing the same code block until the lock is released.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing &lt;code&gt;Lock&lt;/code&gt; in Flask
&lt;/h2&gt;

&lt;p&gt;Here's how you can implement a &lt;code&gt;Lock&lt;/code&gt; in a Flask route:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Import the Lock:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Lock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize the Lock:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the Lock in a Route:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/endpoint&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_request&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
       &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
           &lt;span class="c1"&gt;# Process the request
&lt;/span&gt;           &lt;span class="k"&gt;pass&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Request processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this setup, when a POST request hits the &lt;code&gt;/endpoint&lt;/code&gt;, the &lt;code&gt;with lock:&lt;/code&gt; statement ensures that only one request at a time enters the processing block. Subsequent requests wait until the lock is released. This sequential processing guards against the dangers of concurrent access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits and Considerations
&lt;/h2&gt;

&lt;p&gt;The primary benefit of using &lt;code&gt;Lock&lt;/code&gt; is the prevention of race conditions, ensuring data integrity and consistency. However, it's crucial to use locks judiciously. Overuse can lead to bottlenecks, reducing the application's ability to handle high volumes of traffic efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Concurrency management is a critical aspect of web application development. In Flask, Python's &lt;code&gt;Lock&lt;/code&gt; provides a straightforward yet powerful tool to handle concurrent requests safely. By integrating &lt;code&gt;Lock&lt;/code&gt; into your Flask routes, you can protect shared resources and maintain the integrity of your application's processes.&lt;/p&gt;

&lt;p&gt;If you have any questions or want to connect I am on &lt;a href="https://x.com/tunahorse21?t=AtMZYVon-YTic3MxkKt5xA&amp;amp;s=09"&gt;X&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flask</category>
      <category>python</category>
    </item>
    <item>
      <title>How to send PDF's with Telegram.</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Wed, 06 Dec 2023 21:58:19 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/how-to-send-pdfs-with-telegram-14ic</link>
      <guid>https://dev.to/thetrebelcc/how-to-send-pdfs-with-telegram-14ic</guid>
      <description>&lt;p&gt;In this article, we will cover how to send a PDF via telegram. &lt;br&gt;
NOTE: This guide assumes you have an API token from Telegram. &lt;/p&gt;

&lt;p&gt;Here is the code so you can see it before we start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests

def send_pdf(bot_token, chat_id, pdf_path):
    """
    Send a PDF document to a specific chat using Telegram Bot API.

    :param bot_token: str - Your Telegram bot token
    :param chat_id: str - Your chat id
    :param pdf_path: str - The path to the PDF file you want to send
    """
    method = "sendDocument"
    url = f"https://api.telegram.org/bot{bot_token}/{method}"
    files = {'document': open(pdf_path, 'rb')}
    data = {'chat_id': chat_id}
    response = requests.post(url, files=files, data=data)
    return response.json()  # Returns the result as a JSON object

# Call the function and print the result
result = send_pdf(bot_token, chat_id, pdf_path)
print(result)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a breakdown. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Function Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;pdf_path&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Description: This specifies the location of the PDF file you want to send.&lt;/li&gt;
&lt;li&gt;Usage: Replace &lt;code&gt;'Invoices1.pdf'&lt;/code&gt; with the path to your document.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

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

&lt;ul&gt;
&lt;li&gt;Description: This is your unique token for interacting with the Telegram bot.&lt;/li&gt;
&lt;li&gt;Usage: Replace &lt;code&gt;YOUR_BOT_TOKEN&lt;/code&gt; with the token you received from BotFather.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

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

&lt;ul&gt;
&lt;li&gt;Description: This is the identifier for the chat where you want to send your PDF.&lt;/li&gt;
&lt;li&gt;Usage: Replace &lt;code&gt;YOUR_CHAT_ID&lt;/code&gt; with the desired chat ID.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you set up your parameters, you move on to making the actual request to send the pdf. &lt;/p&gt;

&lt;h3&gt;
  
  
  Making the Request
&lt;/h3&gt;

&lt;p&gt;Inside the function, the code constructs and sends an HTTP request to the Telegram API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;: &lt;code&gt;method = "sendDocument"&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This specifies the action we want our bot to perform - sending a document.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Constructing the URL&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;url = f"https://api.telegram.org/bot{bot_token}/{method}"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This dynamically creates the request URL using your bot's token.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Preparing the File&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;files = {'document': open(pdf_path, 'rb')}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This opens your PDF in binary read mode, ready for transmission.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Sending the Request&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;response = requests.post(url, files=files, data=data)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This sends the file to the specified chat via your bot.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Execution
&lt;/h2&gt;

&lt;p&gt;result = send_pdf(bot_token, chat_id, pdf_path)&lt;br&gt;
print(result)&lt;/p&gt;

&lt;p&gt;Once you execute, you will know pretty quickly if the PDF has been sent ! Feel free to comment or contact me with any questions. &lt;/p&gt;

&lt;p&gt;You can contact me at &lt;a href="https://twitter.com/tunahorse21?lang=en"&gt;X&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>How to run a internal Flask APK Server</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Wed, 11 Oct 2023 20:56:22 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/how-to-run-a-internal-flask-apk-server-14o0</link>
      <guid>https://dev.to/thetrebelcc/how-to-run-a-internal-flask-apk-server-14o0</guid>
      <description>&lt;h1&gt;
  
  
  Flask APK Server
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This Flask application serves an APK file to clients. Developed as a minimalistic solution, the app is designed to be used in a controlled environment with a limited number of users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Simple endpoint (&lt;code&gt;/apk&lt;/code&gt;) to download the APK.&lt;/li&gt;
&lt;li&gt;Endpoint (&lt;code&gt;/test&lt;/code&gt;) to test the server's functionality.&lt;/li&gt;
&lt;li&gt;Endpoint (&lt;code&gt;/apk-test&lt;/code&gt;) to check the presence and size of the APK file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Flask App:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
python
from flask import Flask, send_file
import os

app = Flask(__name__)

@app.route('/apk')
def download_apk():
    apk_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app.apk')
    return send_file(apk_path, as_attachment=True, download_name='app.apk')

@app.route('/apk-test')
def test_apk():
    apk_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app.apk')
    try:
        size = os.path.getsize(apk_path)
        return jsonify(status="success", message=f"The APK exists and its size is {size} bytes")
    except OSError:
        return jsonify(status="error", message="The APK does not exist or there's a problem accessing it.")

@app.route('/test')
def test_route():
    return "Test route is working!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=2000, debug=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>flask</category>
      <category>python</category>
    </item>
    <item>
      <title>Automating Product Descriptions in Odoo with OpenAI's GPT-3 and Python</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Fri, 15 Sep 2023 21:24:59 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/automating-product-descriptions-in-odoo-with-openais-gpt-3-and-python-p3</link>
      <guid>https://dev.to/thetrebelcc/automating-product-descriptions-in-odoo-with-openais-gpt-3-and-python-p3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you're managing an online store or any business that requires a catalog of products, you know how time-consuming it can be to write unique and engaging product descriptions. What if there was a way to automate this process?&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll walk you through how to automatically generate product descriptions in Odoo using OpenAI's GPT-3. We'll use Python to bridge the gap between Odoo and OpenAI's API. Don't worry if you're not a coding expert; we'll guide you through each step, including an explanation of the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Odoo Account&lt;/strong&gt;: You should have an Odoo instance where you can add or update products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI API Key&lt;/strong&gt;: Get an API key from OpenAI by signing up at their &lt;a href="https://beta.openai.com/signup/"&gt;Developer Dashboard&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.x&lt;/strong&gt;: Make sure Python is installed on your machine. You can download it from &lt;a href="https://www.python.org/downloads/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For more Python and Odoo scripts, you can check out this GitHub repository: &lt;a href="https://github.com/tunahorse/Odoo-Unable-to-Unreserve-Fix-Script"&gt;Odoo Unable to Unreserve Fix Script&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Install Required Python Packages
&lt;/h3&gt;

&lt;p&gt;First, you need to install some Python packages that our script will use. Open your terminal and run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
pip install xmlrpc.client openai configparser&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a Configuration File
&lt;/h3&gt;

&lt;p&gt;Create a new text file named &lt;code&gt;config.ini\&lt;/code&gt; in your working directory and paste the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;`ini&lt;br&gt;
[OpenAI]&lt;br&gt;
api_key=YOUR_OPENAI_API_KEY&lt;/p&gt;

&lt;p&gt;[Odoo]&lt;br&gt;
url=YOUR_ODOO_URL&lt;br&gt;
db=YOUR_ODOO_DB_NAME&lt;br&gt;
username=YOUR_ODOO_USERNAME&lt;br&gt;
password=YOUR_ODOO_PASSWORD&lt;br&gt;
`&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Replace the placeholders with your actual OpenAI API key and Odoo credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Write the Python Script
&lt;/h3&gt;

&lt;p&gt;Create a new Python file (&lt;code&gt;main.py\&lt;/code&gt;) and paste the script shared at the beginning of this article. Save the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Understanding the Code
&lt;/h3&gt;

&lt;p&gt;The script is divided into several key parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SSL Configuration&lt;/strong&gt;: This part of the code sets up a secure connection for the script. This is especially important if you're working in an environment that requires secure HTTPS connections but doesn't support HTTPS verification.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;python&lt;br&gt;
try:&lt;br&gt;
    _create_unverified_https_context = ssl._create_unverified_context&lt;br&gt;
except AttributeError:&lt;br&gt;
    pass&lt;br&gt;
else:&lt;br&gt;
    ssl._create_default_https_context = _create_unverified_https_context&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loading Configurations&lt;/strong&gt;: Here, we read the OpenAI and Odoo details from the &lt;code&gt;config.ini\&lt;/code&gt; file you created earlier.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;python&lt;br&gt;
config = configparser.ConfigParser()&lt;br&gt;
config.read('config.ini')&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Odoo Login&lt;/strong&gt;: This part logs you into your Odoo account and sets up the XML-RPC clients needed to interact with Odoo's API.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;python&lt;br&gt;
common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url))&lt;br&gt;
uid = common.authenticate(db, username, password, {})&lt;br&gt;
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url))&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fetching Products&lt;/strong&gt;: This part fetches the first 100 products from your Odoo database.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;python&lt;br&gt;
product_ids = models.execute_kw(db, uid, password,&lt;br&gt;
    'product.template', 'search',&lt;br&gt;
    [[]],&lt;br&gt;
    {'limit': 100})&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Updating Descriptions&lt;/strong&gt;: Here, for each product, we call the OpenAI API to generate a product description and then update the product in Odoo.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;python&lt;br&gt;
for product in products:&lt;br&gt;
    response = openai.Completion.create(&lt;br&gt;
        engine="text-davinci-003",&lt;br&gt;
        prompt=f"Describe the product: {product['name']}",&lt;br&gt;
        temperature=0.5,&lt;br&gt;
        max_tokens=100&lt;br&gt;
    )&lt;br&gt;
    description = response.choices[0].text.strip()&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 5: Run the Script
&lt;/h3&gt;

&lt;p&gt;Open your terminal, navigate to the folder where &lt;code&gt;main.py\&lt;/code&gt; is saved, and run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
python main.py&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Verify the Changes
&lt;/h3&gt;

&lt;p&gt;Log in to your Odoo account and navigate to the products. You should see that the product descriptions have been updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Automating product descriptions can save you a lot of time and effort. With just a few lines of Python code, we've created a powerful tool that uses OpenAI's GPT-3 to generate product descriptions and updates them in Odoo. This tutorial not only guides you through how to set up and use the script but also provides an in-depth explanation of how the code works. Happy automating!&lt;/p&gt;

</description>
      <category>python</category>
      <category>chatgpt</category>
      <category>openai</category>
    </item>
    <item>
      <title>Automating AWS WorkSpaces Tier Management with CloudFormation</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Wed, 06 Sep 2023 19:23:58 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/automating-aws-workspaces-tier-management-with-cloudformation-80m</link>
      <guid>https://dev.to/thetrebelcc/automating-aws-workspaces-tier-management-with-cloudformation-80m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Managing resources efficiently is crucial in any cloud environment. In the context of AWS WorkSpaces, keeping track of CPU utilization and making timely tier changes can help in optimizing costs and performance. This article presents a CloudFormation template that automatically manages AWS WorkSpaces tiers based on CPU utilization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Resources:
  # CloudWatch Alarms for CPU Utilization
  CpuUtilizationAlarmHigh:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: CpuHighUtilizationAlarm
      AlarmDescription: Trigger when CPU Utilization is high
      Namespace: AWS/WorkSpaces
      MetricName: CPUUtilization
      Statistic: Average
      Period: 300
      EvaluationPeriods: 1
      Threshold: 80
      ComparisonOperator: GreaterThanOrEqualToThreshold
      AlarmActions:
        - Ref: TierManagementSNSTopic

  CpuUtilizationAlarmLow:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: CpuLowUtilizationAlarm
      AlarmDescription: Trigger when CPU Utilization is low
      Namespace: AWS/WorkSpaces
      MetricName: CPUUtilization
      Statistic: Average
      Period: 300
      EvaluationPeriods: 1
      Threshold: 20
      ComparisonOperator: LessThanOrEqualToThreshold
      AlarmActions:
        - Ref: TierManagementSNSTopic

  # SNS Topic to be triggered by CloudWatch Alarms
  TierManagementSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: WorkSpaceTierManagement

  # Lambda function to change WorkSpace tier
  WorkSpaceTierChangeLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: WorkSpaceTierChange
      Handler: index.handler
      Role: arn:aws:iam::your-account-id:role/execution_role
      Code:
        ZipFile: |
          import boto3
          import json

          workspaces_client = boto3.client('workspaces')

          def handler(event, context):
              sns_message = json.loads(event['Records'][0]['Sns']['Message'])
              alarm_name = sns_message.get('AlarmName', 'default-alarm')

              # Fetch all WorkSpaces in a specific directory
              directory_id = "your-directory-id-here"
              workspaces = workspaces_client.describe_workspaces(DirectoryId=directory_id)['Workspaces']

              for workspace in workspaces:
                  workspace_id = workspace['WorkspaceId']

                  if 'High' in alarm_name:
                      change_workspace_tier(workspace_id, 'POWERPRO')
                  elif 'Low' in alarm_name:
                      change_workspace_tier(workspace_id, 'POWER')

          def change_workspace_tier(workspace_id, new_tier):
              try:
                  response = workspaces_client.modify_workspace_properties(
                      WorkspaceId=workspace_id,
                      WorkspaceProperties={
                          'ComputeTypeName': new_tier
                      }
                  )
                  print(f"Changed tier of {workspace_id} to {new_tier}")
              except Exception as e:
                  print(f"Failed to change tier: {e}")

      Runtime: python3.8
      Timeout: 5

  # SNS Subscription to Lambda
  SNSTopicSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: lambda
      TopicArn: !Ref TierManagementSNSTopic
      Endpoint: !GetAtt [WorkSpaceTierChangeLambdaFunction, Arn]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CloudFormation template comprises the following key components:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. CloudWatch Alarms
&lt;/h3&gt;

&lt;p&gt;Two CloudWatch Alarms are defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CpuUtilizationAlarmHigh&lt;/code&gt;: Triggers when CPU Utilization is above 80%.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CpuUtilizationAlarmLow&lt;/code&gt;: Triggers when CPU Utilization is below 20%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. SNS Topic
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;TierManagementSNSTopic&lt;/code&gt; is an SNS Topic that will receive notifications from the CloudWatch Alarms.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Lambda Function
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;WorkSpaceTierChangeLambdaFunction&lt;/code&gt; is triggered when an SNS notification is received. It changes the tier of WorkSpaces based on the alarm conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. SNS Subscription
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;SNSTopicSubscription&lt;/code&gt; subscribes the Lambda function to the SNS Topic, allowing the function to be invoked when an alarm triggers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI installed and configured&lt;/li&gt;
&lt;li&gt;A valid AWS WorkSpaces setup&lt;/li&gt;
&lt;li&gt;IAM role with sufficient permissions for CloudWatch and WorkSpaces (&lt;code&gt;execution_role&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment Instructions
&lt;/h2&gt;

&lt;p&gt;Follow these steps to deploy the CloudFormation template:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Validate the Template
&lt;/h3&gt;

&lt;p&gt;First, validate the CloudFormation template to ensure it is well-formed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation validate-template &lt;span class="nt"&gt;--template-body&lt;/span&gt; file://path/to/template.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Upload the Template to S3 (Optional)
&lt;/h3&gt;

&lt;p&gt;You can upload the template to an S3 bucket if it's too large or if you prefer to keep it centralized.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;path/to/template.yaml s3://your-s3-bucket/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Deploy the Stack
&lt;/h3&gt;

&lt;p&gt;You can deploy the CloudFormation stack using the AWS CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# If template is local&lt;/span&gt;
aws cloudformation create-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; WorkSpacesTierManagement &lt;span class="nt"&gt;--template-body&lt;/span&gt; file://path/to/template.yaml

&lt;span class="c"&gt;# Or if template is in S3&lt;/span&gt;
aws cloudformation create-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; WorkSpacesTierManagement &lt;span class="nt"&gt;--template-url&lt;/span&gt; https://s3.amazonaws.com/your-s3-bucket/template.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Monitor the Stack
&lt;/h3&gt;

&lt;p&gt;You can monitor the status of the stack in the AWS CloudFormation console or via the AWS CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; WorkSpacesTierManagement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This CloudFormation template offers an automated approach to manage AWS WorkSpaces tiers based on CPU utilization, thus optimizing resource usage. Deploying this stack in your AWS environment can significantly streamline your WorkSpaces management process.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Serving Dynamically Generated PDFs in Django Production</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Thu, 17 Aug 2023 22:02:28 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/serving-dynamically-generated-pdfs-in-django-production-2670</link>
      <guid>https://dev.to/thetrebelcc/serving-dynamically-generated-pdfs-in-django-production-2670</guid>
      <description>&lt;h2&gt;
  
  
  Serving Dynamically Generated PDFs in Django
&lt;/h2&gt;

&lt;p&gt;In many Django projects, there's a need to generate and serve files dynamically. One common scenario is generating PDFs on-the-fly based on user interactions. Here are various strategies to handle such cases, especially when serving these files with Django both in development (&lt;code&gt;DEBUG=True&lt;/code&gt;) and in production (&lt;code&gt;DEBUG=False&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Using a dedicated directory for generated files:
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step-by-Step Implementation:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a Directory&lt;/strong&gt;: Set up a directory named &lt;code&gt;generated_pdfs&lt;/code&gt; at the root level of your project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Update &lt;code&gt;urls.py&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add the following to your &lt;code&gt;urls.py&lt;/code&gt; to serve files from the &lt;code&gt;generated_pdfs&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.static&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'generated_pdfs/&amp;lt;path:path&amp;gt;/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'document_root'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&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;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'generated_pdfs'&lt;/span&gt;&lt;span class="p"&gt;)}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Modify the PDF generation logic&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When saving the dynamically generated PDF, ensure it's saved to the &lt;code&gt;generated_pdfs&lt;/code&gt; directory.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Access the PDFs&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After generating a PDF, it can be accessed via: &lt;code&gt;http://localhost:8000/generated_pdfs/&amp;lt;pdf_name&amp;gt;.pdf&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Using Django's static file handling:
&lt;/h3&gt;

&lt;p&gt;This method involves treating the generated PDFs like any other static file in Django, but it requires running the &lt;code&gt;collectstatic&lt;/code&gt; command each time a new file is generated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step-by-Step Implementation:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Save the PDF&lt;/strong&gt;: When generating a new PDF, save it in Django's static directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automate &lt;code&gt;collectstatic&lt;/code&gt; after PDF generation&lt;/strong&gt;:&lt;br&gt;
To ensure the newly generated PDFs are available immediately, run the &lt;code&gt;collectstatic&lt;/code&gt; command after generating a PDF. You can automate this by adding the following logic in your PDF generation view:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;call_command&lt;/span&gt;

&lt;span class="c1"&gt;# ... Your PDF generation logic ...
&lt;/span&gt;
&lt;span class="c1"&gt;# After saving the PDF, run collectstatic
&lt;/span&gt;&lt;span class="n"&gt;call_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'collectstatic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'--noinput'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Serving in Production&lt;/strong&gt;: In a production environment, ensure that you've set up a web server or a CDN to serve the static files.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Troubleshooting:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;If you encounter a &lt;strong&gt;HTTP 500 error&lt;/strong&gt;, check the Django development server console logs for detailed error messages.&lt;/li&gt;
&lt;li&gt;Ensure the &lt;code&gt;generated_pdfs&lt;/code&gt; directory and the files within have the appropriate read permissions.&lt;/li&gt;
&lt;li&gt;In the case of conflicting URL patterns, Django evaluates them in order. Ensure that no pattern above the &lt;code&gt;generated_pdfs&lt;/code&gt; pattern is catching the request first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Follow me on &lt;a href="https://twitter.com/tunahorse21?t=xpoeAEPYS2X_9CU_nJg39w&amp;amp;s=09"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>django</category>
    </item>
    <item>
      <title>A Guide to Pagination in the 3dcart API: Fetching Sales Data</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Tue, 04 Jul 2023 19:11:48 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/a-guide-to-pagination-in-the-3dcart-api-fetching-sales-data-227d</link>
      <guid>https://dev.to/thetrebelcc/a-guide-to-pagination-in-the-3dcart-api-fetching-sales-data-227d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Working with APIs often involves retrieving large amounts of data. To efficiently handle all of this data, pagination is a crucial technique. This article aims to guide you through the process of implementing pagination with the 3dcart API, using a Python script as an example. By the end, you'll have a clear understanding of how to fetch sales data from the 3dcart API using pagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving into pagination, ensure you have the following prerequisites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Basic knowledge of Python programming.&lt;/li&gt;
&lt;li&gt;Familiarity with making API requests using Python.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up the Script
&lt;/h2&gt;

&lt;p&gt;To begin, let's set up a script that fetches sales data from the 3dcart API. The script provided below serves as a starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Import the necessary libraries
import requests
import json
import time

def fetch_sales_data_from_api(sales_order_names):
    # Load headers from the config file
    with open('config.json') as config_file:
        config = json.load(config_file)

    # Set up the base URL and headers
    base_url = "https://apirest.3dcart.com/3dCartWebAPI/v1/Orders"
    headers = {
        'SecureURL': config['SecureURL'],
        'PrivateKey': config['PrivateKey'],
        'Token': config['Token']
    }

    # Set pagination parameters
    limit = 100  # Number of results per page
    offset = 0   # Initial offset value
    max_retries = 3

    all_order_data = []

    # Iterate over the order statuses
    for order_status in sales_order_names:
        # Remove the "WH-" prefix from the order status
        invoice_number = order_status.replace("WH-", "")

        # Reset the offset for each order status
        offset = 0

        # Construct the initial URL
        url = f"{base_url}?invoicenumber={invoice_number}&amp;amp;limit={limit}&amp;amp;offset={offset}"

        # Retry logic
        success = False
        retries = 0

        while not success and retries &amp;lt; max_retries:
            response = requests.request("GET", url, headers=headers)

            if response.status_code == 200:
                success = True
                print(f"Successfully fetched data from {url}")
                orders = response.json()
                if orders:
                    all_order_data.extend(orders)
                break

            else:
                print("Retrying...")
                retries += 1
                time.sleep(10)

        if not success:
            print("Failed to fetch data after multiple retries.")

    return all_order_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  What is Pagination ?
&lt;/h1&gt;

&lt;p&gt;Pagination is a technique used to retrieve large amounts of data in manageable chunks or pages. The 3dcart API supports pagination by allowing you to specify the &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;offset&lt;/code&gt; parameters in your API requests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;limit&lt;/code&gt; parameter determines the number of results per page.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;offset&lt;/code&gt; parameter indicates the starting position of the data to be retrieved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically think of pagination as breaking up large chunks of data to sort data faster. &lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Pagination
&lt;/h2&gt;

&lt;p&gt;In the provided script, pagination is implemented in the &lt;code&gt;fetch_sales_data_from_api&lt;/code&gt; function. Here's a breakdown of how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The script loads the necessary headers from a configuration file (&lt;code&gt;config.json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It sets up the base URL and constructs the headers for the API request.&lt;/li&gt;
&lt;li&gt;Pagination parameters are defined:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;limit&lt;/code&gt;: The number of results to retrieve per page. In the example, it's set to 100.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;offset&lt;/code&gt;: The initial starting position. It's reset for each order status in the loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The script initializes an empty list, &lt;code&gt;all_order_data&lt;/code&gt;, to store the fetched order data.&lt;/li&gt;
&lt;li&gt;For each order status, the script constructs the URL by combining the base URL, the invoice number (with the "WH-" prefix removed), the limit, and the offset.&lt;/li&gt;
&lt;li&gt;A retry loop is implemented to handle potential failures during the API request. It tries up to &lt;code&gt;max_retries&lt;/code&gt; times, with a 10-second delay between retries.&lt;/li&gt;
&lt;li&gt;Upon a successful response (status code 200), the script extracts the orders data from the response and appends it to the &lt;code&gt;all_order_data&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt;If the maximum number of retries is reached without success, an appropriate message is printed.&lt;/li&gt;
&lt;li&gt;Finally, the function returns the complete &lt;code&gt;all_order_data&lt;/code&gt; list.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explored how to implement pagination when working with the 3dcart API. By using the provided script as a starting point, you can fetch sales data from the API while handling pagination seamlessly. Remember to adapt the script and configuration file to suit your specific requirements.&lt;/p&gt;

&lt;p&gt;Pagination is a powerful technique that allows you to work with large datasets more efficiently. With this knowledge, you're now equipped to fetch data from the 3dcart API using pagination effectively.&lt;/p&gt;

&lt;p&gt;Here is a gist of the code&lt;br&gt;
&lt;a href="https://gist.github.com/tunahorse/01d74fd67af06b625ce5c59481f906d9"&gt;https://gist.github.com/tunahorse/01d74fd67af06b625ce5c59481f906d9&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://twitter.com/tunahorse21?t=xpoeAEPYS2X_9CU_nJg39w&amp;amp;s=09"&gt;twitter &lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Create a Odoo email sales report, with Python.</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Mon, 26 Jun 2023 21:18:50 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/create-a-odoo-email-sales-report-with-python-1a8c</link>
      <guid>https://dev.to/thetrebelcc/create-a-odoo-email-sales-report-with-python-1a8c</guid>
      <description>&lt;h2&gt;
  
  
  Odoo Email Noty: Sales Reporting Tool
&lt;/h2&gt;

&lt;p&gt;I created a Python script helps you conveniently pull sales data from your Odoo server and compile it into a detailed email report. &lt;/p&gt;

&lt;p&gt;Here is the repo. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tunahorse/Odoo_email_noty"&gt;https://github.com/tunahorse/Odoo_email_noty&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pull sales data from an Odoo server&lt;/li&gt;
&lt;li&gt;Segment sales data &lt;/li&gt;
&lt;li&gt;Generate a detailed report of total orders and total amounts for both segments&lt;/li&gt;
&lt;li&gt;Send the generated report via email, customized with the details of each order&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get user input for date
&lt;/h2&gt;

&lt;p&gt;date_input = input("Please enter a dateThe script you've provided is a Python script that pulls sales data from an Odoo server and sends an email with the sales report. The report contains a summary of total orders and the total order amount, as well as details of each order.&lt;/p&gt;

&lt;p&gt;Here's a detailed breakdown of what each part of the script does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The script starts by importing several Python libraries which are required for its operation:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- `xmlrpc.client`: This is used to make remote procedure call (RPC) to the Odoo server.
- `ssl`: This is used to create a secure connection to the Odoo server.
- `datetime`, `date`, `timedelta`: These are used to manipulate dates.
- `email.mime.multipart` and `email.mime.text`: These are used to create and manipulate the email to be sent.
- `smtplib`: This is used to send the email.
- `configparser`: This is used to read the configuration file which contains the necessary details to connect to the Odoo server and the email server.
- `pprint`: This is used to pretty-print data for debugging purposes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The script reads the configuration file named 'config.ini' using &lt;code&gt;configparser&lt;/code&gt;. This file contains necessary information such as the Odoo server details (URL, database name, username, and password) and the email server details (SMTP server, port, from address, to address, and password).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It creates an SSL context to allow connection to a self-signed Odoo server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It connects to the Odoo server using &lt;code&gt;xmlrpc.client.ServerProxy&lt;/code&gt; and authenticates with the server using the username and password from the config file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It takes user input for a date. If no date is entered or if the entered date is not valid, it uses today's date.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It fetches sales data from the Odoo server using the &lt;code&gt;models.execute_kw&lt;/code&gt; function. It uses the 'sale.order' model and the 'search_read' method to fetch sales orders for the specified date.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It calculates the total number of orders and the total order amount by iterating over the fetched sales data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It creates an email using &lt;code&gt;MIMEMultipart&lt;/code&gt; and sets the 'From', 'To', and 'Subject' fields.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It creates the body of the email by adding the total number of orders and the total order amount. It then iterates over the sales data again to add the details of each order to the email body.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It attaches the body to the email using &lt;code&gt;msg.attach&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It connects to the SMTP server using &lt;code&gt;smtplib.SMTP&lt;/code&gt;, logs in with the 'From' email address and password, and sends the email using &lt;code&gt;server.sendmail&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, it disconnects from the SMTP server using &lt;code&gt;server.quit&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember to replace all the placeholder values in the 'config.ini' file with your actual details, and also make sure that the Odoo server is properly set up and contains the 'sale.order' model with the required fields.&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Managing Duplicate Sales Records in Odoo Using Python</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Wed, 24 May 2023 20:32:15 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/handling-double-sales-records-in-odoo-with-python-3o1f</link>
      <guid>https://dev.to/thetrebelcc/handling-double-sales-records-in-odoo-with-python-3o1f</guid>
      <description>&lt;p&gt;I recently had a situation where an Odoo instance was importing sales from multiple sales channels, due to the nature of the import process some sales got created twice. Technically sales records with the same name should not allowed, but alas here we are. &lt;/p&gt;

&lt;p&gt;I made this script to find any records with the same name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import xmlrpc.client

# Odoo details
url = 'your_url'
db = 'your_db'
username = 'your_username'
password = 'your_password'

# Get the uid
common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url))
uid = common.authenticate(db, username, password, {})

# Get the models
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url))

# Search for sales order records
sales_order_ids = models.execute_kw(db, uid, password,
    'sale.order', 'search',
    [[]])

# Read the sales order records
sales_orders = models.execute_kw(db, uid, password,
    'sale.order', 'read',
    [sales_order_ids])

# Dictionary to store the count of each record
record_count = {}

# Go through each sales order
for order in sales_orders:
    # If the order name is not in the record_count
    if order['name'] not in record_count:
        # Add it with a count of 1
        record_count[order['name']] = 1
    else:
        # If it is already there, increment the count
        record_count[order['name']] += 1

# Print the repeated records
for record, count in record_count.items():
    if count &amp;gt; 1:
        print(f"\033[91m Multiple sales records: {record} \033[0m")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
    </item>
    <item>
      <title>ChatGPT == Slack: The Impact of User Base on the Workflow Stack</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Mon, 22 May 2023 18:42:18 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/chatgpt-slack-the-impact-of-user-base-on-the-workflow-stack-n01</link>
      <guid>https://dev.to/thetrebelcc/chatgpt-slack-the-impact-of-user-base-on-the-workflow-stack-n01</guid>
      <description>&lt;p&gt;In the digital landscape, many tools and platforms vie for dominance in the enterprise ecosystem. Among these, chat and collaboration tools like Slack and AI-based language models like ChatGPT, a product from OpenAI, have become staples of the modern workflow stack. While these tools may seem dissimilar, their success is deeply rooted in the same concept: User adoption and seamless integration within the workflow stack. In this article, we'll explore how these tools affect the workflow stack and why enterprises tend to adopt them, focusing on the case of ChatGPT's triumph over its competitor, Bard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Workflow Stack
&lt;/h2&gt;

&lt;p&gt;Before we delve into comparisons, let's define the "workflow stack". The workflow stack is an enterprise's unique combination of tools and software that streamline business processes. These can include anything from project management software to AI-assisted language models that help generate content. Slack, an instant messaging platform, is commonly found in the workflow stack due to its functionality for team communication and project collaboration. &lt;/p&gt;

&lt;p&gt;ChatGPT, on the other hand, is an AI language model that provides different services, such as drafting emails, creating content, coding assistance, and more. Its flexibility and multi-functionality make it a valuable addition to the workflow stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power of User Adoption: Slack's Story
&lt;/h2&gt;

&lt;p&gt;Slack's dominance in the market comes from its widespread adoption. Even though there might be arguably superior tools, the sheer number of users who have already integrated Slack into their workflow stack makes it a hard-to-replace element of many enterprises. Slack's easy-to-use interface, seamless integration with other tools, and efficient communication system have made it the go-to choice for team collaboration. &lt;/p&gt;

&lt;p&gt;Enterprises often have to pay the fees associated with Slack, not necessarily because it's the best tool available, but because it is so deeply ingrained in their workflow stack. Switching to a different tool would mean massive training costs, disruption of work, and potential communication breakdowns - all costs that most businesses would rather avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ChatGPT Advantage: User Adoption and Flexibility
&lt;/h2&gt;

&lt;p&gt;Much like Slack, ChatGPT's success is also largely determined by its user base. However, ChatGPT's victory over competitors like Bard is rooted in two main factors: user adoption and flexibility.&lt;/p&gt;

&lt;p&gt;ChatGPT boasts a large user base, which continues to grow thanks to its ability to provide diverse solutions. This wide user adoption not only signifies its current triumph but also suggests a prosperous future. As more users integrate ChatGPT into their workflow stack, its usefulness and value within the enterprise ecosystem grow.&lt;/p&gt;

&lt;p&gt;Moreover, ChatGPT offers an unparalleled level of flexibility. It's not limited to one function but provides a range of services, from generating content to drafting emails and even assisting in coding. This multi-functionality allows enterprises to use it in different aspects of their workflow stack, reinforcing its adoption and further solidifying its position within the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;User adoption and integration within the workflow stack are critical success factors for digital tools. Both Slack and ChatGPT exemplify how a large user base and seamless integration can secure a tool's place within the enterprise workflow stack.&lt;/p&gt;

&lt;p&gt;While Slack's place is maintained more due to the costs and complications associated with switching, ChatGPT is securing its future through widespread user adoption and versatility. As we look to the future of work, it will be interesting to see how these factors continue to shape the tools that become integral parts of our workflow stacks.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Will Google's BARD Fall Flat?</title>
      <dc:creator>Fabian Anguiano</dc:creator>
      <pubDate>Wed, 17 May 2023 16:16:21 +0000</pubDate>
      <link>https://dev.to/thetrebelcc/-will-googles-bard-fall-flat-1e15</link>
      <guid>https://dev.to/thetrebelcc/-will-googles-bard-fall-flat-1e15</guid>
      <description>&lt;p&gt;Google's Bard, a collaborative AI tool, has been making waves since its launch less than two months ago. It started as an experiment that allowed users to interact with AI in new and creative ways, and the response from users has been largely positive. With its rapid evolution, Bard has been able to incorporate advanced math and reasoning skills, and even coding capabilities, making it one of the most versatile tools in the AI landscape today&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;While the introduction of new features and the expansion of Bard into more countries suggests a bright future for the product&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, some observers have raised concerns about Google's track record with ambitious projects.&lt;/p&gt;

&lt;p&gt;There's no denying that Google's history of terminating projects raises doubts about Bard's future. From Google Optimize to Google Cloud IoT Core, the tech giant has a long list of 285 services that it has killed off over the years&lt;sup id="fnref3"&gt;3&lt;/sup&gt;. This "Google Graveyard" has led many to wonder if Bard will eventually meet the same fate. &lt;/p&gt;

&lt;p&gt;The nature of large organizations like Google can be a double-edged sword. On one hand, their resources allow for ambitious projects like Bard to be conceived and developed. On the other hand, the bureaucratic nature of large corporations often leads to slow decision-making processes and a lack of agility. This can negatively impact the usability of their products. &lt;/p&gt;

&lt;p&gt;For example, Google's previous attempts at social networks, Google Plus and Google Buzz, faced criticism for their confusing user interfaces and lack of unique features. These projects were eventually terminated. The lessons from these experiences must be kept in mind as Google continues to develop and improve Bard.  &lt;/p&gt;

&lt;p&gt;One of the central themes of the HBO series "Silicon Valley" is the struggle of innovators against corporate giants. In one memorable scene, Hooli, the show's Google-esque tech behemoth, attempts to reverse engineer a rival's product. Despite having a team of skilled engineers and vast resources, the project falls behind schedule&lt;sup id="fnref4"&gt;4&lt;/sup&gt;. This fictional scenario echoes real concerns about Google's ability to deliver on its ambitious AI projects. The "behind schedule" scene from "Silicon Valley" serves as a humorous but pointed reminder of the potential pitfalls that Google's Bard could face in its developmental journey.&lt;/p&gt;

&lt;p&gt;Despite these concerns, it's important to note that Bard is still in its early stages, and Google has demonstrated a commitment to its development and improvement. The recent updates, including the integration of Google Lens and new coding features, suggest that Google is serious about making Bard a long-term project&lt;sup id="fnref5"&gt;5&lt;/sup&gt;&lt;sup id="fnref6"&gt;6&lt;/sup&gt;&lt;sup id="fnref7"&gt;7&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;However, only time will tell if Bard can avoid the fate of its predecessors and become a lasting part of Google's product suite. Will Bard become another tombstone in the Google Graveyard or will it defy the odds and become a lasting success? That's a story still being written.&lt;/p&gt;

&lt;p&gt;Yes, this was written with ChatGPT, I tried to use Bard but the result were subpar. &lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;source ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;source ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;source ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;source ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;source ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;source ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;source ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>chatgpt</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
