blog.notmyidea.org/changing-the-primary-key-of-a-model-in-django.html

149 lines
No EOL
12 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<title>
Changing the primary key of a model in&nbsp;Django - Alexis Métaireau </title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://blog.notmyidea.org/theme/css/main.css?v2"
type="text/css" />
<link href="https://blog.notmyidea.org/feeds/all.atom.xml"
type="application/atom+xml"
rel="alternate"
title="Alexis Métaireau ATOM Feed" />
</head>
<body>
<div id="content">
<section id="links">
<ul>
<li>
<a class="main" href="/">Alexis Métaireau</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/journal/index.html">Journal</a>
</li>
<li>
<a class="selected"
href="https://blog.notmyidea.org/code/">Code, etc.</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/weeknotes/">Notes hebdo</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/lectures/">Lectures</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/projets.html">Projets</a>
</li>
</ul>
</section>
<header>
<h1 class="post-title">Changing the primary key of a model in&nbsp;Django</h1>
<time datetime="2024-02-22T00:00:00+01:00">22 février 2024</time>
</header>
<article>
<p>I had to change the primary key of a django model, and I wanted to create a
migration for&nbsp;this.</p>
<p>The previous model was using django <a href="https://
docs.djangoproject.com/en/5.0/topics/db/models/#automatic-primary-key-fields">automatic primary key&nbsp;fields</a></p>
<p>I firstly changed the model to include the new <code>uuid</code> field, and added the <code>id</code>
field (the old primary key), like&nbsp;this:</p>
<div class="highlight"><pre><span></span><code> <span class="n">uuid</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">UUIDField</span><span class="p">(</span>
<span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">,</span> <span class="n">editable</span><span class="o">=</span><span class="kc">False</span>
<span class="p">)</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">IntegerField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre></div>
<p>Then I created the migration,&nbsp;it:</p>
<ul>
<li>Adds a new <code>uuid</code> field/column in the&nbsp;database</li>
<li>Iterate over the existing items in the table, and generates an uuid for&nbsp;them</li>
<li>Change the old primary key to a different&nbsp;type</li>
<li>Drop the old&nbsp;index</li>
<li>Mark the new uuid as a primary&nbsp;key.</li>
</ul>
<p>To generate the migrations I did <code>django-admin makemigrations</code>, and iterated on
it. Here is the migration I ended up&nbsp;with:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">uuid</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">migrations</span><span class="p">,</span> <span class="n">models</span>
<span class="k">class</span> <span class="nc">Migration</span><span class="p">(</span><span class="n">migrations</span><span class="o">.</span><span class="n">Migration</span><span class="p">):</span>
<span class="n">dependencies</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">(</span><span class="s2">&quot;umap&quot;</span><span class="p">,</span> <span class="s2">&quot;0017_migrate_to_openstreetmap_oauth2&quot;</span><span class="p">),</span>
<span class="p">]</span>
<span class="n">operations</span> <span class="o">=</span> <span class="p">[</span>
<span class="c1"># Add the new uuid field</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">AddField</span><span class="p">(</span>
<span class="n">model_name</span><span class="o">=</span><span class="s2">&quot;datalayer&quot;</span><span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="s2">&quot;uuid&quot;</span><span class="p">,</span>
<span class="n">field</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">UUIDField</span><span class="p">(</span>
<span class="n">default</span><span class="o">=</span><span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">,</span> <span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">serialize</span><span class="o">=</span><span class="kc">False</span>
<span class="p">),</span>
<span class="p">),</span>
<span class="c1"># Generate UUIDs for existing records</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">RunSQL</span><span class="p">(</span><span class="s2">&quot;UPDATE umap_datalayer SET uuid = gen_random_uuid()&quot;</span><span class="p">),</span>
<span class="c1"># Remove the primary key constraint</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">RunSQL</span><span class="p">(</span><span class="s2">&quot;ALTER TABLE umap_datalayer DROP CONSTRAINT umap_datalayer_pk&quot;</span><span class="p">),</span>
<span class="c1"># Drop the &quot;id&quot; primary key…</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">AlterField</span><span class="p">(</span>
<span class="s2">&quot;datalayer&quot;</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">&quot;id&quot;</span><span class="p">,</span> <span class="n">field</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">IntegerField</span><span class="p">(</span><span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="p">),</span>
<span class="c1"># … to put it back on the &quot;uuid&quot;</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">AlterField</span><span class="p">(</span>
<span class="n">model_name</span><span class="o">=</span><span class="s2">&quot;datalayer&quot;</span><span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="s2">&quot;uuid&quot;</span><span class="p">,</span>
<span class="n">field</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">UUIDField</span><span class="p">(</span>
<span class="n">default</span><span class="o">=</span><span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">,</span>
<span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
<span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">serialize</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">),</span>
<span class="p">]</span>
</code></pre></div>
<h2 id="generating-uuids-in-pure-python">Generating UUIDs in pure&nbsp;python</h2>
<p>The uuid generation can also be done with pure python, like this. It works with all databases, but might be slower. Use it with <code>migrations.RunPython()</code>.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">gen_uuid</span><span class="p">(</span><span class="n">apps</span><span class="p">,</span> <span class="n">schema_editor</span><span class="p">):</span>
<span class="n">DataLayer</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s2">&quot;umap&quot;</span><span class="p">,</span> <span class="s2">&quot;DataLayer&quot;</span><span class="p">)</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">DataLayer</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">():</span>
<span class="n">row</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">()</span>
<span class="n">row</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">update_fields</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;uuid&quot;</span><span class="p">])</span>
</code></pre></div>
<h2 id="getting-the-constraint-name">Getting the constraint&nbsp;name</h2>
<p>One of the things that took me some time is to have a way to get the constraint name before removing it. I wanted to do this with the Django <span class="caps">ORM</span>, but I didn&#8217;t find how. So here is how in plain <span class="caps">SQL</span>. This only works with PostgreSQL,&nbsp;though.</p>
<div class="highlight"><pre><span></span><code><span class="n">migrations</span><span class="o">.</span><span class="n">RunSQL</span><span class="p">(</span><span class="s2">&quot;&quot;&quot;</span>
<span class="s2">DO $$</span>
<span class="s2">BEGIN</span>
<span class="s2"> EXECUTE &#39;ALTER TABLE umap_datalayer DROP CONSTRAINT &#39; || (</span>
<span class="s2"> SELECT indexname</span>
<span class="s2"> FROM pg_indexes</span>
<span class="s2"> WHERE tablename = &#39;umap_datalayer&#39; AND indexname LIKE &#39;%pkey&#39;</span>
<span class="s2"> );</span>
<span class="s2">END $$;</span>
<span class="s2">&quot;&quot;&quot;</span><span class="p">),</span>
</code></pre></div>
<p>
<a href="https://blog.notmyidea.org/tag/django.html">#django</a>
, <a href="https://blog.notmyidea.org/tag/orm.html">#orm</a>
, <a href="https://blog.notmyidea.org/tag/migrations.html">#migrations</a>
- Posté dans la catégorie <a href="https://blog.notmyidea.org/code/">code</a>
</p>
</article>
<footer>
<a id="feed" href="/feeds/all.atom.xml">
<img alt="RSS Logo" src="/theme/rss.svg" />
</a>
</footer>
</div>
</body>
</html>