<?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: Zahidul Islam</title>
    <description>The latest articles on DEV Community by Zahidul Islam (@thezahids).</description>
    <link>https://dev.to/thezahids</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4170074%2F4d9f11cd-f72c-4daa-9bda-b24b40b6b238.png</url>
      <title>DEV Community: Zahidul Islam</title>
      <link>https://dev.to/thezahids</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thezahids"/>
    <language>en</language>
    <item>
      <title>Odoo 19 Odoo 20 Migration Notes &amp; Fixes</title>
      <dc:creator>Zahidul Islam</dc:creator>
      <pubDate>Sat, 10 Oct 2026 22:16:39 +0000</pubDate>
      <link>https://dev.to/thezahids/odoo-19-odoo-20-migration-notes-fixes-h6g</link>
      <guid>https://dev.to/thezahids/odoo-19-odoo-20-migration-notes-fixes-h6g</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwf00rr77ii8b0m1middm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwf00rr77ii8b0m1middm.jpeg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;KeyError: 'ir.model.access'&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If your module fails to load with a &lt;code&gt;KeyError: 'ir.model.access'&lt;/code&gt;, check your security folder. Odoo 20 has changed how access rights are structured and named.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What changed:&lt;/strong&gt; The file &lt;code&gt;security/ir.model.access.csv&lt;/code&gt; has been replaced by &lt;code&gt;security/ir.access.csv&lt;/code&gt;, and the columns have been streamlined.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old format:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id,name,model_id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_case_user,Case User,model_disciplinary_case,my_module.group_user,1,1,1,0

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New format:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id,name,model_id,group_id/id,operation,domain
access_case_user,Case User,disciplinary.case,my_module.group_user,cru,

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Permission Mapping Quick Reference:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;r&lt;/code&gt; $\rightarrow$ read&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ru&lt;/code&gt; $\rightarrow$ read + update&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cru&lt;/code&gt; $\rightarrow$ create + read + update&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;crud&lt;/code&gt; $\rightarrow$ full access&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. &lt;code&gt;KeyError: 'ir.rule'&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If your security configuration breaks during registry loading, check your record rules alongside your access rights.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Old record rule (XML):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;record&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"rule_employee_case"&lt;/span&gt; &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"ir.rule"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"domain_force"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        [('employee_id.user_id', '=', user.id)]
    &lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/record&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New access domain format:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;access_case_employee,Case Employee,disciplinary.case,my_module.group_employee,r,"[('employee_id.user_id', '=', user.id)]"

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

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. &lt;code&gt;employee_type&lt;/code&gt; became &lt;code&gt;employee_type_id&lt;/code&gt; (And changed type!)
&lt;/h3&gt;

&lt;p&gt;This one caught me off guard because it wasn't just a simple rename.&lt;/p&gt;

&lt;p&gt;If you run:&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hr.employee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;_fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;employee_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;You will hit a &lt;code&gt;KeyError: 'employee_type'&lt;/code&gt;. Running a shell check reveals the new field:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hr.employee&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;_fields&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&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="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="c1"&gt;# Result: employee_type_id
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The catch:&lt;/strong&gt; It is no longer a Selection field; it's a &lt;strong&gt;Many2one&lt;/strong&gt; relation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Old code:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;employee_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Selection&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New code:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;employee_type_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Many2one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hr.employee.type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Employee Type&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't forget your domains:&lt;/strong&gt; Update your logic wherever the field is evaluated, e.g.:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&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;employee_type_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;employee_type_id&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employee_type_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;h3&gt;
  
  
  4. &lt;code&gt;_sql_constraints&lt;/code&gt; warning
&lt;/h3&gt;

&lt;p&gt;Odoo 20 deprecates the old dictionary-based SQL constraints in favor of explicit model constraints.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Old:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_sql_constraints&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unique_policy&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;unique(name, company_id)&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;Policy name must be unique per company!&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="p"&gt;]&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_unique_policy&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="nc"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNIQUE(name, company_id)&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;Policy name must be unique per company!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Quickly find lingering old constraints across your custom codebase using:&lt;br&gt;
&lt;code&gt;grep -R "_sql_constraints" custom_addons20&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. &lt;code&gt;report_file&lt;/code&gt; broke QWeb reports
&lt;/h3&gt;

&lt;p&gt;When running PDF reports, you might run into this error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;ValueError: Invalid field 'report_file' in 'ir.actions.report'&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Remove &lt;code&gt;&amp;lt;field name="report_file"&amp;gt;...&amp;lt;/field&amp;gt;&lt;/code&gt; entirely from your report actions. Keep only &lt;code&gt;report_name&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"report_name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    hr_gratuity.report_gratuity_settlement
&lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Test your PDF outputs thoroughly. For standard fields use &lt;code&gt;&amp;lt;span t-field="doc.name"/&amp;gt;&lt;/code&gt;, and for computed expressions use &lt;code&gt;&amp;lt;t t-out="doc.basic_percentage or 0"/&amp;gt;&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Check technical model names carefully
&lt;/h3&gt;

&lt;p&gt;A single-character typo can completely block a module from installing. Always double-check your model definitions. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong:&lt;/strong&gt; &lt;code&gt;disciplinary.action.action_type&lt;/code&gt; $\rightarrow$ &lt;strong&gt;Correct:&lt;/strong&gt; &lt;code&gt;disciplinary.action.type&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong:&lt;/strong&gt; &lt;code&gt;disciplinary.employee_response&lt;/code&gt; $\rightarrow$ &lt;strong&gt;Correct:&lt;/strong&gt; &lt;code&gt;disciplinary.employee.response&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify your technical names with:&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="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"_name ="&lt;/span&gt; custom_addons20/your_module/models &lt;span class="nt"&gt;-n&lt;/span&gt;

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

&lt;/div&gt;






&lt;h3&gt;
  
  
  Quick Migration Checklist
&lt;/h3&gt;

&lt;p&gt;Before pushing an upgraded Odoo 20 module to production, run through this mental or automated checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Security files (&lt;code&gt;ir.access.csv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;ir.rule&lt;/code&gt; usage &amp;amp; inline domains&lt;/li&gt;
&lt;li&gt;[ ] Model technical names&lt;/li&gt;
&lt;li&gt;[ ] Renamed fields (&lt;code&gt;employee_type&lt;/code&gt; $\rightarrow$ &lt;code&gt;employee_type_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Changed field types (Selection to Many2one)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;_sql_constraints&lt;/code&gt; to &lt;code&gt;models.Constraint&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] XML views &amp;amp; form layouts&lt;/li&gt;
&lt;li&gt;[ ] Report actions (cleaned &lt;code&gt;report_file&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] QWeb PDF output rendering&lt;/li&gt;
&lt;li&gt;[ ] Domains and &lt;code&gt;onchange&lt;/code&gt; methods&lt;/li&gt;
&lt;li&gt;[ ] User/group permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Handy search command to scan for outdated definitions:&lt;/strong&gt;&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="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"ir.model.access&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;ir.rule&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;_sql_constraints&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;report_file"&lt;/span&gt; custom_addons20/your_module &lt;span class="nt"&gt;-n&lt;/span&gt;

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  odoo #python #erp
&lt;/h1&gt;

</description>
      <category>erp</category>
      <category>odoo</category>
      <category>python</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
